hanoman 0.9.11 → 0.9.13
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/dist/build-info.json +3 -3
- package/dist/cli.js +358 -37
- package/dist/server.js +20667 -17844
- package/package.json +1 -1
- package/prisma/migrations/20261001081455_qa_workspace/migration.sql +85 -0
- package/prisma/migrations/20261001093314_qa_attachment_sync/migration.sql +31 -0
- package/prisma/schema.prisma +97 -0
- package/web/assets/{ChangelogScreen-CYHlMkuo.js → ChangelogScreen-Ceqp0J6L.js} +1 -1
- package/web/assets/{DalangHanomanScreen-BIREPQkt.js → DalangHanomanScreen-CPJtZxV3.js} +1 -1
- package/web/assets/{DocPreviewModal-Dy5PZX3L.js → DocPreviewModal-C_vQOOHV.js} +1 -1
- package/web/assets/{DocsWorkspace-sstV_mwF.js → DocsWorkspace-D4P_5t9b.js} +2 -2
- package/web/assets/{IdeScreen-BoZ21X0r.js → IdeScreen-bD4qiBDt.js} +1 -1
- package/web/assets/{LeadScreen-B8j5AbCq.js → LeadScreen-Db9PHjdT.js} +1 -1
- package/web/assets/{ReviewScreen-9jkwgxsp.js → ReviewScreen-DLlDOQod.js} +1 -1
- package/web/assets/{SchedulerScreen-BmeeP6iC.js → SchedulerScreen-D6ffDlTM.js} +1 -1
- package/web/assets/{SettingsScreen-BpjqRvvn.js → SettingsScreen-72EvmiY4.js} +4 -4
- package/web/assets/{TeamScreen-ftIlQ3wy.js → TeamScreen-CY-5tnPa.js} +1 -1
- package/web/assets/TerminalScreen-CKac8Ti6.js +2 -0
- package/web/assets/{TriageScreen-CMJajN4R.js → TriageScreen-BEb2vxPb.js} +1 -1
- package/web/assets/{VpsScreen-B1tHTxXY.js → VpsScreen-DTJd5Gks.js} +1 -1
- package/web/assets/index-BsjfXxQM.css +32 -0
- package/web/assets/index-DYNYB1Nj.js +1039 -0
- package/web/assets/{live-DZWDT8yU.js → live-nMnpVkPn.js} +1 -1
- package/web/index.html +2 -2
- package/web/assets/TerminalScreen-CQuR2PLL.js +0 -2
- package/web/assets/index-C0hF6fPX.js +0 -1017
- package/web/assets/index-ezlZRsLF.css +0 -32
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{aP as za,aQ as Oa,aR as Ia,aS as Pa,aT as Ba,aU as Ma,aV as La,aW as Da,aX as Ha,aY as _a,aZ as Na,a_ as Ra,a$ as Fa,b0 as Wa,b1 as $a,b2 as Ga,e as j,j as e,aq as C,c as ee,d as Y,ar as me,a as y,f as V,am as Ua,aI as N,B as ne,I as Fe,N as ue,b3 as Ta,C as ba,b4 as ka,b5 as Ne,b6 as xa,b7 as Ka,b8 as Oe,b9 as Qe,ba as Ja,bb as na,bc as Va,bd as Ya,be as Xa,A as qa,bf as Qa,bg as Za,S as M,bh as et,bi as Ye,bj as at,V as tt,Y as te,Z as fa,u as $e,bk as nt,bl as it,h as lt,a8 as st,bm as rt,p as dt,bn as ot,O as ja,bo as Se,bp as We,bq as ct,br as ut,bs as gt,bt as ht,bu as ya,bv as va,bw as Re,bx as Sa,by as mt,bz as pt,aL as bt,bA as Ee,bB as ia,H as kt,bC as la,bD as sa,bE as xt,a0 as Xe,bF as wa,bG as ft,W as jt,X as yt,bH as vt,bI as St}from"./index-C0hF6fPX.js";import{R as o}from"./react-DfGhg7ZW.js";import"./markdown-CWEY34jk.js";import{D as wt}from"./DocPreviewModal-Dy5PZX3L.js";import"./xterm-3VOAfa_q.js";const Ct=[...za,...Oa,...Ia,...Pa,...Ba,...Ma,...La,...Da,...Ha,..._a,...Na,...Ra,...Fa,...Wa,...$a,...Ga],Et=1;function At(){const[a,n]=o.useState(null),[i,r]=o.useState([]),[t,c]=o.useState(""),[d,h]=o.useState(""),[f,g]=o.useState([]),[u,k]=o.useState(null),z=o.useCallback(()=>j.listClientAccounts().then(x=>n(x.items)).catch(()=>n([])),[]);o.useEffect(()=>{z(),j.listProjects().then(x=>r(x.items.map(O=>({id:O.id,name:O.name})))).catch(()=>r([]))},[z]);const R=async()=>{k(null);try{await j.createClientAccount({email:t,password:d,projects:f}),c(""),h(""),g([]),await z()}catch{k("Gagal membuat akun — periksa email (mungkin sudah dipakai) dan panjang password.")}},F=async(x,O)=>{await j.updateClientAccount(x,O),await z()},A=async x=>{await j.deleteClientAccount(x),await z()},m=(x,O,K)=>K?[...x,O]:x.filter(U=>U!==O);return e.jsxs(e.Fragment,{children:[e.jsxs(C,{eyebrow:"akses",title:"Buat akun klien",children:[e.jsxs("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginBottom:14,lineHeight:1.5},children:["Akun klien melihat ",e.jsx("b",{children:"daftar backlog"})," dan ",e.jsx("b",{children:"tiket help desk"})," project yang dipilih — baca-saja, tanpa terminal, dokumen internal, maupun setelan. Ia tak pernah melihat dashboard operator."]}),e.jsx(ee,{label:"Email klien",children:e.jsx(Y,{"aria-label":"Email klien",placeholder:"mis. budi@tokomekar.co.id",value:t,onChange:x=>c(x.target.value)})}),e.jsx(ee,{label:"Password awal",hint:"Minimal 8 karakter. Sampaikan lewat kanal yang aman — klien bisa menggantinya sendiri.",children:e.jsx(Y,{"aria-label":"Password awal",type:"password",placeholder:"mis. kunci-tokomekar-2026",value:d,onChange:x=>h(x.target.value)})}),e.jsx(ee,{label:"Project yang boleh dilihat",children:e.jsx("div",{style:{display:"flex",flexWrap:"wrap",gap:14},children:i.map(x=>e.jsx(me,{"aria-label":`Beri akses ${x.name}`,label:x.name,checked:f.includes(x.id),onChange:O=>g(K=>m(K,x.id,O))},x.id))})}),u&&e.jsx("div",{style:{color:"var(--status-err)",fontSize:"var(--text-sm)",marginBottom:10},children:u}),e.jsx(y,{size:"sm",leftIcon:"user-plus",onClick:R,disabled:!t||d.length<8,children:"Buat akun"})]}),e.jsx(C,{eyebrow:"akses",title:"Akun klien",children:a===null?e.jsx(V,{kind:"loading",title:"Memuat…"}):a.length===0?e.jsx(V,{kind:"empty",icon:"users",title:"Belum ada akun klien",hint:"Buat akun di atas, lalu pilih project yang boleh ia lihat."}):e.jsx("div",{style:{display:"flex",flexDirection:"column",gap:4},children:a.map(x=>e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:12,flexWrap:"wrap",padding:"12px 0",borderBottom:"1px solid var(--border-hair)"},children:[e.jsx("span",{style:{fontWeight:500,color:"var(--text-strong)"},children:x.email}),e.jsx(Ua,{status:x.disabled?"stopped":"done",size:"sm",children:x.disabled?"nonaktif":"aktif"}),e.jsx("div",{style:{flex:1,minWidth:240,display:"flex",flexWrap:"wrap",gap:14},children:i.map(O=>e.jsx(me,{"aria-label":`${x.email} · ${O.name}`,label:O.name,checked:x.projects.includes(O.id),onChange:K=>void F(x.id,{projects:m(x.projects,O.id,K)})},O.id))}),e.jsx(y,{size:"sm",variant:"ghost",onClick:()=>void F(x.id,{disabled:!x.disabled}),children:x.disabled?"Aktifkan":"Nonaktifkan"}),e.jsx(y,{size:"sm",variant:"ghost",onClick:()=>void A(x.id),children:"Hapus"})]},x.id))})})]})}const Te=["sessions:read","backlog:read","ide:read"],ra=[{cap:"sessions:write",label:"Tulis terminal",desc:"Ketik ke terminal, steer, interrupt, jawab & ambil alih dialog."},{cap:"sessions:spawn",label:"Mulai sesi",desc:"Membuka sesi agen BARU di mesin ini atas perintah hub — eksekusi agen di mesin ini. Hub tak bisa memaksa melewati gerbang beban."},{cap:"backlog:write",label:"Tandai selesai",desc:"Hanya menandai backlog selesai; suntingan backlog lain tetap tertutup untuk hub."}],da=[{lane:"event",label:"event",desc:"Lahir/tutup sesi, perubahan grant — lajur audit lokal (default nyala)."},{lane:"server",label:"server",desc:"console.* proses server ini, disadap & diredaksi sebelum dikirim."},{lane:"transcript",label:"transcript",desc:"Transkrip sesi terminal yang sudah ditutup."}],zt={off:"mati",connecting:"menyambung",open:"tersambung",backoff:"mencoba lagi",unsupported:"hub tak mendukung",rejected:"ditolak hub"},Ot={off:"neutral",connecting:"neutral",open:"ok",backoff:"warn",unsupported:"warn",rejected:"warn"};function De({title:a,desc:n,children:i,last:r}){return e.jsxs("div",{className:"hn-setting-row",style:{display:"flex",alignItems:"center",gap:16,padding:"14px 0",borderBottom:r?"none":"1px solid var(--border-hair)"},children:[e.jsxs("div",{style:{flex:1,minWidth:0},children:[e.jsx("div",{style:{fontSize:14,fontWeight:600,color:"var(--text-strong)"},children:a}),e.jsx("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginTop:3,lineHeight:1.5},children:n})]}),e.jsx("div",{className:"hn-setting-control",style:{flex:"0 0 auto"},children:i})]})}function It({onToast:a}){const[n,i]=o.useState(null),[r,t]=o.useState(!1),[c,d]=o.useState(!1);if(o.useEffect(()=>{j.getRemoteControl().then(i).catch(()=>t(!0))},[]),!n)return e.jsx(C,{eyebrow:"kendali",title:"Kendali jarak jauh dari hub",children:r?e.jsx("div",{style:{fontSize:13,color:"var(--text-muted)"},children:"Gagal memuat grant kendali jarak jauh."}):e.jsx(V,{kind:"loading",compact:!0,title:"Memuat grant…"})});const{enabled:h,capabilities:f}=n.control,g=m=>f.includes(m),u=(...m)=>[...new Set(m.flat())],k=async(m,x)=>{d(!0);try{i(await j.putRemoteControl({control:{enabled:m,capabilities:x}})),a==null||a(m?"Grant kendali jarak jauh disimpan":"Kendali jarak jauh dimatikan","ok","check")}catch{a==null||a("Gagal menyimpan grant kendali jarak jauh","err","x-circle")}finally{d(!1)}},z=m=>k(m,m&&f.length===0?Te:f),R=m=>k(h,m?u(f,Te):[]),F=(m,x)=>k(h,x?u(f,Te,[m]):f.filter(O=>O!==m)),A=async(m,x)=>{d(!0);try{i(await j.putRemoteControl({logs:{...n.logs,[m]:x}})),a==null||a(x?`Lajur ${m} mulai dikirim ke hub`:`Lajur ${m} berhenti dikirim`,"ok","check")}catch{a==null||a("Gagal menyimpan lajur log","err","x-circle")}finally{d(!1)}};return e.jsxs(e.Fragment,{children:[e.jsxs(C,{eyebrow:"kendali",title:"Kendali jarak jauh dari hub",children:[e.jsxs("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginBottom:12,lineHeight:1.5},children:["Mengizinkan operator ",e.jsx("b",{children:"hub"})," tempat mesin ini tersinkron bertindak di mesin ini lewat route yang sama dengan dashboard lokal. Default mati dan hanya bisa dinyalakan dari mesin ini — hub tak bisa menyalakannya sendiri."]}),e.jsx(De,{title:"Izinkan kendali jarak jauh",desc:"Membuka socket relay ke hub. Mati = tak ada jalur perintah dari hub sama sekali.",children:e.jsx(N,{checked:h,disabled:c,onChange:m=>void z(m),"aria-label":"Izinkan kendali jarak jauh"})}),e.jsx(De,{title:"Lihat",desc:"Daftar sesi, fase, dialog, dokumen & review sesi, IDE baca-saja.",children:e.jsx(N,{checked:g("sessions:read"),disabled:c||!h,onChange:m=>void R(m),"aria-label":"Lihat"})}),ra.map((m,x)=>e.jsx(De,{title:m.label,desc:m.desc,last:x===ra.length-1,children:e.jsx(N,{checked:g(m.cap),disabled:c||!h,onChange:O=>void F(m.cap,O),"aria-label":m.label})},m.cap))]}),e.jsxs(C,{eyebrow:"kendali",title:"Lajur log ke hub",children:[e.jsxs("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginBottom:12,lineHeight:1.5},children:["Mengirim baris dari mesin ini ke ",e.jsx("code",{children:"POST /sync/logs"})," di hub — lepas dari grant kendali jarak jauh di atas. Tiap lajur bisa dinyalakan/dimatikan sendiri-sendiri, langsung berlaku tanpa restart."]}),da.map((m,x)=>e.jsx(De,{title:m.label,desc:m.desc,last:x===da.length-1,children:e.jsx(N,{checked:n.logs[m.lane],disabled:c,onChange:O=>void A(m.lane,O),"aria-label":m.label})},m.lane))]}),e.jsx(C,{eyebrow:"kendali",title:"Status relay",children:e.jsxs("div",{"data-testid":"relay-status",style:{display:"flex",gap:10,alignItems:"center",flexWrap:"wrap",fontSize:13},children:[e.jsx(ne,{tone:Ot[n.relay.state],children:zt[n.relay.state]}),e.jsx("span",{style:{color:"var(--text-muted)",overflowWrap:"anywhere"},children:n.relay.hubOrigin??"mesin ini tak tersambung ke hub"}),n.relay.lastClose&&e.jsxs("span",{style:{color:"var(--text-muted)"},children:["tutup terakhir ",n.relay.lastClose.code,n.relay.lastClose.reason?` · ${n.relay.lastClose.reason}`:""]})]})}),e.jsx(C,{eyebrow:"kendali",title:"Audit aksi hub di mesin ini",children:n.audit.length===0?e.jsx("div",{style:{fontSize:13,color:"var(--text-subtle)",padding:"8px 0"},children:"Belum ada aksi dari hub."}):n.audit.map(m=>e.jsxs("div",{"data-testid":"remote-audit-row",style:{fontSize:12.5,padding:"8px 0",borderBottom:"1px solid var(--border-hair)",overflowWrap:"anywhere"},children:[e.jsx("b",{children:m.kind})," · ",m.msg," · ",e.jsx("span",{style:{color:"var(--text-muted)"},children:new Date(m.ts).toLocaleString("id-ID")})]},m.id))})]})}const Pt={name:"",url:"",events:["*"],enabled:!0,allowPrivate:!1},Bt=ka.map(a=>({entity:a.entity,label:a.label,types:Ne.filter(n=>n.entity===a.entity).map(n=>n.type)})),oa=a=>a?new Date(a).toLocaleString("id-ID",{dateStyle:"short",timeStyle:"medium"}):"—",ce={fontSize:12.5,color:"var(--text-muted)",lineHeight:1.5},Ke={fontFamily:"var(--font-mono)",fontSize:12};function Mt({e:a}){return a.enabled?e.jsx(ne,{tone:"ok",children:"aktif"}):a.disabledAt?e.jsx(ne,{tone:"err",children:"dinonaktifkan otomatis"}):e.jsx(ne,{children:"nonaktif"})}function Lt({onToast:a,onOpenDocs:n}={}){const[i,r]=o.useState(null),[t,c]=o.useState(!1),[d,h]=o.useState(null),[f,g]=o.useState(null),[u,k]=o.useState(!1),[z,R]=o.useState({}),[F,A]=o.useState(null),[m,x]=o.useState([]),[O,K]=o.useState(null),U=o.useCallback(async()=>{try{r((await j.listWebhooks()).endpoints),c(!1)}catch{c(!0)}},[]);o.useEffect(()=>{U()},[U]);const ge=async s=>{A(s);try{x((await j.listWebhookDeliveries(s)).items)}catch{x([])}},pe=async()=>{if(d){k(!0);try{if(d.id)await j.updateWebhook(d.id,{name:d.name,url:d.url,events:d.events,enabled:d.enabled,allowPrivate:d.allowPrivate}),a==null||a("Endpoint webhook diperbarui");else{const s=await j.createWebhook({name:d.name,url:d.url,events:d.events,enabled:d.enabled,allowPrivate:d.allowPrivate});g(s.secret??null),a==null||a("Endpoint webhook dibuat")}h(null),await U()}catch(s){a==null||a(`Gagal menyimpan: ${s.message}`,"err")}finally{k(!1)}}},ie=async s=>{k(!0);try{const w=await j.updateWebhook(s,{rotateSecret:!0});g(w.secret??null),await U()}catch(w){a==null||a(`Gagal merotasi secret: ${w.message}`,"err")}finally{k(!1)}},we=async(s,w)=>{try{await j.updateWebhook(s.id,{enabled:w}),await U()}catch(L){a==null||a(`Gagal mengubah status: ${L.message}`,"err")}},le=async s=>{k(!0);try{const w=await j.testWebhook(s);R(L=>({...L,[s]:w})),await U()}catch(w){R(L=>({...L,[s]:{ok:!1,httpStatus:null,durationMs:0,error:w.message}}))}finally{k(!1)}},ae=async()=>{if(O)try{await j.deleteWebhook(O.id),a==null||a("Endpoint webhook dihapus"),await U()}catch(s){a==null||a(`Gagal menghapus: ${s.message}`,"err")}finally{K(null)}},se=async s=>{try{await j.retryWebhookDelivery(s),F&&await ge(F),await U()}catch(w){a==null||a(`Gagal antre ulang: ${w.message}`,"err")}},b=(s,w)=>h(L=>L&&{...L,events:w?[...new Set([...L.events.filter(_=>_!=="*"),...s])]:L.events.filter(_=>_!=="*"&&!s.includes(_))}),B=(d==null?void 0:d.events.includes("*"))??!1;return e.jsxs(e.Fragment,{children:[e.jsxs(C,{eyebrow:"integrasi",title:"Webhook keluar",children:[e.jsxs("div",{style:{...ce,marginBottom:12},children:["hanoman mengirim ",e.jsx("b",{children:"HTTP POST"})," bertanda tangan ke endpoint yang Anda daftarkan setiap kali sesuatu berubah — backlog dibuat atau berpindah stage, sesi mulai dan selesai, putusan lead terbit, tiket masuk, notifikasi. Pengiriman berjalan di antrean: penerima yang lambat atau mati ",e.jsx("b",{children:"tidak"})," memperlambat hanoman."]}),e.jsxs("div",{style:{display:"flex",gap:8,marginBottom:16},children:[e.jsxs(y,{size:"sm",onClick:()=>{h({...Pt}),g(null)},children:[e.jsx(Fe,{name:"plus",size:14})," Tambah endpoint"]}),e.jsxs(y,{size:"sm",variant:"ghost",onClick:n,children:[e.jsx(Fe,{name:"book-open",size:14})," Dokumentasi webhook"]})]}),t?e.jsx(V,{kind:"error",compact:!0,title:"Gagal memuat endpoint webhook",hint:"Coba muat ulang.",action:()=>void U(),actionLabel:"Coba lagi"}):i===null?e.jsx("div",{style:ce,children:"Memuat…"}):i.length===0?e.jsx(V,{kind:"empty",compact:!0,title:"Belum ada endpoint webhook",hint:"Daftarkan URL penerima pertama untuk mulai berlangganan peristiwa hanoman."}):e.jsx("div",{style:{display:"flex",flexDirection:"column",gap:10},children:i.map(s=>e.jsxs("div",{"data-testid":`webhook-${s.id}`,style:{border:"1px solid var(--border-hair)",borderRadius:"var(--radius-sm)",padding:12},children:[e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:10,flexWrap:"wrap"},children:[e.jsx("div",{style:{fontWeight:600},children:s.name}),e.jsx(Mt,{e:s}),s.pending>0?e.jsxs(ne,{children:[s.pending," mengantre"]}):null,e.jsxs("div",{style:{marginLeft:"auto",display:"flex",gap:6,alignItems:"center"},children:[e.jsx(N,{checked:s.enabled,"aria-label":`Aktifkan ${s.name}`,onChange:w=>void we(s,w)}),e.jsx(y,{size:"sm",variant:"ghost",disabled:u,onClick:()=>void le(s.id),children:"Test"}),e.jsx(y,{size:"sm",variant:"ghost",onClick:()=>void ge(s.id),children:"Riwayat"}),e.jsx(y,{size:"sm",variant:"ghost",onClick:()=>{g(null),h({id:s.id,name:s.name,url:s.url,events:s.events,enabled:s.enabled,allowPrivate:s.allowPrivate})},children:"Ubah"}),e.jsx(y,{size:"sm",variant:"ghost",onClick:()=>K(s),children:"Hapus"})]})]}),e.jsx("div",{style:{...Ke,color:"var(--text-muted)",marginTop:6},children:s.url}),e.jsxs("div",{style:{...ce,marginTop:6},children:["Peristiwa: ",e.jsx("code",{children:s.events.join(", ")})," · secret …",s.secretHint," ·"," ","sukses terakhir ",oa(s.lastSuccessAt),s.failureStreak>0?` · ${s.failureStreak} gagal beruntun`:""]}),s.disabledReason?e.jsx("div",{style:{...ce,marginTop:6,color:"var(--status-err-tint)"},children:s.disabledReason}):null,z[s.id]?e.jsx("div",{style:{...ce,marginTop:6},children:z[s.id].ok?`Test berhasil — HTTP ${z[s.id].httpStatus} · ${z[s.id].durationMs} ms`:`Test gagal — ${z[s.id].error}`}):null]},s.id))})]}),f?e.jsxs(C,{eyebrow:"secret",title:"Salin sekarang — secret tak ditampilkan lagi",children:[e.jsx(ue,{tone:"brass",children:e.jsx("div",{style:{...Ke,wordBreak:"break-all"},children:f})}),e.jsxs("div",{style:{display:"flex",gap:8,marginTop:10},children:[e.jsx(y,{size:"sm",onClick:()=>{var s;return void((s=navigator.clipboard)==null?void 0:s.writeText(f))},children:"Salin secret"}),e.jsx(y,{size:"sm",variant:"ghost",onClick:()=>g(null),children:"Tutup"})]})]}):null,d?e.jsx(C,{eyebrow:d.id?"ubah":"tambah",title:d.id?"Ubah endpoint":"Endpoint baru",children:e.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:12},children:[e.jsx(ee,{label:"Nama",children:e.jsx(Y,{value:d.name,placeholder:"mis. Dashboard internal",onChange:s=>h({...d,name:s.target.value})})}),e.jsx(ee,{label:"URL tujuan",children:e.jsx(Y,{value:d.url,placeholder:"https://contoh.id/hanoman-webhook",onChange:s=>h({...d,url:s.target.value})})}),e.jsxs("div",{children:[e.jsx("div",{style:{fontWeight:600,fontSize:13,marginBottom:6},children:"Peristiwa yang dikirim"}),e.jsx(me,{checked:B,label:"Semua peristiwa",onChange:s=>h({...d,events:s?["*"]:[]})}),B?null:e.jsxs("div",{style:{marginTop:8,display:"flex",flexDirection:"column",gap:8},children:[Bt.map(s=>e.jsxs("div",{children:[e.jsx(me,{checked:s.types.every(w=>d.events.includes(w)),label:s.label,onChange:w=>b(s.types,w)}),e.jsx("div",{style:{paddingLeft:22,display:"flex",flexWrap:"wrap",gap:10},children:s.types.map(w=>e.jsx(me,{checked:d.events.includes(w),label:w,onChange:L=>h({...d,events:L?[...new Set([...d.events.filter(_=>_!=="*"),w])]:d.events.filter(_=>_!==w&&_!=="*")})},w))})]},s.entity)),e.jsxs("div",{style:ce,children:[e.jsx("code",{children:Ta})," selalu terkirim saat tombol Test ditekan, apa pun langganannya."]})]})]}),e.jsx(me,{checked:d.allowPrivate,label:"Izinkan alamat internal / loopback",onChange:s=>h({...d,allowPrivate:s})}),e.jsx("div",{style:ce,children:"Biarkan mati kecuali penerima memang berjalan di mesin/jaringan yang sama. Menyalakannya membuka jalan ke layanan internal yang tak pernah dimaksudkan terekspos."}),e.jsx(me,{checked:d.enabled,label:"Aktif",onChange:s=>h({...d,enabled:s})}),e.jsxs("div",{style:{display:"flex",gap:8},children:[e.jsx(y,{size:"sm",disabled:u||!d.name||!d.url||d.events.length===0,onClick:()=>void pe(),children:"Simpan"}),e.jsx(y,{size:"sm",variant:"ghost",onClick:()=>h(null),children:"Batal"}),d.id?e.jsx(y,{size:"sm",variant:"ghost",disabled:u,onClick:()=>void ie(d.id),children:"Rotasi secret"}):null]})]})}):null,F?e.jsxs(C,{eyebrow:"riwayat",title:"Riwayat pengiriman",children:[e.jsx("div",{style:{display:"flex",justifyContent:"flex-end",marginBottom:8},children:e.jsx(y,{size:"sm",variant:"ghost",onClick:()=>A(null),children:"Tutup riwayat"})}),m.length===0?e.jsx("div",{style:ce,children:"Belum ada pengiriman."}):e.jsx("div",{className:"hn-local-overflow",style:{overflowX:"auto"},children:e.jsxs("table",{style:{width:"100%",borderCollapse:"collapse",fontSize:12.5},children:[e.jsx("thead",{children:e.jsxs("tr",{style:{textAlign:"left",color:"var(--text-muted)"},children:[e.jsx("th",{style:{padding:"6px 8px"},children:"Waktu"}),e.jsx("th",{style:{padding:"6px 8px"},children:"Jenis"}),e.jsx("th",{style:{padding:"6px 8px"},children:"Status"}),e.jsx("th",{style:{padding:"6px 8px"},children:"HTTP"}),e.jsx("th",{style:{padding:"6px 8px"},children:"Percobaan"}),e.jsx("th",{style:{padding:"6px 8px"},children:"Galat"}),e.jsx("th",{})]})}),e.jsx("tbody",{children:m.map(s=>e.jsxs("tr",{style:{borderTop:"1px solid var(--border-hair)"},children:[e.jsx("td",{style:{padding:"6px 8px",whiteSpace:"nowrap"},children:oa(s.createdAt)}),e.jsx("td",{style:{padding:"6px 8px",...Ke},children:s.eventType}),e.jsx("td",{style:{padding:"6px 8px"},children:s.status}),e.jsx("td",{style:{padding:"6px 8px"},children:s.httpStatus??"—"}),e.jsx("td",{style:{padding:"6px 8px"},children:`${s.attempt} / ${s.maxAttempts}`}),e.jsx("td",{style:{padding:"6px 8px",color:"var(--text-muted)"},children:s.error??"—"}),e.jsx("td",{style:{padding:"6px 8px"},children:s.status==="failed"||s.status==="dropped"?e.jsx(y,{size:"sm",variant:"ghost",onClick:()=>void se(s.id),children:"Antre ulang"}):null})]},s.id))})]})})]}):null,e.jsx(ba,{open:!!O,title:"Hapus endpoint webhook?",message:O?`"${O.name}" beserta seluruh riwayat pengirimannya akan dihapus.`:"",confirmLabel:"Hapus",onCancel:()=>K(null),onConfirm:()=>void ae()})]})}const J={fontSize:13,color:"var(--text-muted)",lineHeight:1.6},Dt={fontFamily:"var(--font-mono)",fontSize:12,whiteSpace:"pre",overflowX:"auto",background:"var(--bone-200)",border:"1px solid var(--border-hair)",borderRadius:"var(--radius-sm)",padding:12,margin:0};function Je({text:a,label:n="Salin"}){const[i,r]=o.useState(!1);return e.jsxs("div",{style:{position:"relative"},children:[e.jsx("pre",{style:Dt,children:a}),e.jsx("div",{style:{marginTop:6},children:e.jsx(y,{size:"sm",variant:"ghost",onClick:()=>{var t;(t=navigator.clipboard)==null||t.writeText(a),r(!0),setTimeout(()=>r(!1),1500)},children:i?"Tersalin":n})})]})}const Ht=`// Node.js (Express) — verifikasi tanda tangan webhook hanoman
|
|
1
|
+
import{aR as za,aS as Oa,aT as Ia,aU as Pa,aV as Ba,aW as Ma,aX as La,aY as Da,aZ as Ha,a_ as _a,a$ as Na,b0 as Ra,b1 as Fa,b2 as Wa,b3 as $a,b4 as Ga,b5 as Ua,e as j,j as e,as as C,c as ee,d as Y,at as me,a as y,f as V,ao as Ta,aK as N,B as ne,I as Fe,N as ue,b6 as Ka,C as ba,b7 as ka,b8 as Ne,b9 as xa,ba as Ja,bb as Oe,bc as Qe,bd as Va,be as na,bf as Ya,bg as Xa,bh as qa,A as Qa,bi as Za,bj as et,S as M,bk as at,bl as Ye,bm as tt,V as nt,Y as te,Z as fa,u as $e,bn as it,bo as lt,h as st,a8 as rt,bp as dt,p as ot,bq as ct,O as ja,br as Se,bs as We,bt as ut,bu as gt,bv as ht,bw as mt,bx as ya,by as va,bz as Re,bA as Sa,bB as pt,bC as bt,aN as kt,bD as Ee,bE as ia,H as xt,bF as la,bG as sa,bH as ft,a0 as Xe,bI as wa,bJ as jt,W as yt,X as vt,bK as St,bL as wt}from"./index-DYNYB1Nj.js";import{R as o}from"./react-DfGhg7ZW.js";import"./markdown-CWEY34jk.js";import{D as Ct}from"./DocPreviewModal-C_vQOOHV.js";import"./xterm-3VOAfa_q.js";const Et=[...za,...Oa,...Ia,...Pa,...Ba,...Ma,...La,...Da,...Ha,..._a,...Na,...Ra,...Fa,...Wa,...$a,...Ga,...Ua],At=1;function zt(){const[a,n]=o.useState(null),[i,r]=o.useState([]),[t,c]=o.useState(""),[d,h]=o.useState(""),[f,g]=o.useState([]),[u,k]=o.useState(null),z=o.useCallback(()=>j.listClientAccounts().then(x=>n(x.items)).catch(()=>n([])),[]);o.useEffect(()=>{z(),j.listProjects().then(x=>r(x.items.map(O=>({id:O.id,name:O.name})))).catch(()=>r([]))},[z]);const R=async()=>{k(null);try{await j.createClientAccount({email:t,password:d,projects:f}),c(""),h(""),g([]),await z()}catch{k("Gagal membuat akun — periksa email (mungkin sudah dipakai) dan panjang password.")}},F=async(x,O)=>{await j.updateClientAccount(x,O),await z()},A=async x=>{await j.deleteClientAccount(x),await z()},m=(x,O,K)=>K?[...x,O]:x.filter(U=>U!==O);return e.jsxs(e.Fragment,{children:[e.jsxs(C,{eyebrow:"akses",title:"Buat akun klien",children:[e.jsxs("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginBottom:14,lineHeight:1.5},children:["Akun klien melihat ",e.jsx("b",{children:"daftar backlog"})," dan ",e.jsx("b",{children:"tiket help desk"})," project yang dipilih — baca-saja, tanpa terminal, dokumen internal, maupun setelan. Ia tak pernah melihat dashboard operator."]}),e.jsx(ee,{label:"Email klien",children:e.jsx(Y,{"aria-label":"Email klien",placeholder:"mis. budi@tokomekar.co.id",value:t,onChange:x=>c(x.target.value)})}),e.jsx(ee,{label:"Password awal",hint:"Minimal 8 karakter. Sampaikan lewat kanal yang aman — klien bisa menggantinya sendiri.",children:e.jsx(Y,{"aria-label":"Password awal",type:"password",placeholder:"mis. kunci-tokomekar-2026",value:d,onChange:x=>h(x.target.value)})}),e.jsx(ee,{label:"Project yang boleh dilihat",children:e.jsx("div",{style:{display:"flex",flexWrap:"wrap",gap:14},children:i.map(x=>e.jsx(me,{"aria-label":`Beri akses ${x.name}`,label:x.name,checked:f.includes(x.id),onChange:O=>g(K=>m(K,x.id,O))},x.id))})}),u&&e.jsx("div",{style:{color:"var(--status-err)",fontSize:"var(--text-sm)",marginBottom:10},children:u}),e.jsx(y,{size:"sm",leftIcon:"user-plus",onClick:R,disabled:!t||d.length<8,children:"Buat akun"})]}),e.jsx(C,{eyebrow:"akses",title:"Akun klien",children:a===null?e.jsx(V,{kind:"loading",title:"Memuat…"}):a.length===0?e.jsx(V,{kind:"empty",icon:"users",title:"Belum ada akun klien",hint:"Buat akun di atas, lalu pilih project yang boleh ia lihat."}):e.jsx("div",{style:{display:"flex",flexDirection:"column",gap:4},children:a.map(x=>e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:12,flexWrap:"wrap",padding:"12px 0",borderBottom:"1px solid var(--border-hair)"},children:[e.jsx("span",{style:{fontWeight:500,color:"var(--text-strong)"},children:x.email}),e.jsx(Ta,{status:x.disabled?"stopped":"done",size:"sm",children:x.disabled?"nonaktif":"aktif"}),e.jsx("div",{style:{flex:1,minWidth:240,display:"flex",flexWrap:"wrap",gap:14},children:i.map(O=>e.jsx(me,{"aria-label":`${x.email} · ${O.name}`,label:O.name,checked:x.projects.includes(O.id),onChange:K=>void F(x.id,{projects:m(x.projects,O.id,K)})},O.id))}),e.jsx(y,{size:"sm",variant:"ghost",onClick:()=>void F(x.id,{disabled:!x.disabled}),children:x.disabled?"Aktifkan":"Nonaktifkan"}),e.jsx(y,{size:"sm",variant:"ghost",onClick:()=>void A(x.id),children:"Hapus"})]},x.id))})})]})}const Te=["sessions:read","backlog:read","ide:read"],ra=[{cap:"sessions:write",label:"Tulis terminal",desc:"Ketik ke terminal, steer, interrupt, jawab & ambil alih dialog."},{cap:"sessions:spawn",label:"Mulai sesi",desc:"Membuka sesi agen BARU di mesin ini atas perintah hub — eksekusi agen di mesin ini. Hub tak bisa memaksa melewati gerbang beban."},{cap:"backlog:write",label:"Tandai selesai",desc:"Hanya menandai backlog selesai; suntingan backlog lain tetap tertutup untuk hub."}],da=[{lane:"event",label:"event",desc:"Lahir/tutup sesi, perubahan grant — lajur audit lokal (default nyala)."},{lane:"server",label:"server",desc:"console.* proses server ini, disadap & diredaksi sebelum dikirim."},{lane:"transcript",label:"transcript",desc:"Transkrip sesi terminal yang sudah ditutup."}],Ot={off:"mati",connecting:"menyambung",open:"tersambung",backoff:"mencoba lagi",unsupported:"hub tak mendukung",rejected:"ditolak hub"},It={off:"neutral",connecting:"neutral",open:"ok",backoff:"warn",unsupported:"warn",rejected:"warn"};function De({title:a,desc:n,children:i,last:r}){return e.jsxs("div",{className:"hn-setting-row",style:{display:"flex",alignItems:"center",gap:16,padding:"14px 0",borderBottom:r?"none":"1px solid var(--border-hair)"},children:[e.jsxs("div",{style:{flex:1,minWidth:0},children:[e.jsx("div",{style:{fontSize:14,fontWeight:600,color:"var(--text-strong)"},children:a}),e.jsx("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginTop:3,lineHeight:1.5},children:n})]}),e.jsx("div",{className:"hn-setting-control",style:{flex:"0 0 auto"},children:i})]})}function Pt({onToast:a}){const[n,i]=o.useState(null),[r,t]=o.useState(!1),[c,d]=o.useState(!1);if(o.useEffect(()=>{j.getRemoteControl().then(i).catch(()=>t(!0))},[]),!n)return e.jsx(C,{eyebrow:"kendali",title:"Kendali jarak jauh dari hub",children:r?e.jsx("div",{style:{fontSize:13,color:"var(--text-muted)"},children:"Gagal memuat grant kendali jarak jauh."}):e.jsx(V,{kind:"loading",compact:!0,title:"Memuat grant…"})});const{enabled:h,capabilities:f}=n.control,g=m=>f.includes(m),u=(...m)=>[...new Set(m.flat())],k=async(m,x)=>{d(!0);try{i(await j.putRemoteControl({control:{enabled:m,capabilities:x}})),a==null||a(m?"Grant kendali jarak jauh disimpan":"Kendali jarak jauh dimatikan","ok","check")}catch{a==null||a("Gagal menyimpan grant kendali jarak jauh","err","x-circle")}finally{d(!1)}},z=m=>k(m,m&&f.length===0?Te:f),R=m=>k(h,m?u(f,Te):[]),F=(m,x)=>k(h,x?u(f,Te,[m]):f.filter(O=>O!==m)),A=async(m,x)=>{d(!0);try{i(await j.putRemoteControl({logs:{...n.logs,[m]:x}})),a==null||a(x?`Lajur ${m} mulai dikirim ke hub`:`Lajur ${m} berhenti dikirim`,"ok","check")}catch{a==null||a("Gagal menyimpan lajur log","err","x-circle")}finally{d(!1)}};return e.jsxs(e.Fragment,{children:[e.jsxs(C,{eyebrow:"kendali",title:"Kendali jarak jauh dari hub",children:[e.jsxs("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginBottom:12,lineHeight:1.5},children:["Mengizinkan operator ",e.jsx("b",{children:"hub"})," tempat mesin ini tersinkron bertindak di mesin ini lewat route yang sama dengan dashboard lokal. Default mati dan hanya bisa dinyalakan dari mesin ini — hub tak bisa menyalakannya sendiri."]}),e.jsx(De,{title:"Izinkan kendali jarak jauh",desc:"Membuka socket relay ke hub. Mati = tak ada jalur perintah dari hub sama sekali.",children:e.jsx(N,{checked:h,disabled:c,onChange:m=>void z(m),"aria-label":"Izinkan kendali jarak jauh"})}),e.jsx(De,{title:"Lihat",desc:"Daftar sesi, fase, dialog, dokumen & review sesi, IDE baca-saja.",children:e.jsx(N,{checked:g("sessions:read"),disabled:c||!h,onChange:m=>void R(m),"aria-label":"Lihat"})}),ra.map((m,x)=>e.jsx(De,{title:m.label,desc:m.desc,last:x===ra.length-1,children:e.jsx(N,{checked:g(m.cap),disabled:c||!h,onChange:O=>void F(m.cap,O),"aria-label":m.label})},m.cap))]}),e.jsxs(C,{eyebrow:"kendali",title:"Lajur log ke hub",children:[e.jsxs("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginBottom:12,lineHeight:1.5},children:["Mengirim baris dari mesin ini ke ",e.jsx("code",{children:"POST /sync/logs"})," di hub — lepas dari grant kendali jarak jauh di atas. Tiap lajur bisa dinyalakan/dimatikan sendiri-sendiri, langsung berlaku tanpa restart."]}),da.map((m,x)=>e.jsx(De,{title:m.label,desc:m.desc,last:x===da.length-1,children:e.jsx(N,{checked:n.logs[m.lane],disabled:c,onChange:O=>void A(m.lane,O),"aria-label":m.label})},m.lane))]}),e.jsx(C,{eyebrow:"kendali",title:"Status relay",children:e.jsxs("div",{"data-testid":"relay-status",style:{display:"flex",gap:10,alignItems:"center",flexWrap:"wrap",fontSize:13},children:[e.jsx(ne,{tone:It[n.relay.state],children:Ot[n.relay.state]}),e.jsx("span",{style:{color:"var(--text-muted)",overflowWrap:"anywhere"},children:n.relay.hubOrigin??"mesin ini tak tersambung ke hub"}),n.relay.lastClose&&e.jsxs("span",{style:{color:"var(--text-muted)"},children:["tutup terakhir ",n.relay.lastClose.code,n.relay.lastClose.reason?` · ${n.relay.lastClose.reason}`:""]})]})}),e.jsx(C,{eyebrow:"kendali",title:"Audit aksi hub di mesin ini",children:n.audit.length===0?e.jsx("div",{style:{fontSize:13,color:"var(--text-subtle)",padding:"8px 0"},children:"Belum ada aksi dari hub."}):n.audit.map(m=>e.jsxs("div",{"data-testid":"remote-audit-row",style:{fontSize:12.5,padding:"8px 0",borderBottom:"1px solid var(--border-hair)",overflowWrap:"anywhere"},children:[e.jsx("b",{children:m.kind})," · ",m.msg," · ",e.jsx("span",{style:{color:"var(--text-muted)"},children:new Date(m.ts).toLocaleString("id-ID")})]},m.id))})]})}const Bt={name:"",url:"",events:["*"],enabled:!0,allowPrivate:!1},Mt=ka.map(a=>({entity:a.entity,label:a.label,types:Ne.filter(n=>n.entity===a.entity).map(n=>n.type)})),oa=a=>a?new Date(a).toLocaleString("id-ID",{dateStyle:"short",timeStyle:"medium"}):"—",ce={fontSize:12.5,color:"var(--text-muted)",lineHeight:1.5},Ke={fontFamily:"var(--font-mono)",fontSize:12};function Lt({e:a}){return a.enabled?e.jsx(ne,{tone:"ok",children:"aktif"}):a.disabledAt?e.jsx(ne,{tone:"err",children:"dinonaktifkan otomatis"}):e.jsx(ne,{children:"nonaktif"})}function Dt({onToast:a,onOpenDocs:n}={}){const[i,r]=o.useState(null),[t,c]=o.useState(!1),[d,h]=o.useState(null),[f,g]=o.useState(null),[u,k]=o.useState(!1),[z,R]=o.useState({}),[F,A]=o.useState(null),[m,x]=o.useState([]),[O,K]=o.useState(null),U=o.useCallback(async()=>{try{r((await j.listWebhooks()).endpoints),c(!1)}catch{c(!0)}},[]);o.useEffect(()=>{U()},[U]);const ge=async s=>{A(s);try{x((await j.listWebhookDeliveries(s)).items)}catch{x([])}},pe=async()=>{if(d){k(!0);try{if(d.id)await j.updateWebhook(d.id,{name:d.name,url:d.url,events:d.events,enabled:d.enabled,allowPrivate:d.allowPrivate}),a==null||a("Endpoint webhook diperbarui");else{const s=await j.createWebhook({name:d.name,url:d.url,events:d.events,enabled:d.enabled,allowPrivate:d.allowPrivate});g(s.secret??null),a==null||a("Endpoint webhook dibuat")}h(null),await U()}catch(s){a==null||a(`Gagal menyimpan: ${s.message}`,"err")}finally{k(!1)}}},ie=async s=>{k(!0);try{const w=await j.updateWebhook(s,{rotateSecret:!0});g(w.secret??null),await U()}catch(w){a==null||a(`Gagal merotasi secret: ${w.message}`,"err")}finally{k(!1)}},we=async(s,w)=>{try{await j.updateWebhook(s.id,{enabled:w}),await U()}catch(L){a==null||a(`Gagal mengubah status: ${L.message}`,"err")}},le=async s=>{k(!0);try{const w=await j.testWebhook(s);R(L=>({...L,[s]:w})),await U()}catch(w){R(L=>({...L,[s]:{ok:!1,httpStatus:null,durationMs:0,error:w.message}}))}finally{k(!1)}},ae=async()=>{if(O)try{await j.deleteWebhook(O.id),a==null||a("Endpoint webhook dihapus"),await U()}catch(s){a==null||a(`Gagal menghapus: ${s.message}`,"err")}finally{K(null)}},se=async s=>{try{await j.retryWebhookDelivery(s),F&&await ge(F),await U()}catch(w){a==null||a(`Gagal antre ulang: ${w.message}`,"err")}},b=(s,w)=>h(L=>L&&{...L,events:w?[...new Set([...L.events.filter(_=>_!=="*"),...s])]:L.events.filter(_=>_!=="*"&&!s.includes(_))}),B=(d==null?void 0:d.events.includes("*"))??!1;return e.jsxs(e.Fragment,{children:[e.jsxs(C,{eyebrow:"integrasi",title:"Webhook keluar",children:[e.jsxs("div",{style:{...ce,marginBottom:12},children:["hanoman mengirim ",e.jsx("b",{children:"HTTP POST"})," bertanda tangan ke endpoint yang Anda daftarkan setiap kali sesuatu berubah — backlog dibuat atau berpindah stage, sesi mulai dan selesai, putusan lead terbit, tiket masuk, notifikasi. Pengiriman berjalan di antrean: penerima yang lambat atau mati ",e.jsx("b",{children:"tidak"})," memperlambat hanoman."]}),e.jsxs("div",{style:{display:"flex",gap:8,marginBottom:16},children:[e.jsxs(y,{size:"sm",onClick:()=>{h({...Bt}),g(null)},children:[e.jsx(Fe,{name:"plus",size:14})," Tambah endpoint"]}),e.jsxs(y,{size:"sm",variant:"ghost",onClick:n,children:[e.jsx(Fe,{name:"book-open",size:14})," Dokumentasi webhook"]})]}),t?e.jsx(V,{kind:"error",compact:!0,title:"Gagal memuat endpoint webhook",hint:"Coba muat ulang.",action:()=>void U(),actionLabel:"Coba lagi"}):i===null?e.jsx("div",{style:ce,children:"Memuat…"}):i.length===0?e.jsx(V,{kind:"empty",compact:!0,title:"Belum ada endpoint webhook",hint:"Daftarkan URL penerima pertama untuk mulai berlangganan peristiwa hanoman."}):e.jsx("div",{style:{display:"flex",flexDirection:"column",gap:10},children:i.map(s=>e.jsxs("div",{"data-testid":`webhook-${s.id}`,style:{border:"1px solid var(--border-hair)",borderRadius:"var(--radius-sm)",padding:12},children:[e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:10,flexWrap:"wrap"},children:[e.jsx("div",{style:{fontWeight:600},children:s.name}),e.jsx(Lt,{e:s}),s.pending>0?e.jsxs(ne,{children:[s.pending," mengantre"]}):null,e.jsxs("div",{style:{marginLeft:"auto",display:"flex",gap:6,alignItems:"center"},children:[e.jsx(N,{checked:s.enabled,"aria-label":`Aktifkan ${s.name}`,onChange:w=>void we(s,w)}),e.jsx(y,{size:"sm",variant:"ghost",disabled:u,onClick:()=>void le(s.id),children:"Test"}),e.jsx(y,{size:"sm",variant:"ghost",onClick:()=>void ge(s.id),children:"Riwayat"}),e.jsx(y,{size:"sm",variant:"ghost",onClick:()=>{g(null),h({id:s.id,name:s.name,url:s.url,events:s.events,enabled:s.enabled,allowPrivate:s.allowPrivate})},children:"Ubah"}),e.jsx(y,{size:"sm",variant:"ghost",onClick:()=>K(s),children:"Hapus"})]})]}),e.jsx("div",{style:{...Ke,color:"var(--text-muted)",marginTop:6},children:s.url}),e.jsxs("div",{style:{...ce,marginTop:6},children:["Peristiwa: ",e.jsx("code",{children:s.events.join(", ")})," · secret …",s.secretHint," ·"," ","sukses terakhir ",oa(s.lastSuccessAt),s.failureStreak>0?` · ${s.failureStreak} gagal beruntun`:""]}),s.disabledReason?e.jsx("div",{style:{...ce,marginTop:6,color:"var(--status-err-tint)"},children:s.disabledReason}):null,z[s.id]?e.jsx("div",{style:{...ce,marginTop:6},children:z[s.id].ok?`Test berhasil — HTTP ${z[s.id].httpStatus} · ${z[s.id].durationMs} ms`:`Test gagal — ${z[s.id].error}`}):null]},s.id))})]}),f?e.jsxs(C,{eyebrow:"secret",title:"Salin sekarang — secret tak ditampilkan lagi",children:[e.jsx(ue,{tone:"brass",children:e.jsx("div",{style:{...Ke,wordBreak:"break-all"},children:f})}),e.jsxs("div",{style:{display:"flex",gap:8,marginTop:10},children:[e.jsx(y,{size:"sm",onClick:()=>{var s;return void((s=navigator.clipboard)==null?void 0:s.writeText(f))},children:"Salin secret"}),e.jsx(y,{size:"sm",variant:"ghost",onClick:()=>g(null),children:"Tutup"})]})]}):null,d?e.jsx(C,{eyebrow:d.id?"ubah":"tambah",title:d.id?"Ubah endpoint":"Endpoint baru",children:e.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:12},children:[e.jsx(ee,{label:"Nama",children:e.jsx(Y,{value:d.name,placeholder:"mis. Dashboard internal",onChange:s=>h({...d,name:s.target.value})})}),e.jsx(ee,{label:"URL tujuan",children:e.jsx(Y,{value:d.url,placeholder:"https://contoh.id/hanoman-webhook",onChange:s=>h({...d,url:s.target.value})})}),e.jsxs("div",{children:[e.jsx("div",{style:{fontWeight:600,fontSize:13,marginBottom:6},children:"Peristiwa yang dikirim"}),e.jsx(me,{checked:B,label:"Semua peristiwa",onChange:s=>h({...d,events:s?["*"]:[]})}),B?null:e.jsxs("div",{style:{marginTop:8,display:"flex",flexDirection:"column",gap:8},children:[Mt.map(s=>e.jsxs("div",{children:[e.jsx(me,{checked:s.types.every(w=>d.events.includes(w)),label:s.label,onChange:w=>b(s.types,w)}),e.jsx("div",{style:{paddingLeft:22,display:"flex",flexWrap:"wrap",gap:10},children:s.types.map(w=>e.jsx(me,{checked:d.events.includes(w),label:w,onChange:L=>h({...d,events:L?[...new Set([...d.events.filter(_=>_!=="*"),w])]:d.events.filter(_=>_!==w&&_!=="*")})},w))})]},s.entity)),e.jsxs("div",{style:ce,children:[e.jsx("code",{children:Ka})," selalu terkirim saat tombol Test ditekan, apa pun langganannya."]})]})]}),e.jsx(me,{checked:d.allowPrivate,label:"Izinkan alamat internal / loopback",onChange:s=>h({...d,allowPrivate:s})}),e.jsx("div",{style:ce,children:"Biarkan mati kecuali penerima memang berjalan di mesin/jaringan yang sama. Menyalakannya membuka jalan ke layanan internal yang tak pernah dimaksudkan terekspos."}),e.jsx(me,{checked:d.enabled,label:"Aktif",onChange:s=>h({...d,enabled:s})}),e.jsxs("div",{style:{display:"flex",gap:8},children:[e.jsx(y,{size:"sm",disabled:u||!d.name||!d.url||d.events.length===0,onClick:()=>void pe(),children:"Simpan"}),e.jsx(y,{size:"sm",variant:"ghost",onClick:()=>h(null),children:"Batal"}),d.id?e.jsx(y,{size:"sm",variant:"ghost",disabled:u,onClick:()=>void ie(d.id),children:"Rotasi secret"}):null]})]})}):null,F?e.jsxs(C,{eyebrow:"riwayat",title:"Riwayat pengiriman",children:[e.jsx("div",{style:{display:"flex",justifyContent:"flex-end",marginBottom:8},children:e.jsx(y,{size:"sm",variant:"ghost",onClick:()=>A(null),children:"Tutup riwayat"})}),m.length===0?e.jsx("div",{style:ce,children:"Belum ada pengiriman."}):e.jsx("div",{className:"hn-local-overflow",style:{overflowX:"auto"},children:e.jsxs("table",{style:{width:"100%",borderCollapse:"collapse",fontSize:12.5},children:[e.jsx("thead",{children:e.jsxs("tr",{style:{textAlign:"left",color:"var(--text-muted)"},children:[e.jsx("th",{style:{padding:"6px 8px"},children:"Waktu"}),e.jsx("th",{style:{padding:"6px 8px"},children:"Jenis"}),e.jsx("th",{style:{padding:"6px 8px"},children:"Status"}),e.jsx("th",{style:{padding:"6px 8px"},children:"HTTP"}),e.jsx("th",{style:{padding:"6px 8px"},children:"Percobaan"}),e.jsx("th",{style:{padding:"6px 8px"},children:"Galat"}),e.jsx("th",{})]})}),e.jsx("tbody",{children:m.map(s=>e.jsxs("tr",{style:{borderTop:"1px solid var(--border-hair)"},children:[e.jsx("td",{style:{padding:"6px 8px",whiteSpace:"nowrap"},children:oa(s.createdAt)}),e.jsx("td",{style:{padding:"6px 8px",...Ke},children:s.eventType}),e.jsx("td",{style:{padding:"6px 8px"},children:s.status}),e.jsx("td",{style:{padding:"6px 8px"},children:s.httpStatus??"—"}),e.jsx("td",{style:{padding:"6px 8px"},children:`${s.attempt} / ${s.maxAttempts}`}),e.jsx("td",{style:{padding:"6px 8px",color:"var(--text-muted)"},children:s.error??"—"}),e.jsx("td",{style:{padding:"6px 8px"},children:s.status==="failed"||s.status==="dropped"?e.jsx(y,{size:"sm",variant:"ghost",onClick:()=>void se(s.id),children:"Antre ulang"}):null})]},s.id))})]})})]}):null,e.jsx(ba,{open:!!O,title:"Hapus endpoint webhook?",message:O?`"${O.name}" beserta seluruh riwayat pengirimannya akan dihapus.`:"",confirmLabel:"Hapus",onCancel:()=>K(null),onConfirm:()=>void ae()})]})}const J={fontSize:13,color:"var(--text-muted)",lineHeight:1.6},Ht={fontFamily:"var(--font-mono)",fontSize:12,whiteSpace:"pre",overflowX:"auto",background:"var(--bone-200)",border:"1px solid var(--border-hair)",borderRadius:"var(--radius-sm)",padding:12,margin:0};function Je({text:a,label:n="Salin"}){const[i,r]=o.useState(!1);return e.jsxs("div",{style:{position:"relative"},children:[e.jsx("pre",{style:Ht,children:a}),e.jsx("div",{style:{marginTop:6},children:e.jsx(y,{size:"sm",variant:"ghost",onClick:()=>{var t;(t=navigator.clipboard)==null||t.writeText(a),r(!0),setTimeout(()=>r(!1),1500)},children:i?"Tersalin":n})})]})}const _t=`// Node.js (Express) — verifikasi tanda tangan webhook hanoman
|
|
2
2
|
import express from "express";
|
|
3
3
|
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
4
4
|
|
|
@@ -20,7 +20,7 @@ app.post("/hanoman", express.raw({ type: "application/json" }), (req, res) => {
|
|
|
20
20
|
if (alreadyHandled(event.id)) return res.sendStatus(200);
|
|
21
21
|
handle(event);
|
|
22
22
|
res.sendStatus(200); // balas 2xx dulu, kerjakan yang berat di latar
|
|
23
|
-
});`,
|
|
23
|
+
});`,Nt=`# Python (Flask) — verifikasi tanda tangan webhook hanoman
|
|
24
24
|
import hmac, hashlib, os, time
|
|
25
25
|
from flask import Flask, request, abort
|
|
26
26
|
|
|
@@ -42,7 +42,7 @@ def hanoman():
|
|
|
42
42
|
if already_handled(event["id"]): # retry membawa id yang sama
|
|
43
43
|
return "", 200
|
|
44
44
|
handle(event)
|
|
45
|
-
return "", 200`,Nt=[["specVersion",`Versi skema amplop. Hari ini selalu "${xa}". Penerima sebaiknya menolak versi yang tak dikenalnya.`],["id","Id peristiwa, unik & STABIL lintas percobaan. Inilah kunci idempotensi Anda."],["type","Jenis peristiwa, mis. spec.stage_changed. Daftar lengkapnya di atas."],["createdAt","Waktu peristiwa terjadi di hanoman (ISO 8601 UTC) — bukan waktu pengiriman."],["project","{ id, name } project terkait, atau null untuk peristiwa lintas-project."],["actor","Siapa yang memicu: user (email), agent (nama token), lead, scheduler, atau system."],["data.entity","Jenis objek yang berubah: spec, project, session, ticket, dan seterusnya."],["data.id","Id baris yang berubah."],["data.action","created | updated | deleted."],["data.changed","Daftar nama field yang benar-benar berubah. Kosong untuk created/deleted."],["data.before","Keadaan SEBELUM perubahan; null untuk created (atau bila dipangkas)."],["data.after","Keadaan SESUDAH perubahan; null untuk deleted."],["data.cascade","Hanya pada project.deleted: jumlah anak yang ikut terhapus."],["truncated","true bila amplop dipangkas karena melewati batas ukuran."],["truncatedFields","Nama field yang dipangkas, mis. after.objective."]];function Rt({onBack:a}={}){const n=ka.map(i=>({label:i.label,events:Ne.filter(r=>r.entity===i.entity)})).concat([{label:"Webhook",events:Ne.filter(i=>i.entity==="webhook")}]);return e.jsxs(e.Fragment,{children:[e.jsxs(C,{eyebrow:"dokumentasi",title:"Dokumentasi webhook",children:[e.jsx("div",{style:{display:"flex",gap:8,marginBottom:12},children:e.jsxs(y,{size:"sm",variant:"ghost",onClick:a,children:[e.jsx(Fe,{name:"arrow-left",size:14})," Kembali ke daftar endpoint"]})}),e.jsxs("div",{style:J,children:["Setiap kali sesuatu berubah di hanoman, kami mengirim satu ",e.jsx("b",{children:"HTTP POST"})," ke URL yang Anda daftarkan. Kanal ini ",e.jsx("b",{children:"satu arah"})," — hanoman tak pernah membaca apa pun dari penerima selain kode status HTTP-nya. Badan permintaan selalu berbentuk amplop seragam ber-versi ",e.jsx("code",{children:xa}),", jadi penerima yang ditulis hari ini tetap bekerja saat jenis peristiwa baru ditambahkan."]})]}),e.jsxs(C,{eyebrow:"katalog",title:"Jenis peristiwa dan kapan terpicu",children:[e.jsx("div",{style:{display:"flex",flexDirection:"column",gap:16},children:n.map(i=>e.jsxs("div",{children:[e.jsx("div",{style:{fontWeight:700,fontSize:13.5,marginBottom:8},children:i.label}),e.jsx("div",{style:{display:"flex",flexDirection:"column",gap:10},children:i.events.map(r=>e.jsxs("div",{style:{paddingLeft:10,borderLeft:"2px solid var(--border-hair)"},children:[e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8,flexWrap:"wrap"},children:[e.jsx("code",{style:{fontFamily:"var(--font-mono)",fontSize:12.5,fontWeight:600},children:r.type}),e.jsx(ne,{tone:"brass",size:"sm",children:r.entityLabel}),e.jsx("span",{style:{fontSize:12.5},children:r.label})]}),e.jsx("div",{style:{...J,marginTop:4},children:r.when})]},r.type))})]},i.label))}),e.jsx(ue,{tone:"info",title:"Satu perubahan = satu peristiwa",style:{marginTop:14},children:e.jsxs("div",{style:J,children:["Peristiwa turunan ",e.jsx("b",{children:"menggantikan"}),", bukan menambah: perubahan stage backlog dikirim sebagai ",e.jsx("code",{children:"spec.stage_changed"})," dan ",e.jsx("b",{children:"tidak"})," juga sebagai"," ",e.jsx("code",{children:"spec.updated"}),". Berlangganan ",e.jsx("code",{children:"spec.*"})," tetap menerima keduanya. Menghapus project memancarkan ",e.jsx("code",{children:"project.deleted"})," saja — anak-anaknya dihapus oleh database, di luar jangkauan pengirim; jumlahnya ada di ",e.jsx("code",{children:"data.cascade"}),"."]})})]}),e.jsx(C,{eyebrow:"amplop",title:"Anatomi amplop",children:e.jsx("div",{className:"hn-local-overflow",style:{overflowX:"auto"},children:e.jsx("table",{style:{width:"100%",borderCollapse:"collapse",fontSize:12.5},children:e.jsx("tbody",{children:Nt.map(([i,r])=>e.jsxs("tr",{style:{borderTop:"1px solid var(--border-hair)"},children:[e.jsx("td",{style:{padding:"6px 8px",fontFamily:"var(--font-mono)",whiteSpace:"nowrap",verticalAlign:"top"},children:i}),e.jsx("td",{style:{padding:"6px 8px",color:"var(--text-muted)"},children:r})]},i))})})})}),e.jsxs(C,{eyebrow:"contoh",title:"Contoh payload per jenis",children:[e.jsx("div",{style:{...J,marginBottom:10},children:"Contoh di bawah dibangun dari katalog yang sama dengan pengirimnya — bukan salinan yang bisa basi. Salin salah satunya sebagai fixture test penerima Anda."}),e.jsx("div",{style:{display:"flex",flexDirection:"column",gap:16},children:Ne.map(i=>e.jsxs("div",{children:[e.jsx("div",{style:{display:"flex",alignItems:"center",gap:8,marginBottom:6},children:e.jsx("code",{style:{fontFamily:"var(--font-mono)",fontSize:12.5,fontWeight:600},children:i.type})}),e.jsx(Je,{text:JSON.stringify(Ka(i.type),null,2),label:`Salin contoh ${i.type}`})]},i.type))})]}),e.jsxs(C,{eyebrow:"keamanan",title:"Header dan verifikasi tanda tangan",children:[e.jsx("div",{style:J,children:"Setiap permintaan membawa header berikut:"}),e.jsx("ul",{style:{...J,paddingLeft:20},children:Object.values(Oe).map(i=>e.jsx("li",{children:e.jsx("code",{style:{fontFamily:"var(--font-mono)"},children:i})},i))}),e.jsxs("div",{style:J,children:["Tanda tangannya ",e.jsx("code",{children:"v1="})," diikuti HMAC-SHA256 heksadesimal atas string"," ",e.jsx("code",{children:"<timestamp>.<raw body>"})," memakai secret endpoint. ",e.jsx("b",{children:"Verifikasi atas byte mentah"}),", bukan hasil ",e.jsx("code",{children:"JSON.parse"})," lalu ",e.jsx("code",{children:"stringify"})," — serialisasi ulang mengubah byte dan tanda tangannya tak akan pernah cocok. Bandingkan dengan perbandingan waktu-tetap, dan tolak permintaan yang timestamp-nya menyimpang lebih dari ",e.jsxs("b",{children:[Qe," detik"]})," supaya kiriman lama tak bisa diputar ulang."]}),e.jsx("div",{style:{marginTop:12},children:e.jsx(Je,{text:Ht,label:"Salin contoh Node.js"})}),e.jsx("div",{style:{marginTop:12},children:e.jsx(Je,{text:_t,label:"Salin contoh Python"})})]}),e.jsxs(C,{eyebrow:"pengiriman",title:"Retry, pengiriman ganda, dan idempotensi",children:[e.jsxs("div",{style:J,children:["Pengiriman berjalan lewat antrean, jadi penerima yang lambat tak pernah memperlambat hanoman. Pengiriman gagal diulang sampai ",e.jsxs("b",{children:[Ja," percobaan"]})," dengan jeda berikut:"]}),e.jsx("div",{className:"hn-local-overflow",style:{overflowX:"auto",marginTop:8},children:e.jsx("table",{style:{borderCollapse:"collapse",fontSize:12.5},children:e.jsxs("tbody",{children:[e.jsxs("tr",{children:[e.jsx("td",{style:{padding:"4px 10px",color:"var(--text-muted)"},children:"Percobaan"}),na.map((i,r)=>e.jsx("td",{style:{padding:"4px 10px",fontFamily:"var(--font-mono)"},children:r+1},r))]}),e.jsxs("tr",{style:{borderTop:"1px solid var(--border-hair)"},children:[e.jsx("td",{style:{padding:"4px 10px",color:"var(--text-muted)"},children:"Jeda sebelumnya"}),na.map((i,r)=>e.jsx("td",{style:{padding:"4px 10px",fontFamily:"var(--font-mono)"},children:i===0?"—":i<60?`${i} dtk`:i<3600?`${i/60} mnt`:`${i/3600} jam`},r))]})]})})}),e.jsx(ue,{tone:"warn",title:"Penerima WAJIB idempoten",style:{marginTop:12},children:e.jsxs("div",{style:J,children:["Kontraknya ",e.jsx("b",{children:"at-least-once"}),": satu peristiwa bisa tiba lebih dari sekali — retry setelah jaringan putus di tengah, atau setelah hanoman di-restart saat pengiriman sedang berjalan. Retry mengirim ",e.jsx("b",{children:"byte yang persis sama"}),", jadi ",e.jsx("code",{children:"id"})," peristiwanya tetap. Simpan ",e.jsx("code",{children:"id"})," yang sudah Anda proses dan abaikan kembarannya. Urutan kedatangan juga tak dijamin — jangan menyimpulkan urutan dari urutan tiba; pakai"," ",e.jsx("code",{children:"createdAt"}),"."]})}),e.jsxs("div",{style:{...J,marginTop:10},children:["Balas ",e.jsx("b",{children:"2xx"})," secepat mungkin lalu kerjakan yang berat di latar; hanoman menutup koneksi setelah 10 detik dan menghitungnya sebagai gagal. Balasan ",e.jsx("b",{children:"410 Gone"})," ","menonaktifkan endpoint seketika — pakai itu bila penerima memang pensiun."," ",e.jsxs("b",{children:[Va," pengiriman gagal beruntun"]})," (yakni yang sudah kehabisan seluruh percobaannya) menonaktifkan endpoint otomatis, dan operator diberi notifikasi."]})]}),e.jsx(C,{eyebrow:"batas",title:"Batas ukuran, laju, dan antrean",children:e.jsxs("div",{style:J,children:["Amplop dibatasi ",e.jsxs("b",{children:[Math.round(Ya/1024)," KiB"]}),". Yang melewatinya dipangkas bertahap — field teks panjang dipotong lebih dulu, lalu ",e.jsx("code",{children:"data.before"})," ","dibuang seluruhnya — dan hasilnya ditandai ",e.jsx("code",{children:"truncated: true"})," dengan daftar"," ",e.jsx("code",{children:"truncatedFields"}),". Amplop ",e.jsx("b",{children:"tak pernah"})," dikirim utuh melebihi batas itu, jadi jangan menganggap ",e.jsx("code",{children:"after"})," selalu lengkap; ambil ulang lewat API bila butuh isi penuh. Tiap endpoint juga punya batas laju per menit, dan antrean per endpoint dibatasi ",e.jsx("b",{children:Xa})," kiriman menunggu — kelebihannya tercatat sebagai"," ",e.jsx("code",{children:"dropped"})," di riwayat, bukan hilang diam-diam."]})}),e.jsxs(C,{eyebrow:"keamanan",title:"Yang kami jaga, dan yang tetap jadi tanggung jawab Anda",children:[e.jsxs("div",{style:J,children:["Secret setiap endpoint disimpan terenkripsi dan ",e.jsx("b",{children:"tak pernah"})," dikembalikan utuh oleh API — ia ditampilkan sekali saat dibuat atau dirotasi. Payload hanya memuat field yang didaftarkan katalog, jadi token, kredensial, dan isi rahasia tak pernah ikut. URL tujuan wajib ",e.jsx("code",{children:"http"}),"/",e.jsx("code",{children:"https"})," tanpa kredensial di dalamnya, dan alamat internal/loopback ditolak kecuali Anda mengizinkannya secara eksplisit per endpoint."]}),e.jsx(ue,{tone:"warn",title:"Batas yang jujur",style:{marginTop:10},children:e.jsxs("div",{style:J,children:["Pemeriksaan alamat dijalankan ulang pada ",e.jsx("b",{children:"setiap"})," percobaan kirim, tapi selalu ada jeda antara memeriksa DNS dan menyambung — DNS rebinding karena itu dipersempit,",e.jsx("b",{children:" tidak ditutup"}),'. Jangan mendaftarkan penerima di jaringan yang tak Anda percayai, dan jangan menyalakan "izinkan alamat internal" kecuali penerimanya memang milik Anda.']})})]}),e.jsx(C,{eyebrow:"panduan",title:"Membuat penerima webhook pertama Anda",children:e.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:12},children:[e.jsxs("div",{children:[e.jsx("div",{style:{fontWeight:600,fontSize:13},children:"Langkah 1 — jalankan penerima"}),e.jsxs("div",{style:J,children:["Salin potongan Node.js atau Python di atas ke berkas baru, isi"," ",e.jsx("code",{children:"HANOMAN_WEBHOOK_SECRET"})," dengan nilai apa pun untuk sementara, lalu jalankan. Cukup satu route yang membalas 2xx."]})]}),e.jsxs("div",{children:[e.jsx("div",{style:{fontWeight:600,fontSize:13},children:"Langkah 2 — buat URL-nya bisa dijangkau"}),e.jsxs("div",{style:J,children:["Kalau penerima berjalan di mesin yang sama dengan hanoman, pakai"," ",e.jsx("code",{children:"http://127.0.0.1:<port>"})," dan nyalakan"," ",e.jsx("b",{children:"Izinkan alamat internal"})," pada endpoint. Kalau di tempat lain, pastikan URL-nya dapat diakses dari mesin ini lewat http/https."]})]}),e.jsxs("div",{children:[e.jsx("div",{style:{fontWeight:600,fontSize:13},children:"Langkah 3 — daftarkan endpoint"}),e.jsxs("div",{style:J,children:["Kembali ke daftar endpoint → ",e.jsx("b",{children:"Tambah endpoint"}),". Isi nama dan URL, pilih peristiwa (mulailah dengan ",e.jsx("b",{children:"Semua peristiwa"}),"), simpan. ",e.jsx("b",{children:"Salin secret yang muncul"})," — ia tak ditampilkan lagi — lalu pasang sebagai ",e.jsx("code",{children:"HANOMAN_WEBHOOK_SECRET"})," di penerima Anda dan jalankan ulang."]})]}),e.jsxs("div",{children:[e.jsx("div",{style:{fontWeight:600,fontSize:13},children:"Langkah 4 — tekan Test, lalu buktikan"}),e.jsxs("div",{style:J,children:["Tombol ",e.jsx("b",{children:"Test"})," mengirim satu ",e.jsx("code",{children:"webhook.ping"}),". Yang benar: penerima memverifikasi tanda tangan dan hanoman menampilkan ",e.jsx("code",{children:"HTTP 200"}),". Bila gagal, pesannya ditampilkan apa adanya. Sesudah itu ubah sesuatu di backlog dan buka"," ",e.jsx("b",{children:"Riwayat"})," — peristiwa sungguhan pertama Anda ada di sana, lengkap dengan status, kode HTTP, dan galatnya bila ada."]})]})]})})]})}const ca=[{id:"claude-code",label:"Claude Code",hint:"~/.claude.json — atau jalankan perintah `claude mcp add` di bawah."},{id:"claude-desktop",label:"Claude Desktop",hint:"Settings → Developer → Edit Config (claude_desktop_config.json)."},{id:"codex",label:"Codex",hint:"~/.codex/config.toml"},{id:"cursor",label:"Cursor / Copilot",hint:"~/.cursor/mcp.json atau .vscode/mcp.json di project."}],Ft={"read-only":"--read-only",default:"",danger:"--danger"};function Wt(a,n,i){const r=Ft[i],t=r?`["mcp", "${r}"]`:'["mcp"]',c=`{
|
|
45
|
+
return "", 200`,Rt=[["specVersion",`Versi skema amplop. Hari ini selalu "${xa}". Penerima sebaiknya menolak versi yang tak dikenalnya.`],["id","Id peristiwa, unik & STABIL lintas percobaan. Inilah kunci idempotensi Anda."],["type","Jenis peristiwa, mis. spec.stage_changed. Daftar lengkapnya di atas."],["createdAt","Waktu peristiwa terjadi di hanoman (ISO 8601 UTC) — bukan waktu pengiriman."],["project","{ id, name } project terkait, atau null untuk peristiwa lintas-project."],["actor","Siapa yang memicu: user (email), agent (nama token), lead, scheduler, atau system."],["data.entity","Jenis objek yang berubah: spec, project, session, ticket, dan seterusnya."],["data.id","Id baris yang berubah."],["data.action","created | updated | deleted."],["data.changed","Daftar nama field yang benar-benar berubah. Kosong untuk created/deleted."],["data.before","Keadaan SEBELUM perubahan; null untuk created (atau bila dipangkas)."],["data.after","Keadaan SESUDAH perubahan; null untuk deleted."],["data.cascade","Hanya pada project.deleted: jumlah anak yang ikut terhapus."],["truncated","true bila amplop dipangkas karena melewati batas ukuran."],["truncatedFields","Nama field yang dipangkas, mis. after.objective."]];function Ft({onBack:a}={}){const n=ka.map(i=>({label:i.label,events:Ne.filter(r=>r.entity===i.entity)})).concat([{label:"Webhook",events:Ne.filter(i=>i.entity==="webhook")}]);return e.jsxs(e.Fragment,{children:[e.jsxs(C,{eyebrow:"dokumentasi",title:"Dokumentasi webhook",children:[e.jsx("div",{style:{display:"flex",gap:8,marginBottom:12},children:e.jsxs(y,{size:"sm",variant:"ghost",onClick:a,children:[e.jsx(Fe,{name:"arrow-left",size:14})," Kembali ke daftar endpoint"]})}),e.jsxs("div",{style:J,children:["Setiap kali sesuatu berubah di hanoman, kami mengirim satu ",e.jsx("b",{children:"HTTP POST"})," ke URL yang Anda daftarkan. Kanal ini ",e.jsx("b",{children:"satu arah"})," — hanoman tak pernah membaca apa pun dari penerima selain kode status HTTP-nya. Badan permintaan selalu berbentuk amplop seragam ber-versi ",e.jsx("code",{children:xa}),", jadi penerima yang ditulis hari ini tetap bekerja saat jenis peristiwa baru ditambahkan."]})]}),e.jsxs(C,{eyebrow:"katalog",title:"Jenis peristiwa dan kapan terpicu",children:[e.jsx("div",{style:{display:"flex",flexDirection:"column",gap:16},children:n.map(i=>e.jsxs("div",{children:[e.jsx("div",{style:{fontWeight:700,fontSize:13.5,marginBottom:8},children:i.label}),e.jsx("div",{style:{display:"flex",flexDirection:"column",gap:10},children:i.events.map(r=>e.jsxs("div",{style:{paddingLeft:10,borderLeft:"2px solid var(--border-hair)"},children:[e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8,flexWrap:"wrap"},children:[e.jsx("code",{style:{fontFamily:"var(--font-mono)",fontSize:12.5,fontWeight:600},children:r.type}),e.jsx(ne,{tone:"brass",size:"sm",children:r.entityLabel}),e.jsx("span",{style:{fontSize:12.5},children:r.label})]}),e.jsx("div",{style:{...J,marginTop:4},children:r.when})]},r.type))})]},i.label))}),e.jsx(ue,{tone:"info",title:"Satu perubahan = satu peristiwa",style:{marginTop:14},children:e.jsxs("div",{style:J,children:["Peristiwa turunan ",e.jsx("b",{children:"menggantikan"}),", bukan menambah: perubahan stage backlog dikirim sebagai ",e.jsx("code",{children:"spec.stage_changed"})," dan ",e.jsx("b",{children:"tidak"})," juga sebagai"," ",e.jsx("code",{children:"spec.updated"}),". Berlangganan ",e.jsx("code",{children:"spec.*"})," tetap menerima keduanya. Menghapus project memancarkan ",e.jsx("code",{children:"project.deleted"})," saja — anak-anaknya dihapus oleh database, di luar jangkauan pengirim; jumlahnya ada di ",e.jsx("code",{children:"data.cascade"}),"."]})})]}),e.jsx(C,{eyebrow:"amplop",title:"Anatomi amplop",children:e.jsx("div",{className:"hn-local-overflow",style:{overflowX:"auto"},children:e.jsx("table",{style:{width:"100%",borderCollapse:"collapse",fontSize:12.5},children:e.jsx("tbody",{children:Rt.map(([i,r])=>e.jsxs("tr",{style:{borderTop:"1px solid var(--border-hair)"},children:[e.jsx("td",{style:{padding:"6px 8px",fontFamily:"var(--font-mono)",whiteSpace:"nowrap",verticalAlign:"top"},children:i}),e.jsx("td",{style:{padding:"6px 8px",color:"var(--text-muted)"},children:r})]},i))})})})}),e.jsxs(C,{eyebrow:"contoh",title:"Contoh payload per jenis",children:[e.jsx("div",{style:{...J,marginBottom:10},children:"Contoh di bawah dibangun dari katalog yang sama dengan pengirimnya — bukan salinan yang bisa basi. Salin salah satunya sebagai fixture test penerima Anda."}),e.jsx("div",{style:{display:"flex",flexDirection:"column",gap:16},children:Ne.map(i=>e.jsxs("div",{children:[e.jsx("div",{style:{display:"flex",alignItems:"center",gap:8,marginBottom:6},children:e.jsx("code",{style:{fontFamily:"var(--font-mono)",fontSize:12.5,fontWeight:600},children:i.type})}),e.jsx(Je,{text:JSON.stringify(Ja(i.type),null,2),label:`Salin contoh ${i.type}`})]},i.type))})]}),e.jsxs(C,{eyebrow:"keamanan",title:"Header dan verifikasi tanda tangan",children:[e.jsx("div",{style:J,children:"Setiap permintaan membawa header berikut:"}),e.jsx("ul",{style:{...J,paddingLeft:20},children:Object.values(Oe).map(i=>e.jsx("li",{children:e.jsx("code",{style:{fontFamily:"var(--font-mono)"},children:i})},i))}),e.jsxs("div",{style:J,children:["Tanda tangannya ",e.jsx("code",{children:"v1="})," diikuti HMAC-SHA256 heksadesimal atas string"," ",e.jsx("code",{children:"<timestamp>.<raw body>"})," memakai secret endpoint. ",e.jsx("b",{children:"Verifikasi atas byte mentah"}),", bukan hasil ",e.jsx("code",{children:"JSON.parse"})," lalu ",e.jsx("code",{children:"stringify"})," — serialisasi ulang mengubah byte dan tanda tangannya tak akan pernah cocok. Bandingkan dengan perbandingan waktu-tetap, dan tolak permintaan yang timestamp-nya menyimpang lebih dari ",e.jsxs("b",{children:[Qe," detik"]})," supaya kiriman lama tak bisa diputar ulang."]}),e.jsx("div",{style:{marginTop:12},children:e.jsx(Je,{text:_t,label:"Salin contoh Node.js"})}),e.jsx("div",{style:{marginTop:12},children:e.jsx(Je,{text:Nt,label:"Salin contoh Python"})})]}),e.jsxs(C,{eyebrow:"pengiriman",title:"Retry, pengiriman ganda, dan idempotensi",children:[e.jsxs("div",{style:J,children:["Pengiriman berjalan lewat antrean, jadi penerima yang lambat tak pernah memperlambat hanoman. Pengiriman gagal diulang sampai ",e.jsxs("b",{children:[Va," percobaan"]})," dengan jeda berikut:"]}),e.jsx("div",{className:"hn-local-overflow",style:{overflowX:"auto",marginTop:8},children:e.jsx("table",{style:{borderCollapse:"collapse",fontSize:12.5},children:e.jsxs("tbody",{children:[e.jsxs("tr",{children:[e.jsx("td",{style:{padding:"4px 10px",color:"var(--text-muted)"},children:"Percobaan"}),na.map((i,r)=>e.jsx("td",{style:{padding:"4px 10px",fontFamily:"var(--font-mono)"},children:r+1},r))]}),e.jsxs("tr",{style:{borderTop:"1px solid var(--border-hair)"},children:[e.jsx("td",{style:{padding:"4px 10px",color:"var(--text-muted)"},children:"Jeda sebelumnya"}),na.map((i,r)=>e.jsx("td",{style:{padding:"4px 10px",fontFamily:"var(--font-mono)"},children:i===0?"—":i<60?`${i} dtk`:i<3600?`${i/60} mnt`:`${i/3600} jam`},r))]})]})})}),e.jsx(ue,{tone:"warn",title:"Penerima WAJIB idempoten",style:{marginTop:12},children:e.jsxs("div",{style:J,children:["Kontraknya ",e.jsx("b",{children:"at-least-once"}),": satu peristiwa bisa tiba lebih dari sekali — retry setelah jaringan putus di tengah, atau setelah hanoman di-restart saat pengiriman sedang berjalan. Retry mengirim ",e.jsx("b",{children:"byte yang persis sama"}),", jadi ",e.jsx("code",{children:"id"})," peristiwanya tetap. Simpan ",e.jsx("code",{children:"id"})," yang sudah Anda proses dan abaikan kembarannya. Urutan kedatangan juga tak dijamin — jangan menyimpulkan urutan dari urutan tiba; pakai"," ",e.jsx("code",{children:"createdAt"}),"."]})}),e.jsxs("div",{style:{...J,marginTop:10},children:["Balas ",e.jsx("b",{children:"2xx"})," secepat mungkin lalu kerjakan yang berat di latar; hanoman menutup koneksi setelah 10 detik dan menghitungnya sebagai gagal. Balasan ",e.jsx("b",{children:"410 Gone"})," ","menonaktifkan endpoint seketika — pakai itu bila penerima memang pensiun."," ",e.jsxs("b",{children:[Ya," pengiriman gagal beruntun"]})," (yakni yang sudah kehabisan seluruh percobaannya) menonaktifkan endpoint otomatis, dan operator diberi notifikasi."]})]}),e.jsx(C,{eyebrow:"batas",title:"Batas ukuran, laju, dan antrean",children:e.jsxs("div",{style:J,children:["Amplop dibatasi ",e.jsxs("b",{children:[Math.round(Xa/1024)," KiB"]}),". Yang melewatinya dipangkas bertahap — field teks panjang dipotong lebih dulu, lalu ",e.jsx("code",{children:"data.before"})," ","dibuang seluruhnya — dan hasilnya ditandai ",e.jsx("code",{children:"truncated: true"})," dengan daftar"," ",e.jsx("code",{children:"truncatedFields"}),". Amplop ",e.jsx("b",{children:"tak pernah"})," dikirim utuh melebihi batas itu, jadi jangan menganggap ",e.jsx("code",{children:"after"})," selalu lengkap; ambil ulang lewat API bila butuh isi penuh. Tiap endpoint juga punya batas laju per menit, dan antrean per endpoint dibatasi ",e.jsx("b",{children:qa})," kiriman menunggu — kelebihannya tercatat sebagai"," ",e.jsx("code",{children:"dropped"})," di riwayat, bukan hilang diam-diam."]})}),e.jsxs(C,{eyebrow:"keamanan",title:"Yang kami jaga, dan yang tetap jadi tanggung jawab Anda",children:[e.jsxs("div",{style:J,children:["Secret setiap endpoint disimpan terenkripsi dan ",e.jsx("b",{children:"tak pernah"})," dikembalikan utuh oleh API — ia ditampilkan sekali saat dibuat atau dirotasi. Payload hanya memuat field yang didaftarkan katalog, jadi token, kredensial, dan isi rahasia tak pernah ikut. URL tujuan wajib ",e.jsx("code",{children:"http"}),"/",e.jsx("code",{children:"https"})," tanpa kredensial di dalamnya, dan alamat internal/loopback ditolak kecuali Anda mengizinkannya secara eksplisit per endpoint."]}),e.jsx(ue,{tone:"warn",title:"Batas yang jujur",style:{marginTop:10},children:e.jsxs("div",{style:J,children:["Pemeriksaan alamat dijalankan ulang pada ",e.jsx("b",{children:"setiap"})," percobaan kirim, tapi selalu ada jeda antara memeriksa DNS dan menyambung — DNS rebinding karena itu dipersempit,",e.jsx("b",{children:" tidak ditutup"}),'. Jangan mendaftarkan penerima di jaringan yang tak Anda percayai, dan jangan menyalakan "izinkan alamat internal" kecuali penerimanya memang milik Anda.']})})]}),e.jsx(C,{eyebrow:"panduan",title:"Membuat penerima webhook pertama Anda",children:e.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:12},children:[e.jsxs("div",{children:[e.jsx("div",{style:{fontWeight:600,fontSize:13},children:"Langkah 1 — jalankan penerima"}),e.jsxs("div",{style:J,children:["Salin potongan Node.js atau Python di atas ke berkas baru, isi"," ",e.jsx("code",{children:"HANOMAN_WEBHOOK_SECRET"})," dengan nilai apa pun untuk sementara, lalu jalankan. Cukup satu route yang membalas 2xx."]})]}),e.jsxs("div",{children:[e.jsx("div",{style:{fontWeight:600,fontSize:13},children:"Langkah 2 — buat URL-nya bisa dijangkau"}),e.jsxs("div",{style:J,children:["Kalau penerima berjalan di mesin yang sama dengan hanoman, pakai"," ",e.jsx("code",{children:"http://127.0.0.1:<port>"})," dan nyalakan"," ",e.jsx("b",{children:"Izinkan alamat internal"})," pada endpoint. Kalau di tempat lain, pastikan URL-nya dapat diakses dari mesin ini lewat http/https."]})]}),e.jsxs("div",{children:[e.jsx("div",{style:{fontWeight:600,fontSize:13},children:"Langkah 3 — daftarkan endpoint"}),e.jsxs("div",{style:J,children:["Kembali ke daftar endpoint → ",e.jsx("b",{children:"Tambah endpoint"}),". Isi nama dan URL, pilih peristiwa (mulailah dengan ",e.jsx("b",{children:"Semua peristiwa"}),"), simpan. ",e.jsx("b",{children:"Salin secret yang muncul"})," — ia tak ditampilkan lagi — lalu pasang sebagai ",e.jsx("code",{children:"HANOMAN_WEBHOOK_SECRET"})," di penerima Anda dan jalankan ulang."]})]}),e.jsxs("div",{children:[e.jsx("div",{style:{fontWeight:600,fontSize:13},children:"Langkah 4 — tekan Test, lalu buktikan"}),e.jsxs("div",{style:J,children:["Tombol ",e.jsx("b",{children:"Test"})," mengirim satu ",e.jsx("code",{children:"webhook.ping"}),". Yang benar: penerima memverifikasi tanda tangan dan hanoman menampilkan ",e.jsx("code",{children:"HTTP 200"}),". Bila gagal, pesannya ditampilkan apa adanya. Sesudah itu ubah sesuatu di backlog dan buka"," ",e.jsx("b",{children:"Riwayat"})," — peristiwa sungguhan pertama Anda ada di sana, lengkap dengan status, kode HTTP, dan galatnya bila ada."]})]})]})})]})}const ca=[{id:"claude-code",label:"Claude Code",hint:"~/.claude.json — atau jalankan perintah `claude mcp add` di bawah."},{id:"claude-desktop",label:"Claude Desktop",hint:"Settings → Developer → Edit Config (claude_desktop_config.json)."},{id:"codex",label:"Codex",hint:"~/.codex/config.toml"},{id:"cursor",label:"Cursor / Copilot",hint:"~/.cursor/mcp.json atau .vscode/mcp.json di project."}],Wt={"read-only":"--read-only",default:"",danger:"--danger"};function $t(a,n,i){const r=Wt[i],t=r?`["mcp", "${r}"]`:'["mcp"]',c=`{
|
|
46
46
|
"mcpServers": {
|
|
47
47
|
"hanoman": {
|
|
48
48
|
"command": "hanoman",
|
|
@@ -59,4 +59,4 @@ args = ${t}
|
|
|
59
59
|
env = { HANOMAN_HOST = "${n}", HANOMAN_AGENT_TOKEN = "hnm_agt_…" }`:a==="claude-code"?`${c}
|
|
60
60
|
|
|
61
61
|
# atau, sekali jalan:
|
|
62
|
-
claude mcp add hanoman --env HANOMAN_HOST=${n} --env HANOMAN_AGENT_TOKEN=hnm_agt_… -- hanoman mcp${r?" "+r:""}`:a==="cursor"?c.replace('"mcpServers"','"servers"'):c}const $t={margin:0,padding:12,fontSize:12,lineHeight:1.5,overflowX:"auto",background:"var(--bone-100)",border:"1px solid var(--ink-200)",borderRadius:"var(--radius-sm)",whiteSpace:"pre"},Ae={fontSize:12.5,color:"var(--text-muted)",lineHeight:1.55,marginBottom:10},fe={padding:"6px 10px 6px 0",borderBottom:"1px solid var(--ink-100)",verticalAlign:"top",fontSize:12.5};function Gt(){const[a,n]=o.useState("claude-code"),[i,r]=o.useState("default"),[t,c]=o.useState(!1),d=typeof window>"u"?"http://localhost:8787":window.location.origin,h=Wt(a,d,i),f=ca.find(u=>u.id===a),g=()=>{var u;(u=navigator.clipboard)==null||u.writeText(h).then(()=>{c(!0),setTimeout(()=>c(!1),1500)},()=>{})};return e.jsxs(e.Fragment,{children:[e.jsxs(C,{eyebrow:"mcp",title:"MCP server",children:[e.jsxs("div",{style:Ae,children:["Agen AI mana pun yang berbicara MCP bisa memakai hanoman lewat ",e.jsx("code",{children:"hanoman mcp"})," — tanpa pembungkus khusus per-klien. Ia memakai ",e.jsx("b",{children:"agent token yang sama"})," dengan REST dan capability yang sama; buat tokennya di kartu di atas, lalu tempel di konfigurasi klien."," ",e.jsxs("b",{children:["Skema tool versi ",Et,"."]})]}),e.jsxs("div",{style:Ae,children:["Prasyarat: ",e.jsx("code",{children:"npm i -g hanoman"})," di mesin tempat klien AI-nya jalan. Host di bawah diisi otomatis dengan instance ini — ",e.jsx("b",{children:"agent token diterbitkan per-instance"}),", jadi token dari instance lain akan selalu ditolak 401 di sini."]}),e.jsxs("div",{style:Ae,children:["Tool berbahaya — membuka sesi agen di worktree, perintah VPS, merge/rebase, penghapusan backlog, perubahan stage — ",e.jsx("b",{children:"hanya muncul"})," pada tingkat ",e.jsx("code",{children:"--danger"}),". Tingkat itu ",e.jsx("b",{children:"bukan kontrol keamanan"}),": ia menentukan tool mana yang terlihat, sementara yang menahan sungguhan adalah capability berlabel ",e.jsx("b",{children:"berbahaya"})," yang kamu centang di kartu di atas. Sebuah tool bisa terlihat dan tetap menjawab 403."]}),e.jsxs("div",{role:"group","aria-label":"Klien MCP",style:{display:"flex",flexWrap:"wrap",gap:8,marginBottom:10},children:[ca.map(u=>e.jsx(y,{size:"sm",variant:u.id===a?"primary":"ghost",onClick:()=>n(u.id),children:u.label},u.id)),e.jsx(y,{size:"sm",variant:i==="read-only"?"primary":"ghost",leftIcon:"eye",onClick:()=>r(u=>u==="read-only"?"default":"read-only"),children:"Mode baca-saja"}),e.jsx(y,{size:"sm",variant:i==="danger"?"primary":"ghost",leftIcon:"alert-triangle",onClick:()=>r(u=>u==="danger"?"default":"danger"),children:"Tampilkan tool berbahaya"})]}),e.jsx("div",{style:{...Ae,marginBottom:8},children:f.hint}),e.jsx("pre",{"data-testid":"mcp-snippet",style:$t,children:h}),e.jsx("div",{style:{marginTop:10},children:e.jsx(y,{size:"sm",leftIcon:"copy",onClick:g,children:t?"Tersalin":"Salin"})})]}),e.jsxs(C,{eyebrow:"tool",title:"Tool yang tersedia",children:[e.jsxs("div",{style:Ae,children:["Centang capability di bawah pada token yang dipakai. Mode baca-saja menyembunyikan seluruh tool bertanda ",e.jsx("i",{children:"tulis"}),"."]}),e.jsx("div",{className:"hn-local-overflow",style:{overflowX:"auto"},children:e.jsxs("table",{"data-testid":"mcp-tools",style:{borderCollapse:"collapse",width:"100%"},children:[e.jsx("thead",{children:e.jsxs("tr",{children:[e.jsx("th",{style:{...fe,textAlign:"left",fontSize:11.5,color:"var(--text-subtle)"},children:"Tool"}),e.jsx("th",{style:{...fe,textAlign:"left",fontSize:11.5,color:"var(--text-subtle)"},children:"Mode"}),e.jsx("th",{style:{...fe,textAlign:"left",fontSize:11.5,color:"var(--text-subtle)"},children:"Capability"})]})}),e.jsx("tbody",{children:Ct.map(u=>e.jsxs("tr",{children:[e.jsxs("td",{style:fe,children:[e.jsx("code",{children:u.name}),e.jsx("div",{style:{fontSize:11.5,color:"var(--text-subtle)"},children:u.title})]}),e.jsx("td",{style:fe,children:u.mode==="read"?"baca":"tulis"}),e.jsx("td",{style:fe,children:u.capability?e.jsx("code",{children:u.capability}):e.jsx("span",{style:{color:"var(--text-subtle)"},children:"—"})})]},u.name))})]})})]})]})}const Ut="https://github.com/denameidina/hanoman/blob/main/docs/agent-integration.md";function Tt({onToast:a}){const[n,i]=o.useState(null),[r,t]=o.useState(!1),[c,d]=o.useState(!1),[h,f]=o.useState(!1),g=`${window.location.origin}${qa.agentDoc}`;async function u(){if(!h){if(n){t(!0);return}f(!0),d(!1);try{i(await j.agentDoc()),t(!0)}catch{d(!0)}finally{f(!1)}}}return e.jsxs(e.Fragment,{children:[e.jsxs(C,{eyebrow:"dokumentasi",title:"Dokumentasi AI Agent",children:[e.jsxs("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginBottom:12,lineHeight:1.5},children:["Satu halaman yang membuat agen mana pun bisa langsung bekerja — cukup berikan"," ",e.jsx("b",{children:"tautan ini + satu agent token"}),". Markdown mentah, bisa diambil agen lewat HTTP biasa, ",e.jsx("b",{children:"tanpa auth"}),"."]}),e.jsx("code",{style:{display:"block",wordBreak:"break-all",fontSize:12,marginBottom:10},children:g}),e.jsxs("div",{style:{display:"flex",gap:8,flexWrap:"wrap",alignItems:"center"},children:[e.jsx(y,{size:"sm",leftIcon:"copy",onClick:()=>{var k;(k=navigator.clipboard)==null||k.writeText(g),a==null||a("Tautan disalin","ok","copy")},children:"Salin tautan"}),e.jsx(y,{size:"sm",variant:"ghost",leftIcon:"book-open",disabled:h,onClick:()=>void u(),children:h?"Memuat…":"Buka"}),e.jsx("a",{href:Ut,target:"_blank",rel:"noreferrer",children:e.jsx(y,{size:"sm",variant:"ghost",leftIcon:"external-link",children:"Lihat di GitHub"})})]}),c&&e.jsx("div",{style:{marginTop:12},children:e.jsx(V,{kind:"error",compact:!0,title:"Gagal memuat dokumentasi",hint:g,action:()=>void u(),actionLabel:"Coba lagi"})})]}),r&&n!==null&&e.jsx(wt,{path:"agent-integration.md",text:n,eyebrow:"hanoman · panduan AI agent",onClose:()=>t(!1)})]})}const Ve={feature:"Brief (feature)",qa:"QA",scaffold:"Scaffold",reverse:"Reverse docs",prd:"PRD",audit:"Audit",breakdown:"Breakdown PRD",goal:"Goal",no_effort:"No effort"},ua=[{id:"claude",label:"Claude Code"},{id:"codex",label:"Codex CLI"}],Kt=[{value:"inline",label:"Inline — agen fase mengerjakan task sendiri (cepat)"},{value:"subagent",label:"Subagent per task — implementer + review tiap task (lebih lambat)"}],Jt={model:null,effort:null},He={padding:"6px 8px",borderTop:"1px solid var(--border-hair)",verticalAlign:"top"};function Vt({flow:a,phase:n,runtime:i,cell:r,onPick:t}){const c=at(i),d=tt(i,r.model??""),h=i==="codex"?te:fa,f=[{value:"",label:"— warisi orchestrator"},...r.model&&!c.some(u=>u.id===r.model)?[{value:r.model,label:r.model}]:[],...c.map(u=>({value:u.id,label:u.label}))],g=[{value:"",label:"— warisi"},...r.effort&&!d.includes(r.effort)?[{value:r.effort,label:r.effort}]:[],...d.map(u=>({value:u,label:u}))];return e.jsxs("div",{style:{display:"flex",gap:6,flexWrap:"wrap"},children:[e.jsx(M,{size:"sm","aria-label":`Model ${a} ${n} ${i}`,value:r.model??"",style:{minWidth:150},options:f,onChange:u=>{const k=u.target.value||null;t({model:k,effort:k&&r.effort?h(k,r.effort):r.effort},`${n} (${i}) → ${k??"warisi"}`)}}),e.jsx(M,{size:"sm","aria-label":`Effort ${a} ${n} ${i}`,value:r.effort??"",style:{minWidth:96},options:g,onChange:u=>t({...r,effort:u.target.value||null},`${n} (${i}) effort → ${u.target.value||"warisi"}`)})]})}function Yt({orchestration:a,onChange:n}){const i=a??Ye,r=c=>i[c]??Ye[c],t=(c,d,h)=>n({...i,[c]:d},h);return e.jsxs(e.Fragment,{children:[e.jsx(C,{eyebrow:"orkestrasi",title:"Orkestrasi subagent per fase",children:e.jsxs("div",{style:{fontSize:12.5,color:"var(--text-muted)",lineHeight:1.5},children:["Sesi utama menjadi ",e.jsx("b",{children:"orchestrator"}),"; setiap fase dikerjakan subagent dengan model & effort di bawah. ",e.jsx("b",{children:"Warisi"})," memakai model/effort orchestrator yang dipilih saat ",e.jsx("b",{children:"Start"}),". Subagent selalu berjalan di runtime orchestrator: sesi claude memakai kolom Claude Code, sesi codex kolom Codex CLI. Flow yang dimatikan berjalan sebagai sesi tunggal seperti sebelumnya."]})}),Qa.map(c=>{const d=r(c);return e.jsx("div",{"data-testid":`orch-flow-${c}`,children:e.jsxs(C,{eyebrow:"flow",title:Ve[c],children:[e.jsxs("div",{style:{display:"flex",alignItems:"center",justifyContent:"space-between",gap:12,marginBottom:10},children:[e.jsx("span",{style:{fontSize:12.5,color:"var(--text-muted)"},children:d.enabled?"Fase dikerjakan subagent":"Sesi tunggal (orkestrasi mati)"}),e.jsx(N,{checked:d.enabled,"aria-label":`Orkestrasi ${c}`,onChange:h=>t(c,{...d,enabled:h},`Orkestrasi ${Ve[c]} · ${h?"aktif":"nonaktif"}`)})]}),Za.has(c)&&e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:10,flexWrap:"wrap",marginBottom:10,opacity:d.enabled?1:.55},children:[e.jsx("span",{style:{fontSize:12.5,color:"var(--text-strong)",fontWeight:600},children:"Mode Execute"}),e.jsx(M,{size:"sm","aria-label":`Mode Execute ${c}`,value:d.executeMode??"inline",style:{minWidth:260,maxWidth:"100%"},options:Kt,onChange:h=>{const f=h.target.value==="subagent"?"subagent":"inline";t(c,{...d,executeMode:f},`Mode Execute ${Ve[c]} · ${f==="subagent"?"subagent per task":"inline"}`)}}),e.jsx("span",{style:{fontSize:11.5,color:"var(--text-muted)"},children:"khusus sesi Claude · Codex selalu inline"})]}),e.jsx("div",{style:{overflowX:"auto",opacity:d.enabled?1:.55},children:e.jsxs("table",{style:{width:"100%",minWidth:560,borderCollapse:"collapse",fontSize:12.5},children:[e.jsx("thead",{children:e.jsxs("tr",{children:[e.jsx("th",{style:{...He,borderTop:"none",textAlign:"left"},children:"Fase"}),ua.map(h=>e.jsx("th",{style:{...He,borderTop:"none",textAlign:"left"},children:h.label},h.id))]})}),e.jsx("tbody",{children:et[c].map(h=>e.jsxs("tr",{children:[e.jsx("td",{style:He,children:h}),ua.map(f=>e.jsx("td",{style:He,children:e.jsx(Vt,{flow:c,phase:h,runtime:f.id,cell:d[f.id][h]??Jt,onPick:(g,u)=>t(c,{...d,[f.id]:{...d[f.id],[h]:g}},u)})},f.id))]},h))})]})})]})},c)})]})}const ze=a=>!!a&&typeof a=="object"&&!Array.isArray(a)&&Object.getPrototypeOf(a)===Object.prototype,qe=(a,n)=>{if(a===n)return!0;if(Array.isArray(a)&&Array.isArray(n))return a.length===n.length&&a.every((i,r)=>qe(i,n[r]));if(ze(a)&&ze(n)){const i=Object.keys(a).filter(t=>a[t]!==void 0),r=Object.keys(n).filter(t=>n[t]!==void 0);return i.length===r.length&&i.every(t=>qe(a[t],n[t]))}return!1};function Ze(a,n,i){if(qe(n,a)&&i!==void 0)return i;if(!ze(a)||!ze(n)||!ze(i))return n;const r={...i};for(const t of Object.keys(n))r[t]=Ze(a[t],n[t],i[t]);return r}const je=a=>ft(a).map(n=>({value:n,label:n==="xhigh"?"x-high":n})),ye=a=>wa(vt,a),ve=(a,n)=>{const i=fa(a,n);return i===n?{}:{effort:i}},ga=[{value:"blip",label:"Blip · 0.1s"},{value:"pop",label:"Pop · 0.1s"},{value:"short",label:"Short · 0.15s"},{value:"ping",label:"Ping · 0.2s"},{value:"coin",label:"Coin · 0.3s"},{value:"alert",label:"Alert · 0.3s"},{value:"medium",label:"Medium · 0.4s"},{value:"chime",label:"Chime · 0.4s"},{value:"success",label:"Success · 0.4s"},{value:"bell",label:"Bell · 0.5s"},{value:"marimba",label:"Marimba · 0.6s"},{value:"long",label:"Long · 0.8s"},{value:"fanfare",label:"Fanfare · 0.9s"},{value:"off",label:"Senyap"}],Xt={model:"sonnet",effort:"medium",autoDefault:!0,autoScaffold:!0,notifyFail:!0,notifyDone:!0,notifySound:"short",notifyDecision:!0,notifyDecisionSound:"alert",agentAccessEnabled:!1,scheduler:bt,goal:pt,agent:"claude",codex:ja,verifyScope:"changed",method:mt,conflict:Sa,lead:Re,telegram:Se,changelog:va,portalChat:ya,orchestration:Ye,remoteControl:ht,logShipping:gt,logRetention:ut,builtinAgents:{},builtinAgentPolicies:{},builtinRuntimeDefaults:ct},T={claude:"Claude Code",codex:"Codex CLI"};function ha({id:a,label:n,active:i}){return e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8,paddingTop:4},children:[e.jsx("div",{style:{fontSize:13.5,fontWeight:700,color:"var(--text-strong)"},children:n}),e.jsx("span",{"data-testid":`agent-badge-${a}`,style:{fontSize:11,fontFamily:"var(--font-mono)",padding:"2px 7px",borderRadius:999,background:i?"var(--brass-100)":"transparent",color:i?"var(--brass-700)":"var(--text-subtle)",border:i?"none":"1px solid var(--border-hair)"},children:i?"dipakai sesi baru":"tidak dipakai sekarang"})]})}function v({title:a,desc:n,children:i,last:r}){return e.jsxs("div",{className:"hn-setting-row",style:{display:"flex",alignItems:"center",gap:16,padding:"14px 0",borderBottom:r?"none":"1px solid var(--border-hair)"},children:[e.jsxs("div",{style:{flex:1,minWidth:0},children:[e.jsx("div",{style:{fontSize:14,fontWeight:600,color:"var(--text-strong)"},children:a}),n&&e.jsx("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginTop:3,lineHeight:1.5},children:n})]}),e.jsx("div",{className:"hn-setting-control",style:{flex:"0 0 auto"},children:i})]})}function qt({me:a,onLoggedOut:n,onToast:i}){const[r,t]=o.useState(""),[c,d]=o.useState(""),[h,f]=o.useState(!1),g=r.length>=1&&c.length>=8&&!h;async function u(){if(g){f(!0);try{await j.changePassword({currentPassword:r,newPassword:c}),t(""),d(""),i==null||i("Password diganti · perangkat lain ter-logout","ok","key-round")}catch(z){i==null||i(z instanceof Xe&&z.status===400?"Password lama salah":"Gagal ganti password","err","x-circle")}finally{f(!1)}}}async function k(){try{await j.logout()}finally{n()}}return e.jsxs(C,{eyebrow:"akun",title:"Akun",actions:e.jsx(y,{size:"sm",variant:"ghost",leftIcon:"log-out",onClick:k,children:"Logout"}),children:[e.jsx(v,{title:"Masuk sebagai",desc:a.email,children:e.jsx("span",{style:{fontFamily:"var(--font-mono)",fontSize:12,color:"var(--text-subtle)"},children:a.id})}),e.jsxs("div",{style:{paddingTop:14},children:[e.jsx("div",{style:{fontSize:14,fontWeight:600,color:"var(--text-strong)",marginBottom:10},children:"Ganti password"}),e.jsxs("div",{className:"hn-grid-mobile",style:{display:"grid",gridTemplateColumns:"1fr 1fr auto",gap:10,alignItems:"end"},children:[e.jsx(ee,{label:"Password lama",children:e.jsx(Y,{type:"password",autoComplete:"current-password",placeholder:"••••••••",value:r,onChange:z=>t(z.target.value),style:{width:"100%"}})}),e.jsx(ee,{label:e.jsxs(e.Fragment,{children:["Password baru ",e.jsx("span",{style:{fontWeight:400,color:"var(--text-subtle)"},children:"· min 8"})]}),children:e.jsx(Y,{type:"password",autoComplete:"new-password",placeholder:"minimal 8 karakter",value:c,onChange:z=>d(z.target.value),style:{width:"100%"}})}),e.jsx(y,{size:"sm",leftIcon:"key-round",disabled:!g,onClick:u,style:{marginBottom:14},children:"Ganti"})]})]})]})}function Qt({me:a,onToast:n}){const{confirm:i,dialog:r}=$e(),[t,c]=o.useState(null),[d,h]=o.useState(""),[f,g]=o.useState(""),[u,k]=o.useState(!1),z=o.useCallback(()=>{j.listUsers().then(c).catch(()=>c([]))},[]);o.useEffect(()=>{z()},[z]);const R=/\S+@\S+\.\S+/.test(d)&&f.length>=8&&!u;async function F(){if(R){k(!0);try{await j.inviteUser({email:d,password:f}),h(""),g(""),z(),n==null||n("User "+d+" diundang","ok","user-plus")}catch(m){n==null||n(m instanceof Xe&&m.status===409?"Email sudah dipakai":"Gagal mengundang user","err","x-circle")}finally{k(!1)}}}async function A(m){try{if(!await i({title:`Hapus user "${m.email}"?`,message:"User ini kehilangan akses ke dashboard seketika.",impact:["Semua sesi login miliknya ikut dicabut.","Tindakan ini tak bisa dibatalkan."],confirmLabel:"Hapus user",run:()=>j.deleteUser(m.id)}))return;z(),n==null||n("User "+m.email+" dihapus","warn","trash-2")}catch(x){n==null||n(x instanceof Xe&&x.status===400?"Tak bisa hapus user terakhir":"Gagal hapus user","err","x-circle")}}return e.jsxs(C,{eyebrow:"users",title:"Users",children:[e.jsx("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginBottom:12,lineHeight:1.5},children:"Undang user lain dengan menetapkan password-nya langsung — tanpa email undangan."}),t===null?e.jsx(V,{kind:"loading",compact:!0,title:"Memuat users…"}):t.map((m,x)=>e.jsx(v,{title:m.email,last:x===t.length-1,desc:"dibuat "+new Date(m.createdAt).toLocaleDateString("id-ID")+(m.id===a.id?" · kamu":""),children:e.jsx(y,{size:"sm",variant:"ghost",leftIcon:"trash-2",disabled:t.length<=1,onClick:()=>A(m),children:"Hapus"})},m.id)),e.jsxs("div",{style:{paddingTop:14},children:[e.jsx("div",{style:{fontSize:14,fontWeight:600,color:"var(--text-strong)",marginBottom:10},children:"Invite user"}),e.jsxs("div",{className:"hn-grid-mobile",style:{display:"grid",gridTemplateColumns:"1fr 1fr auto",gap:10,alignItems:"end"},children:[e.jsx(ee,{label:"Email",children:e.jsx(Y,{type:"email",value:d,placeholder:"user@nafanesia.id",onChange:m=>h(m.target.value),style:{width:"100%"}})}),e.jsx(ee,{label:e.jsxs(e.Fragment,{children:["Password ",e.jsx("span",{style:{fontWeight:400,color:"var(--text-subtle)"},children:"· min 8"})]}),children:e.jsx(Y,{type:"password",autoComplete:"new-password",placeholder:"minimal 8 karakter",value:f,onChange:m=>g(m.target.value),style:{width:"100%"}})}),e.jsx(y,{size:"sm",leftIcon:"user-plus",disabled:!R,onClick:F,style:{marginBottom:14},children:"Invite"})]})]}),r]})}function Zt({onToast:a}){const{confirm:n,dialog:i}=$e(),[r,t]=o.useState(null),[c,d]=o.useState(""),[h,f]=o.useState(null),[g,u]=o.useState(!1),k=o.useCallback(()=>{j.listDeviceTokens().then(t).catch(()=>t([]))},[]);o.useEffect(()=>{k()},[k]);async function z(){if(!(c.trim().length<1||g)){u(!0);try{const A=await j.createDeviceToken({name:c.trim()});f({name:A.name,token:A.token}),d(""),k(),a==null||a("Token perangkat dibuat — salin sekarang","ok","key-round")}catch{a==null||a("Gagal membuat token","err","x-circle")}finally{u(!1)}}}async function R(A){try{if(!await n({title:`Cabut token perangkat "${A.name}"?`,message:"Perangkat itu tak bisa sync lagi sampai token baru dibuat.",confirmLabel:"Cabut token",icon:"key-round",run:()=>j.revokeDeviceToken(A.id)}))return;k(),a==null||a("Token dicabut","warn","trash-2")}catch{a==null||a("Gagal mencabut token","err","x-circle")}}const F=(r??[]).filter(A=>!A.revokedAt);return e.jsxs(C,{eyebrow:"perangkat",title:"Device tokens",children:[e.jsxs("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginBottom:12,lineHeight:1.5},children:["Token per-perangkat untuk menyinkronkan instance lokal ke hub (Bearer). Tempel di",e.jsx("code",{style:{margin:"0 4px"},children:"SYNC_DEVICE_TOKEN"})," pada instance client. Plaintext hanya tampil sekali."]}),h&&e.jsxs("div",{style:{padding:12,marginBottom:12,border:"1px solid var(--brass-300)",borderRadius:"var(--radius-sm)",background:"var(--brass-100)"},children:[e.jsxs("div",{style:{fontSize:12.5,fontWeight:600,marginBottom:6},children:["Token untuk “",h.name,"” — salin sekarang, tak akan ditampilkan lagi:"]}),e.jsx("code",{style:{display:"block",wordBreak:"break-all",fontSize:12},children:h.token}),e.jsxs("div",{style:{marginTop:8,display:"flex",gap:8},children:[e.jsx(y,{size:"sm",leftIcon:"copy",onClick:()=>{var A;(A=navigator.clipboard)==null||A.writeText(h.token),a==null||a("Disalin","ok","copy")},children:"Salin"}),e.jsx(y,{size:"sm",variant:"ghost",onClick:()=>f(null),children:"Tutup"})]})]}),r===null?e.jsx(V,{kind:"loading",compact:!0,title:"Memuat token…"}):F.length===0?e.jsx("div",{style:{fontSize:13,color:"var(--text-subtle)",padding:"8px 0"},children:"Belum ada token perangkat."}):F.map((A,m)=>e.jsx(v,{title:A.name,last:m===F.length-1,desc:"dibuat "+new Date(A.createdAt).toLocaleDateString("id-ID")+(A.lastSeenAt?" · terlihat "+new Date(A.lastSeenAt).toLocaleString("id-ID"):" · belum dipakai"),children:e.jsx(y,{size:"sm",variant:"ghost",leftIcon:"trash-2",onClick:()=>R(A),children:"Cabut"})},A.id)),e.jsxs("div",{className:"hn-grid-mobile",style:{paddingTop:14,display:"grid",gridTemplateColumns:"1fr auto",gap:10,alignItems:"end"},children:[e.jsx(ee,{label:"Nama perangkat",children:e.jsx(Y,{value:c,placeholder:"laptop-dena",onChange:A=>d(A.target.value),style:{width:"100%"}})}),e.jsx(y,{size:"sm",leftIcon:"plus",disabled:c.trim().length<1||g,onClick:z,style:{marginBottom:14},children:"Buat token"})]}),i]})}function en({onToast:a}){const{confirm:n,dialog:i}=$e(),[r,t]=o.useState(""),[c,d]=o.useState(null),h=o.useCallback(()=>{j.listSessionResults(r||void 0).then(d).catch(()=>d([]))},[r]);o.useEffect(()=>{h()},[h]);async function f(){if(!r){a==null||a("Isi project id untuk purge","warn","alert-triangle");return}try{if(!await n({title:`Purge activity log project "${r}"?`,message:"Seluruh entri hasil sesi project ini dihapus dari device ini.",impact:["Log bersifat append-only — entri yang dihapus tak bisa dipulihkan."],confirmLabel:"Purge",run:async()=>{const g=await j.purgeSessionResults(r);a==null||a(`${g.purged} entri dihapus`,"warn","trash-2")}}))return;h()}catch{a==null||a("Gagal purge","err","x-circle")}}return e.jsxs(C,{eyebrow:"aktivitas",title:"Activity log",children:[e.jsx("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginBottom:12,lineHeight:1.5},children:"Ringkasan hasil sesi lintas device (transisi stage, commit, PR) — append-only. Filter per project."}),e.jsxs("div",{className:"hn-grid-mobile",style:{display:"grid",gridTemplateColumns:"1fr auto",gap:10,alignItems:"end",marginBottom:12},children:[e.jsx(ee,{label:"Project id (opsional)",children:e.jsx(Y,{value:r,placeholder:"mis. hanoman — kosong = semua project",onChange:g=>t(g.target.value),style:{width:"100%"}})}),e.jsx(y,{size:"sm",variant:"ghost",leftIcon:"trash-2",disabled:!r,onClick:f,style:{marginBottom:14},children:"Purge"})]}),c===null?e.jsx(V,{kind:"loading",compact:!0,title:"Memuat aktivitas…"}):c.length===0?e.jsx("div",{style:{fontSize:13,color:"var(--text-subtle)",padding:"8px 0"},children:"Belum ada aktivitas."}):c.map((g,u)=>e.jsx(v,{last:u===c.length-1,title:`${g.specId??g.projectId}${g.oldStage&&g.newStage?` · ${g.oldStage} → ${g.newStage}`:""}`,desc:[g.status,g.commitSha?g.commitSha.slice(0,8):null,g.branch,g.author,new Date(g.createdAt).toLocaleString("id-ID")].filter(Boolean).join(" · "),children:g.prUrl&&e.jsx("a",{href:g.prUrl,target:"_blank",rel:"noreferrer",children:e.jsx(y,{size:"sm",variant:"ghost",leftIcon:"external-link",children:"PR"})})},g.id)),i]})}const an={sync:"Sync",claude:"Claude",vps:"VPS",runtime:"Runtime",bootstrap:"Bootstrap (read-only)"};function tn({onToast:a}){const[n,i]=o.useState(null),[r,t]=o.useState({}),c=o.useCallback(()=>{j.getConfig().then(i).catch(()=>i(null))},[]);if(o.useEffect(()=>{c()},[c]),!n)return e.jsx(V,{kind:"loading",title:"Memuat konfigurasi…"});const d=u=>t(k=>{const z={...k};return delete z[u],z}),h=async u=>{const k=r[u.key]??"";try{await j.putConfig(u.key,k),d(u.key),c(),a==null||a(`${u.label} disimpan`,"ok","check-circle-2")}catch{a==null||a(`Gagal menyimpan ${u.label}`,"err","x-circle")}},f=async u=>{try{await j.deleteConfig(u.key),d(u.key),c(),a==null||a(`${u.label} direset`,"warn","rotate-ccw")}catch{a==null||a("Gagal reset","err","x-circle")}},g=[...new Set(n.entries.map(u=>u.group))];return e.jsx(e.Fragment,{children:g.map(u=>e.jsxs(C,{eyebrow:u,title:an[u]??u,children:[u==="sync"&&e.jsx("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginBottom:8},children:n.sync.running?n.sync.connected?"● Tersambung ke hub":"◐ Sync aktif, menyambung…":"○ Tidak sync (HUB murni)"}),n.entries.filter(k=>k.group===u).map(k=>e.jsx(v,{title:k.label,desc:k.help,children:e.jsx(nn,{entry:k,draft:r[k.key],onDraft:z=>t(R=>({...R,[k.key]:z})),onSave:()=>h(k),onReset:()=>f(k)})},k.key))]},u))})}function nn({entry:a,draft:n,onDraft:i,onSave:r,onReset:t}){var d,h;const c=e.jsxs("span",{style:{fontSize:10.5,fontFamily:"var(--font-mono)",color:"var(--text-subtle)",marginRight:8},children:[a.source," · ",a.apply]});if(!a.editable)return e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8},children:[c,e.jsx("code",{style:{fontSize:12},children:a.masked??a.value??"—"})]});if(a.kind==="secret")return e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8},children:[c,a.hasValue&&n===void 0?e.jsxs(e.Fragment,{children:[e.jsx("code",{style:{fontSize:12},children:a.masked}),e.jsx(y,{size:"sm",variant:"ghost",leftIcon:"pencil",onClick:()=>i(""),children:"Ganti"}),e.jsx(y,{size:"sm",variant:"ghost",leftIcon:"trash-2",onClick:t,children:"Hapus"})]}):e.jsxs(e.Fragment,{children:[e.jsx(Y,{"aria-label":a.label,type:"password",placeholder:a.hasValue?"biarkan kosong = pertahankan":((d=We(a.key))==null?void 0:d.example)??"tempel token…",value:n??"",onChange:f=>i(f.target.value),style:{width:240}}),e.jsx(y,{size:"sm",leftIcon:"save",onClick:r,children:"Simpan"})]})]});if(a.kind==="bool"){const f=(n??a.value)==="1";return e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8},children:[c,e.jsx(N,{checked:f,onChange:g=>{i(g?"1":"0")}}),n!==void 0&&e.jsx(y,{size:"sm",leftIcon:"save",onClick:r,children:"Simpan"})]})}return e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8},children:[c,e.jsx(Y,{"aria-label":a.label,type:a.kind==="int"?"number":"text",placeholder:(h=We(a.key))==null?void 0:h.example,value:n??a.value??"",onChange:f=>i(f.target.value),style:{width:240}}),e.jsx(y,{size:"sm",leftIcon:"save",onClick:r,children:"Simpan"}),a.source==="db"&&e.jsx(y,{size:"sm",variant:"ghost",leftIcon:"rotate-ccw",onClick:t,children:"Reset"})]})}const ln=[{had:"sessions:write",needs:"sessions:spawn",sentence:"dulu bisa membuka sesi baru"},{had:"ide:write",needs:"ide:git",sentence:"dulu bisa merge/rebase & menghapus branch"},{had:"backlog:write",needs:"backlog:lifecycle",sentence:"dulu bisa integrate & menghapus backlog"},{had:"vps:write",needs:"vps:exec",sentence:"dulu bisa menjalankan perintah di VPS"}];function ma(a){return ln.filter(n=>a.includes(n.had)&&!a.includes(n.needs)).map(n=>n.sentence)}function _e({ids:a,picked:n,onToggle:i,ariaLabel:r,label:t}){const c=o.useRef(null),d=a.filter(f=>n.includes(f)).length,h=a.length>0&&d===a.length;return o.useEffect(()=>{c.current&&(c.current.indeterminate=d>0&&!h)},[d,h]),a.length===0?null:e.jsxs("label",{style:{display:"inline-flex",alignItems:"center",gap:4,cursor:"pointer",fontSize:11.5,color:"var(--text-subtle)",marginLeft:t?0:5},children:[t&&e.jsx("span",{children:t}),e.jsx("input",{ref:c,type:"checkbox","aria-label":r,checked:h,onChange:()=>i(a,!h)})]})}function sn({onToast:a}={}){const{confirm:n,dialog:i}=$e(),[r,t]=o.useState([]),[c,d]=o.useState(null),[h,f]=o.useState(null),[g,u]=o.useState(""),[k,z]=o.useState([]),[R,F]=o.useState(null),[A,m]=o.useState(!1),x=o.useCallback(()=>{j.listAgentTokens().then(b=>d(b.items)).catch(()=>d([]))},[]);o.useEffect(()=>{j.getSettings().then(f).catch(()=>{}),j.getAgentCapabilities().then(b=>t(b.capabilities)).catch(()=>{}),x()},[x]);async function O(b){if(!h)return;const B={...h,agentAccessEnabled:b};f(B);try{const s=await j.getSettings().catch(()=>{});await j.putSettings(Ze(h,B,s)),a==null||a("Akses AI agent "+(b?"aktif":"nonaktif"),b?"ok":"warn","bot")}catch{a==null||a("Gagal menyimpan","err","x-circle"),f(h)}}const K=b=>z(B=>B.includes(b)?B.filter(s=>s!==b):[...B,b]),U=(b,B)=>z(s=>B?Array.from(new Set([...s,...b])):s.filter(w=>!b.includes(w)));async function ge(){if(!(g.trim().length<1||A)){m(!0);try{const b=await j.createAgentToken({name:g.trim(),capabilities:k});F({name:b.name,token:b.token}),u(""),z([]),x(),a==null||a("Agent token dibuat — salin sekarang","ok","key-round")}catch{a==null||a("Gagal membuat token","err","x-circle")}finally{m(!1)}}}async function pe(b){try{if(!await n({title:`Cabut agent token "${b.name}"?`,message:"Agen yang memakainya langsung kehilangan akses.",confirmLabel:"Cabut token",icon:"key-round",run:()=>j.revokeAgentToken(b.id)}))return;x(),a==null||a("Token dicabut","warn","trash-2")}catch{a==null||a("Gagal mencabut token","err","x-circle")}}async function ie(b,B){try{await j.patchAgentToken(b.id,{enabled:B}),x()}catch{a==null||a("Gagal mengubah token","err","x-circle")}}const we=Array.from(new Set(r.map(b=>b.domain))),le=b=>r.filter(B=>B.access===b).map(B=>B.id),ae=r.map(b=>b.id),se=(c??[]).filter(b=>!b.revokedAt);return e.jsxs(e.Fragment,{children:[e.jsx(Tt,{onToast:a}),e.jsxs(C,{eyebrow:"ai agent",title:"Akses AI Agent",children:[e.jsxs("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginBottom:12,lineHeight:1.5},children:["Beri AI agent eksternal kendali hanoman lewat token (header ",e.jsx("code",{children:"Authorization: Bearer"}),"). Tiap fitur dibuka per ",e.jsx("b",{children:"capability"}),". Selagi master switch mati, semua token ditolak."]}),e.jsx(v,{title:"Aktifkan akses AI agent",last:!0,desc:"Master switch. Nonaktif → semua agent token 401, apa pun capability-nya.",children:e.jsx(N,{checked:!!(h!=null&&h.agentAccessEnabled),onChange:b=>void O(b)})})]}),e.jsxs(C,{eyebrow:"token",title:"Agent tokens",children:[R&&e.jsxs("div",{style:{padding:12,marginBottom:12,border:"1px solid var(--brass-300)",borderRadius:"var(--radius-sm)",background:"var(--brass-100)"},children:[e.jsxs("div",{style:{fontSize:12.5,fontWeight:600,marginBottom:6},children:["Token “",R.name,"” — salin sekarang, tak akan ditampilkan lagi:"]}),e.jsx("code",{style:{display:"block",wordBreak:"break-all",fontSize:12},children:R.token}),e.jsxs("div",{style:{marginTop:8,display:"flex",gap:8},children:[e.jsx(y,{size:"sm",leftIcon:"copy",onClick:()=>{var b;(b=navigator.clipboard)==null||b.writeText(R.token),a==null||a("Disalin","ok","copy")},children:"Salin"}),e.jsx(y,{size:"sm",variant:"ghost",onClick:()=>F(null),children:"Tutup"})]})]}),c===null?e.jsx(V,{kind:"loading",compact:!0,title:"Memuat token…"}):se.length===0?e.jsx("div",{style:{fontSize:13,color:"var(--text-subtle)",padding:"8px 0"},children:"Belum ada agent token."}):se.map((b,B)=>e.jsx(v,{title:b.name,last:B===se.length-1,desc:`${b.tokenPrefix}… · ${b.capabilities.length} capability · `+(b.lastUsedAt?"terpakai "+new Date(b.lastUsedAt).toLocaleString("id-ID"):"belum dipakai")+(ma(b.capabilities).length?` — token ini ${ma(b.capabilities).join(", ")}; sekarang tidak, sampai capability berbahayanya dicentang. Cabut lalu buat ulang bila memang perlu.`:""),children:e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:10},children:[e.jsx(N,{size:"sm",checked:b.enabled,onChange:s=>void ie(b,s)}),e.jsx(y,{size:"sm",variant:"ghost",leftIcon:"trash-2",onClick:()=>pe(b),children:"Cabut"})]})},b.id)),e.jsxs("div",{style:{paddingTop:14},children:[e.jsx(ee,{label:"Nama token",children:e.jsx(Y,{value:g,placeholder:"mis. agent-ci",onChange:b=>u(b.target.value),style:{width:"100%"}})}),e.jsx("div",{style:{marginTop:12,fontSize:12.5,fontWeight:600,color:"var(--text-strong)"},children:"Capability"}),e.jsxs("div",{className:"hn-grid-mobile",style:{marginTop:8,display:"grid",gridTemplateColumns:"1fr auto auto auto",gap:"6px 14px",alignItems:"center"},children:[e.jsx("div",{style:{textAlign:"right"},children:e.jsx(_e,{label:"Pilih semua capability",ariaLabel:"pilih semua capability",ids:ae,picked:k,onToggle:U})}),e.jsxs("div",{style:{fontSize:11.5,color:"var(--text-subtle)",textAlign:"center"},children:["baca",e.jsx(_e,{ariaLabel:"pilih semua baca",ids:le("read"),picked:k,onToggle:U})]}),e.jsxs("div",{style:{fontSize:11.5,color:"var(--text-subtle)",textAlign:"center"},children:["tulis",e.jsx(_e,{ariaLabel:"pilih semua tulis",ids:le("write"),picked:k,onToggle:U})]}),e.jsxs("div",{style:{fontSize:11.5,color:"var(--status-err)",textAlign:"center"},children:["berbahaya",e.jsx(_e,{ariaLabel:"pilih semua berbahaya",ids:le("danger"),picked:k,onToggle:U})]}),we.map(b=>{const B=r.find(_=>_.domain===b&&_.access==="read"),s=r.find(_=>_.domain===b&&_.access==="write"),w=r.find(_=>_.domain===b&&_.access==="danger"),L=nt.find(_=>_.domain===b);return e.jsxs(o.Fragment,{children:[e.jsxs("div",{style:{fontSize:13,color:"var(--text-muted)"},children:[e.jsxs("div",{style:{fontWeight:600,color:"var(--text-strong)"},children:[(L==null?void 0:L.label)??b,s!=null&&s.risk||w!=null&&w.risk?" ⚠":""]}),(L==null?void 0:L.desc)&&e.jsx("div",{style:{fontSize:11.5,color:"var(--text-subtle)",lineHeight:1.4,marginTop:1},children:L.desc}),w&&e.jsxs("div",{style:{fontSize:11.5,color:"var(--status-err)",lineHeight:1.4,marginTop:2},children:[w.label.replace(/^[^—]+—\s*/,"berbahaya: ")," — ",w.desc]})]}),e.jsx("div",{style:{textAlign:"center"},children:B&&e.jsx("input",{type:"checkbox","aria-label":B.id,checked:k.includes(B.id),onChange:()=>K(B.id)})}),e.jsx("div",{style:{textAlign:"center"},children:s&&e.jsx("input",{type:"checkbox","aria-label":s.id,checked:k.includes(s.id),onChange:()=>K(s.id)})}),e.jsx("div",{style:{textAlign:"center"},children:w&&e.jsx("input",{type:"checkbox","aria-label":w.id,checked:k.includes(w.id),onChange:()=>K(w.id)})})]},b)})]}),e.jsx("div",{style:{marginTop:14},children:e.jsx(y,{size:"sm",leftIcon:"plus",disabled:g.trim().length<1||A,onClick:()=>void ge(),children:"Buat token"})})]}),i]}),e.jsx(Gt,{})]})}function rn(){const[a,n]=o.useState(null),[i,r]=o.useState(!1),t=o.useCallback(()=>{j.setupStatus().then(n).catch(()=>n(null))},[]);return o.useEffect(t,[t]),a?i?e.jsx(ot,{status:a,onDone:()=>{r(!1),t()}}):e.jsx("div",{"data-testid":"setup-card",children:e.jsxs(C,{eyebrow:"setup",title:"Setup awal",children:[e.jsxs("div",{style:{fontSize:12.5,lineHeight:1.6,marginBottom:12},children:["Peruntukan: ",a.deployment==="local"?"Device saya sendiri":"Diakses orang lain",e.jsx("br",{}),a.hardening?"Hardening menyala":"Hardening mati",a.hardeningLocked?" — dipasang lewat env, tak bisa diubah dari sini":""]}),e.jsx(y,{onClick:()=>r(!0),children:"Jalankan ulang setup"})]})}):e.jsx(V,{kind:"loading",title:"Memuat setup…"})}const pa=[{key:"akun",label:"Akun",icon:"user-round"},{key:"users",label:"Users",icon:"users"},{key:"akses-klien",label:"Akses klien",icon:"user-check"},{key:"perangkat",label:"Perangkat",icon:"key-round"},{key:"kendali-jarak-jauh",label:"Kendali jarak jauh",icon:"shield"},{key:"agent",label:"Akses AI Agent",icon:"bot"},{key:"custom-agent",label:"Custom agent",icon:"bot"},{key:"aktivitas",label:"Aktivitas",icon:"activity"},{key:"konfigurasi",label:"Konfigurasi",icon:"sliders"},{key:"setup",label:"Setup awal",icon:"shield"},{key:"telegram",label:"Telegram",icon:"send"},{key:"webhook",label:"Webhook",icon:"webhook"},{key:"umum",label:"Umum",icon:"sliders-horizontal"},{key:"model",label:"Model sesi",icon:"cpu"},{key:"orkestrasi",label:"Orkestrasi",icon:"layers"},{key:"sesi",label:"Sesi",icon:"bell"}];function hn({onToast:a,me:n,onLoggedOut:i}){const r=it(),[t,c]=o.useState(null),[d,h]=o.useState(!1),f=o.useRef(Promise.resolve()),[g,u]=lt("settings","tab","akun",dt),k=st(),z=k==="mobile",[R,F]=o.useState(!1),[A,m]=o.useState(null),[x,O]=o.useState(!1),[K,U]=o.useState(null);o.useEffect(()=>{j.getCodexVersion().then(U).catch(()=>{})},[]);const[ge,pe]=o.useState(null),[ie,we]=o.useState("");o.useEffect(()=>{g==="sesi"&&(j.getMethodStatus().then(pe).catch(()=>pe(null)),j.listProjects({limit:100}).then(W=>we(H=>{var de;return H||(((de=W.items.find(Pe=>Pe.binding??Pe.repoDir))==null?void 0:de.id)??"")})).catch(()=>{}))},[g]);const le=o.useCallback(()=>{h(!1),c(null),j.getSettings().then(c).catch(()=>h(!0))},[]);o.useEffect(()=>{le()},[le]);const ae=o.useCallback(()=>{O(!1),m(null),j.getTelegramStatus().then(m).catch(()=>O(!0))},[]);o.useEffect(()=>{g==="telegram"&&ae()},[g,ae]);const[se,b]=o.useState(null),[B,s]=o.useState({}),[w,L]=o.useState(null),[_,Ge]=o.useState(!1),Ie=o.useCallback(()=>{j.getTelegramCredentials().then(W=>{b(W),s({})}).catch(()=>b(null))},[]);o.useEffect(()=>{g==="telegram"&&Ie()},[g,Ie]);function Ca(){var ea,aa,ta;if(d)return e.jsx(V,{kind:"error",title:"Gagal memuat pengaturan",hint:"Pengaturan tidak ditampilkan agar tidak menimpa nilai di server.",action:le});if(!t)return e.jsx(V,{kind:"loading",title:"Memuat pengaturan…"});const W=(E,I,D,oe)=>{const X=t;c(E),f.current=f.current.then(async()=>{const he=await j.getSettings().catch(()=>{});await j.putSettings(Ze(X,E,he))}).catch(()=>{}),I&&a&&a(I,D||"ok",oe||"check-circle-2")},H=(E,I)=>W({...t,...E},I),de=(E,I)=>D=>H({[E]:D},I+(D?" · aktif":" · nonaktif")),Pe=async E=>{if(ie)try{await j.createShell(ie,{method:E.method,agent:E.agent}),a==null||a(`Pemasangan ${E.label} · ${T[E.agent]} berjalan di Terminal`,"ok","terminal")}catch{a==null||a("Gagal membuka sesi terminal pemasangan","err","alert-triangle")}},Q=t.codex??ja,Be=t.agent??"claude";if(g==="telegram"){const E=t.telegram??Se,I=(A==null?void 0:A.readiness)??"memuat",D=S=>S==="db"?e.jsx(ne,{tone:"ok",children:"tersimpan"}):S==="env"?e.jsx(ne,{tone:"warn",children:"dari .env · deprecated"}):e.jsx(ne,{children:"belum diisi"}),oe=()=>{const S=Object.fromEntries(Object.entries(B).filter(([,Z])=>Z!==""));if(!Object.keys(S).length){a==null||a("Tak ada perubahan","info","info");return}j.putTelegramCredentials(S).then(Z=>{b(Z),s({}),L(null),a==null||a("Kredensial Telegram disimpan","ok","check-circle-2"),ae()}).catch(Z=>a==null?void 0:a(Z.message||"Gagal menyimpan","err","alert-triangle"))},X=()=>{L("sending"),j.testTelegramConnection().then(L).catch(S=>L({ok:!1,error:S.message||"Gagal menghubungi server",inbound:{ok:!1,reason:"Status jalur masuk tak terbaca — server tak menjawab.",missingCapabilities:[],polling:!1}}))},he=()=>{Ge(!1),j.deleteTelegramCredentials().then(S=>{a==null||a(S.envFallback.length?`Kredensial dihapus — ${S.envFallback.length} nilai masih datang dari .env`:"Kredensial Telegram dihapus","ok","check-circle-2"),L(null),Ie(),ae()}).catch(S=>a==null?void 0:a(S.message||"Gagal menghapus","err","alert-triangle"))};return e.jsxs(e.Fragment,{children:[e.jsxs(C,{eyebrow:"telegram",title:"Kredensial Telegram",children:[e.jsxs("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginBottom:10,lineHeight:1.5},children:["Disimpan terenkripsi di database dan berlaku langsung — tanpa mengedit ",e.jsx("code",{children:".env"}),", tanpa restart. Nilai ",e.jsx("code",{children:".env"})," lama tetap dipakai selama field-nya masih kosong."]}),se?e.jsxs(e.Fragment,{children:[se.fields.map((S,Z)=>{var Ce,$;return e.jsx(v,{title:S.label,desc:S.help,last:Z===se.fields.length-1,children:e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8},children:[D(S.source),e.jsx(Y,{"aria-label":S.label,mono:!0,type:S.kind==="secret"?"password":"text",placeholder:S.kind==="secret"?S.masked??((Ce=We(S.key))==null?void 0:Ce.example)??"belum diisi":(($=We(S.key))==null?void 0:$.example)??"belum diisi",value:B[S.key]??(S.kind==="secret"?"":S.value??""),onChange:be=>s(G=>({...G,[S.key]:be.target.value})),style:{width:300}})]})},S.key)}),e.jsx("div",{style:{display:"flex",gap:8,marginTop:12},children:e.jsx(y,{size:"sm",leftIcon:"save",onClick:oe,children:"Simpan kredensial"})})]}):e.jsx(V,{kind:"loading",compact:!0,title:"Memuat kredensial…"})]}),e.jsxs(C,{eyebrow:"uji",title:"Uji koneksi & hapus",actions:e.jsx(y,{size:"sm",variant:"ghost",leftIcon:"refresh-cw",onClick:Ie,children:"Refresh"}),children:[e.jsx(v,{title:"Test Connection",desc:"Mengirim satu pesan percobaan ke chat tujuan. Batas 10 detik — tak pernah menggantung.",children:e.jsx(y,{size:"sm",leftIcon:"send",disabled:w==="sending",onClick:X,children:"Test Connection"})}),w&&w!=="sending"&&e.jsxs("div",{style:{marginTop:10,display:"grid",gap:8},children:[w.ok?e.jsxs(ue,{tone:"ok",children:["Berhasil — bot @",w.botUsername??"?"," mengirim ke chat ",w.chatId,"."]}):e.jsx(ue,{tone:"err",children:w.error}),(ea=w.inbound)!=null&&ea.ok?e.jsx(ue,{tone:"ok",children:"Jalur masuk siap — gateway sedang long polling dan AgentToken-nya sah."}):e.jsxs(ue,{tone:"warn",children:["Jalur masuk BELUM siap: ",((aa=w.inbound)==null?void 0:aa.reason)??"status tak terbaca.",!!((ta=w.inbound)!=null&&ta.missingCapabilities.length)&&e.jsxs(e.Fragment,{children:[" Capability yang kurang: ",e.jsx("code",{children:w.inbound.missingCapabilities.join(", ")}),"."]})," ","Selama ini merah, pesan Telegram tidak akan pernah tertangkap."]})]}),e.jsx(v,{title:"Hapus kredensial",last:!0,desc:"Menghapus keempat nilai dari database. Bila .env lama masih terisi, nilainya kembali dipakai.",children:e.jsx(y,{size:"sm",variant:"danger",leftIcon:"trash-2",onClick:()=>Ge(!0),children:"Hapus kredensial"})}),e.jsx(ba,{open:_,title:"Hapus kredensial Telegram?",message:"Gateway berhenti kecuali nilai .env lama masih tersedia.",confirmLabel:"Hapus",onConfirm:he,onCancel:()=>Ge(!1)})]}),e.jsxs(C,{eyebrow:"gateway",title:"Gateway Telegram",actions:e.jsx(y,{size:"sm",variant:"ghost",leftIcon:"refresh-cw",onClick:ae,children:"Refresh"}),children:[e.jsx("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginBottom:10,lineHeight:1.5},children:"Satu private chat yang diizinkan terikat ke satu session operator Hanoman persisten. Bahasa natural tetap antarmuka utama; command hanya shortcut ke session yang sama."}),e.jsx(v,{title:"Gateway aktif",desc:"Menyalakan long polling in-process seketika. Mematikan tidak membunuh session tmux atau memory.",children:e.jsx(N,{label:"Gateway aktif",checked:E.enabled,onChange:S=>{W({...t,telegram:{...E,enabled:S}},`Gateway Telegram · ${S?"aktif":"nonaktif"}`)}})}),e.jsx(v,{title:"Kirim progress ringkas",desc:"Hanya progress eksplisit dan fakta status Hanoman; layar PTY/reasoning tidak pernah diteruskan.",children:e.jsx(N,{label:"Kirim progress ringkas",checked:E.progress,onChange:S=>{W({...t,telegram:{...E,progress:S}},`Progress Telegram · ${S?"aktif":"nonaktif"}`)}})}),x?e.jsx(V,{kind:"error",compact:!0,title:"Gagal membaca status Telegram",action:ae}):A?e.jsxs(e.Fragment,{children:[e.jsx(v,{title:`Readiness · ${I}`,desc:A.running?"Long poll aktif.":A.lastError??"Gateway belum polling.",children:e.jsx("span",{style:{fontFamily:"var(--font-mono)",fontSize:12},children:A.botUsername?`@${A.botUsername}`:"bot belum terverifikasi"})}),e.jsx(v,{title:"Allowlist",last:!A.missingCapabilities.length,desc:`${A.allowlistCount} Telegram numeric user id diizinkan.`,children:e.jsx("span",{children:A.configured?"kredensial lengkap":"kredensial belum lengkap"})}),A.missingCapabilities.length>0&&e.jsx(v,{title:"Capability kurang",last:!0,desc:A.missingCapabilities.join(", ")})]}):e.jsx(V,{kind:"loading",compact:!0,title:"Memuat status Telegram…"}),e.jsxs("div",{style:{marginTop:14,fontSize:12.5,color:"var(--text-muted)",lineHeight:1.7},children:[e.jsx("b",{children:"Onboarding"}),e.jsxs("ol",{style:{margin:"8px 0 0",paddingLeft:20},children:[e.jsx("li",{children:"Buat satu bot private-chat lewat BotFather, salin token-nya."}),e.jsx("li",{children:"Di Akses AI Agent, aktifkan master switch dan buat AgentToken dengan capability yang ditampilkan status."}),e.jsx("li",{children:"Isi keempat field di kartu Kredensial lalu Simpan."}),e.jsx("li",{children:"Tekan Test Connection sampai hijau."}),e.jsxs("li",{children:["Nyalakan gateway di atas dan kirim ",e.jsx("code",{children:"/status"})," dari Telegram."]})]})]})]})]})}if(g==="umum")return e.jsxs(e.Fragment,{children:[e.jsxs(C,{eyebrow:"general",title:"Umum",children:[e.jsx(v,{title:"Full-auto sebagai default",desc:"Run baru jalan sendiri sampai selesai. Manusia tetap bisa steer / interupsi kapan pun.",children:e.jsx(N,{checked:t.autoDefault,onChange:de("autoDefault","Full-auto default")})}),e.jsx(v,{title:"Auto-scaffold doc index",last:!0,desc:"Project from-scratch otomatis di-scaffold doc index-nya setelah objective terkunci.",children:e.jsx(N,{checked:t.autoScaffold,onChange:de("autoScaffold","Auto-scaffold")})})]}),e.jsxs("div",{style:{display:"flex",alignItems:"center",justifyContent:"space-between",gap:12,padding:"4px 2px",color:"var(--text-subtle)",fontSize:12,fontFamily:"var(--font-mono)"},children:[e.jsx("span",{children:"hanoman · nafanesia.id"}),e.jsx(y,{size:"sm",variant:"ghost",leftIcon:"rotate-ccw",onClick:()=>W(JSON.parse(JSON.stringify(Xt)),"Pengaturan dikembalikan ke default","warn","rotate-ccw"),children:"Reset ke default"})]})]});if(g==="orkestrasi")return e.jsx(Yt,{orchestration:t.orchestration,onChange:(E,I)=>H({orchestration:E},I)});if(g==="model"){const E=l=>{var P;const p=(K==null?void 0:K.version)??null;return jt(l,p)?e.jsxs("div",{"data-testid":"codex-version-note",style:{fontSize:12,lineHeight:1.5,marginBottom:10,padding:"8px 10px",borderRadius:8,background:"var(--warn-bg, #fdf6e3)",color:"var(--text-muted)"},children:["Codex CLI terpasang ",e.jsx("b",{children:p}),", sedangkan ",e.jsx("b",{children:l})," butuh ",e.jsx("b",{children:(P=yt(l))==null?void 0:P.minClient}),". Sesi tetap boleh dijalankan, tapi modelnya belum tentu dikenali CLI ini. Perbarui dengan ",e.jsx("code",{children:"npm i -g @openai/codex@latest"}),"."]}):null},I=l=>wa(St,l),D=t.conflict??Sa,oe=(l,p)=>H({conflict:{...D,...l}},p),X=Be==="codex"?{agent:"codex",model:Q.model,effort:Q.effort}:{agent:"claude",model:t.model,effort:t.effort},he=t.lead??Re,S=he.engine??Re.engine,Z=async(l,p)=>{const P=he;c({...t,lead:{...he,engine:{...S,...l}}});try{const re=await j.getLeadConfig(),Le=await j.putLeadConfig({...re,engine:{...re.engine??Re.engine,...l}});c(xe=>xe&&{...xe,lead:Le}),a==null||a(p,"ok","check-circle-2")}catch{c(re=>re&&{...re,lead:P}),a==null||a("Gagal menyimpan setelan lead","err","alert-triangle")}},Ce=t.telegram??Se,$=Ce.engine??Se.engine,be=async(l,p)=>{const P=t;c({...t,telegram:{...Ce,engine:{...$,...l}}});try{const re=await j.getSettings(),Le=re.telegram??Se,xe={...re,telegram:{...Le,engine:{...Le.engine??Se.engine,...l}}},Aa=await j.putSettings(xe);c(Aa??xe),a==null||a(p,"ok","check-circle-2")}catch{c(P),a==null||a("Gagal menyimpan setelan operator Telegram","err","alert-triangle")}},G=t.changelog??va,Me=(l,p)=>H({changelog:{...G,...l}},p),q=t.portalChat??ya,ke=(l,p)=>H({portalChat:{...q,...l}},p),Ue=(l,p)=>{const P=Math.floor(Number(l));return Number.isFinite(P)&&P>=0?P:p};return e.jsxs(e.Fragment,{children:[e.jsxs(C,{eyebrow:"agen",title:"Agen sesi",children:[e.jsx("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginBottom:10,lineHeight:1.5},children:"Mesin yang menjalankan sesi baru. Perilaku sesi identik — worktree terisolasi, fase, stage, review, integrate. Yang berbeda hanya CLI-nya, dan karenanya katalog model/effort-nya. Indikator limit hanya membaca kuota Anthropic, jadi sesi codex tak muncul di sana."}),e.jsx(v,{title:"Agen default",last:!0,desc:"Sesi backlog masih bisa memilih agen lain saat Start.",children:e.jsx(M,{size:"sm","aria-label":"Agen default",value:Be,style:{width:190},options:[{value:"claude",label:T.claude},{value:"codex",label:T.codex}],onChange:l=>H({agent:l.target.value},"Agen → "+l.target.value)})})]}),e.jsxs(C,{eyebrow:"model",title:"Model sesi — default global",children:[e.jsxs("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginBottom:10,lineHeight:1.5},children:["Default untuk sesi baru; bisa di-override per sesi saat ",e.jsx("b",{children:"Start"}),". Di terminal, ",e.jsx("code",{children:"/model"}),"mengubahnya kapan saja. Dengan orkestrasi aktif, model ini menjadi model ",e.jsx("b",{children:"orchestrator"}),"; model & effort tiap fase diatur di ",e.jsx("b",{children:"tab Orkestrasi"}),". Yang benar-benar dipakai adalah blok milik agen terpilih di atas — yang satunya tersimpan, menunggu giliran."]}),e.jsxs("div",{"data-testid":"agent-group-claude",children:[e.jsxs("div",{style:{padding:"8px 0",color:"var(--text-muted)",fontSize:12},children:["Katalog model diperiksa otomatis setiap 5 menit.",Object.entries(r.providers).map(([l,p])=>e.jsxs("div",{children:[l,": ",p.source==="bundled"?"daftar bawaan":p.source==="cache"?"cache terakhir":"dari CLI",p.checkedAt?" · diperiksa "+new Date(p.checkedAt).toLocaleTimeString():"",p.error?" · "+p.error:""]},l))]}),e.jsx(ha,{id:"claude",label:T.claude,active:Be==="claude"}),e.jsx(v,{title:"Model",desc:"Diteruskan apa adanya ke `claude --model`.",children:e.jsx(M,{size:"sm","aria-label":"Model claude",value:t.model,options:ye(t.model),style:{width:190},onChange:l=>H({model:l.target.value,...ve(l.target.value,t.effort)},"Model claude → "+l.target.value)})}),e.jsx(v,{title:"Effort",last:!0,desc:"Anggaran berpikir per giliran (`claude --effort`).",children:e.jsx(M,{size:"sm","aria-label":"Effort claude",value:t.effort,options:je(t.model),style:{width:130},onChange:l=>H({effort:l.target.value},"Effort claude → "+l.target.value)})})]}),e.jsxs("div",{"data-testid":"agent-group-codex",style:{borderTop:"1px solid var(--border-hair)",marginTop:6},children:[e.jsx(ha,{id:"codex",label:T.codex,active:Be==="codex"}),E(Q.model),e.jsx(v,{title:"Model",desc:"Diteruskan apa adanya ke `codex -m`.",children:e.jsx(M,{size:"sm","aria-label":"Model codex",value:Q.model,style:{width:190},options:I(Q.model),onChange:l=>{const p=l.target.value;H({codex:{model:p,effort:te(p,Q.effort)}},"Model codex → "+p)}})}),e.jsx(v,{title:"Effort",last:!0,desc:"Diteruskan ke `codex -c model_reasoning_effort`.",children:e.jsx(M,{size:"sm","aria-label":"Effort codex",value:Q.effort,style:{width:130},options:Ee(Q.model).map(l=>({value:l,label:l})),onChange:l=>H({codex:{...Q,effort:l.target.value}},"Effort codex → "+l.target.value)})})]})]}),e.jsxs(C,{eyebrow:"konflik",title:"Konflik rebase & merge",children:[e.jsx("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginBottom:10,lineHeight:1.5},children:"Saat rebase/merge berkonflik, hanoman menyerahkan worktree-nya ke satu sesi agen untuk dibereskan. Pekerjaannya sempit dan tak berfase, jadi ia boleh memakai model & effort yang berbeda dari sesi kerja. Berlaku untuk ketiga pintu integrasi: backlog, git graph, dan PRD."}),e.jsx(v,{title:"Pakai setelan sendiri",desc:"Mati = ikut default global di atas. Hidup = sesi konflik memakai pilihan di bawah.",children:e.jsx(N,{"aria-label":"Override agen konflik",checked:D.enabled,onChange:l=>oe({enabled:l},"Setelan konflik"+(l?" · aktif":" · ikut default global"))})}),D.enabled?e.jsxs(e.Fragment,{children:[e.jsx(v,{title:"Agen",desc:"Mesin yang membereskan konflik. Bisa beda dari agen sesi kerja.",children:e.jsx(M,{size:"sm","aria-label":"Agen konflik",value:D.agent,style:{width:190},options:[{value:"claude",label:T.claude},{value:"codex",label:T.codex}],onChange:l=>{const p=l.target.value,P=p==="codex"?Q:{model:t.model,effort:t.effort};oe({agent:p,model:P.model,effort:p==="codex"?te(P.model,P.effort):P.effort},"Agen konflik → "+p)}})}),D.agent==="codex"&&E(D.model),e.jsx(v,{title:"Model",children:e.jsx(M,{size:"sm","aria-label":"Model konflik",value:D.model,style:{width:190},options:D.agent==="codex"?I(D.model):ye(D.model),onChange:l=>{const p=l.target.value;oe({model:p,...D.agent==="codex"?{effort:te(p,D.effort)}:ve(p,D.effort)},"Model konflik → "+p)}})}),e.jsx(v,{title:"Effort",last:!0,desc:"Konflik biasanya mekanis — effort rendah sering cukup.",children:e.jsx(M,{size:"sm","aria-label":"Effort konflik",value:D.effort,style:{width:130},options:D.agent==="codex"?Ee(D.model).map(l=>({value:l,label:l})):je(D.model),onChange:l=>oe({effort:l.target.value},"Effort konflik → "+l.target.value)})})]}):e.jsxs("div",{"data-testid":"conflict-inherited",style:{fontSize:12.5,color:"var(--text-muted)",padding:"12px 0 2px",lineHeight:1.5},children:["Sesi konflik memakai default global: ",e.jsx("b",{children:T[X.agent]})," ·"," ",e.jsx("code",{children:X.model})," · ",e.jsx("code",{children:X.effort}),"."]})]}),e.jsxs(C,{eyebrow:"lead",title:"Agen hanoman-lead",children:[e.jsxs("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginBottom:10,lineHeight:1.5},children:["Mesin yang menjalankan agen pemimpin — panggilan sekali-jalan non-interaktif yang membaca docs, plan, kode, dan riwayat git sebelum memutuskan. Berlaku untuk ketiga pintu lead (kontrak, deteksi otomatis, denyut) dan dipakai putusan berikutnya, tanpa restart. Rem darurat, denyut, dan opt-in per project tetap diurus di layar ",e.jsx("b",{children:"Lead"}),"."]}),e.jsx(v,{title:"Pakai setelan sendiri",desc:"Mati = ikut default global di atas. Hidup = lead memakai pilihan di bawah.",children:e.jsx(N,{"aria-label":"Override agen lead",checked:S.enabled,onChange:l=>Z({enabled:l},"Setelan lead"+(l?" · aktif":" · ikut default global"))})}),S.enabled?e.jsxs(e.Fragment,{children:[e.jsx(v,{title:"Runtime",desc:"Mesin yang menjalankan lead. Bisa beda dari agen sesi kerja.",children:e.jsx(M,{size:"sm","aria-label":"Runtime lead",value:S.agent,style:{width:190},options:[{value:"claude",label:T.claude},{value:"codex",label:T.codex}],onChange:l=>{const p=l.target.value,P=p==="codex"?Q:{model:t.model,effort:t.effort};Z({agent:p,model:P.model,effort:p==="codex"?te(P.model,P.effort):P.effort},"Runtime lead → "+p)}})}),S.agent==="codex"&&E(S.model),e.jsx(v,{title:"Model",children:e.jsx(M,{size:"sm","aria-label":"Model lead",value:S.model,style:{width:190},options:S.agent==="codex"?I(S.model):ye(S.model),onChange:l=>{const p=l.target.value;Z({model:p,...S.agent==="codex"?{effort:te(p,S.effort)}:ve(p,S.effort)},"Model lead → "+p)}})}),e.jsx(v,{title:"Effort",last:!0,desc:"Putusan lead menuntut membaca bukti — SoT, ADR, plan, kode, riwayat git. Effort rendah memangkas kedalamannya.",children:e.jsx(M,{size:"sm","aria-label":"Effort lead",value:S.effort,style:{width:130},options:S.agent==="codex"?Ee(S.model).map(l=>({value:l,label:l})):je(S.model),onChange:l=>Z({effort:l.target.value},"Effort lead → "+l.target.value)})})]}):e.jsxs("div",{"data-testid":"lead-engine-inherited",style:{fontSize:12.5,color:"var(--text-muted)",padding:"12px 0 2px",lineHeight:1.5},children:["hanoman-lead memakai default global: ",e.jsx("b",{children:T[X.agent]})," ·"," ",e.jsx("code",{children:X.model})," · ",e.jsx("code",{children:X.effort}),"."]})]}),e.jsxs(C,{eyebrow:"telegram",title:"Agen operator Telegram",children:[e.jsxs("div",{"data-testid":"telegram-engine-desc",style:{fontSize:12.5,color:"var(--text-muted)",marginBottom:10,lineHeight:1.5},children:["Mesin yang menjalankan sesi operator Telegram — satu sesi persisten per chat, yang membaca API hanoman lalu merangkum. Berlaku untuk sesi operator ",e.jsx("b",{children:"berikutnya"})," yang dibuat; sesi yang sedang jalan tetap memakai setelan lamanya (satu proses, satu model seumur hidup) dan tidak di-restart diam-diam — tutup dari chat dengan ",e.jsx("code",{children:"/engine restart"}),". Setelan yang sama juga bisa diubah dari chat: ",e.jsx("code",{children:"/engine"}),", ",e.jsx("code",{children:"/runtime"}),","," ",e.jsx("code",{children:"/model"}),", ",e.jsx("code",{children:"/effort"}),". Berlaku global untuk semua chat."]}),e.jsx(v,{title:"Pakai setelan sendiri",desc:"Mati = ikut default global di atas. Hidup = sesi operator Telegram memakai pilihan di bawah.",children:e.jsx(N,{"aria-label":"Override agen Telegram",checked:$.enabled,onChange:l=>be({enabled:l},"Setelan operator Telegram"+(l?" · aktif":" · ikut default global"))})}),$.enabled?e.jsxs(e.Fragment,{children:[e.jsx(v,{title:"Runtime",desc:"Mesin yang menjalankan sesi operator. Bisa beda dari agen sesi kerja.",children:e.jsx(M,{size:"sm","aria-label":"Runtime Telegram",value:$.agent,style:{width:190},options:[{value:"claude",label:T.claude},{value:"codex",label:T.codex}],onChange:l=>{const p=l.target.value,P=p==="codex"?Q:{model:t.model,effort:t.effort};be({agent:p,model:P.model,effort:p==="codex"?te(P.model,P.effort):P.effort},"Runtime operator Telegram → "+p)}})}),$.agent==="codex"&&E($.model),e.jsx(v,{title:"Model",children:e.jsx(M,{size:"sm","aria-label":"Model Telegram",value:$.model,style:{width:190},options:$.agent==="codex"?I($.model):ye($.model),onChange:l=>{const p=l.target.value;be({model:p,...$.agent==="codex"?{effort:te(p,$.effort)}:ve(p,$.effort)},"Model operator Telegram → "+p)}})}),e.jsx(v,{title:"Effort",last:!0,desc:"Operator Telegram jarang menulis kode — effort rendah memangkas latensi balasan secara langsung.",children:e.jsx(M,{size:"sm","aria-label":"Effort Telegram",value:$.effort,style:{width:130},options:$.agent==="codex"?Ee($.model).map(l=>({value:l,label:l})):je($.model),onChange:l=>be({effort:l.target.value},"Effort operator Telegram → "+l.target.value)})})]}):e.jsxs("div",{"data-testid":"telegram-engine-inherited",style:{fontSize:12.5,color:"var(--text-muted)",padding:"12px 0 2px",lineHeight:1.5},children:["Sesi operator Telegram memakai default global: ",e.jsx("b",{children:T[X.agent]})," ·"," ",e.jsx("code",{children:X.model})," · ",e.jsx("code",{children:X.effort}),"."]})]}),e.jsxs(C,{eyebrow:"changelog",title:"Agen changelog",children:[e.jsxs("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginBottom:10,lineHeight:1.5},children:["Mesin yang menulis narasi changelog per project — panggilan sekali-jalan non-interaktif yang merangkum backlog selesai, rentang commit, atau isi sebuah rilis menjadi teks pendek berorientasi pemakai. Berlaku pada pembangkitan ",e.jsx("b",{children:"berikutnya"}),", tanpa restart. Agen yang gagal tak menggagalkan changelog: barisnya tetap lahir sebagai draf ringkas ber-catatan."]}),e.jsx(v,{title:"Pakai setelan sendiri",desc:"Mati = ikut default global di atas. Hidup = pembuat changelog memakai pilihan di bawah.",children:e.jsx(N,{"aria-label":"Override agen changelog",checked:G.enabled,onChange:l=>Me({enabled:l},"Setelan changelog"+(l?" · aktif":" · ikut default global"))})}),G.enabled?e.jsxs(e.Fragment,{children:[e.jsx(v,{title:"Runtime",desc:"Mesin yang menulis changelog. Bisa beda dari agen sesi kerja.",children:e.jsx(M,{size:"sm","aria-label":"Runtime changelog",value:G.agent,style:{width:190},options:[{value:"claude",label:T.claude},{value:"codex",label:T.codex}],onChange:l=>{const p=l.target.value,P=p==="codex"?Q:{model:t.model,effort:t.effort};Me({agent:p,model:P.model,effort:p==="codex"?te(P.model,P.effort):P.effort},"Runtime changelog → "+p)}})}),G.agent==="codex"&&E(G.model),e.jsx(v,{title:"Model",children:e.jsx(M,{size:"sm","aria-label":"Model changelog",value:G.model,style:{width:190},options:G.agent==="codex"?I(G.model):ye(G.model),onChange:l=>{const p=l.target.value;Me({model:p,...G.agent==="codex"?{effort:te(p,G.effort)}:ve(p,G.effort)},"Model changelog → "+p)}})}),e.jsx(v,{title:"Effort",last:!0,desc:"Merangkum judul jadi prosa pendek — effort rendah biasanya cukup dan memangkas ongkos setiap pembangkitan.",children:e.jsx(M,{size:"sm","aria-label":"Effort changelog",value:G.effort,style:{width:130},options:G.agent==="codex"?Ee(G.model).map(l=>({value:l,label:l})):je(G.model),onChange:l=>Me({effort:l.target.value},"Effort changelog → "+l.target.value)})})]}):e.jsxs("div",{"data-testid":"changelog-engine-inherited",style:{fontSize:12.5,color:"var(--text-muted)",padding:"12px 0 2px",lineHeight:1.5},children:["Pembuat changelog memakai default global: ",e.jsx("b",{children:T[X.agent]})," ·"," ",e.jsx("code",{children:X.model})," · ",e.jsx("code",{children:X.effort}),"."]})]}),e.jsxs(C,{eyebrow:"portal",title:"Obrolan portal klien",children:[e.jsxs("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginBottom:10,lineHeight:1.5},children:["Permukaan obrolan di portal klien: klien memilih ",e.jsx("b",{children:"Brainstorming"})," (digali sampai jelas, keluarannya PRD draft untuk Anda review) atau ",e.jsx("b",{children:"Bertanya"})," (dijawab langsung dari dokumen project). Jatah berlaku ",e.jsx("b",{children:"per project"})," dan dihitung bulanan; brainstorming dan pertanyaan punya jatah masing-masing. Mati = tab Obrolan tak muncul di portal."]}),e.jsx(v,{title:"Aktifkan obrolan",desc:"Mati = klien hanya punya Pekerjaan & Help desk, persis seperti sebelumnya.",children:e.jsx(N,{"aria-label":"Aktifkan obrolan portal",checked:q.enabled,onChange:l=>ke({enabled:l},"Obrolan portal"+(l?" · aktif":" · mati"))})}),q.enabled&&e.jsxs(e.Fragment,{children:[e.jsx(v,{title:"Jatah brainstorming",desc:"Berapa sesi brainstorming per project tiap bulan. 0 = tertutup.",children:e.jsx(Y,{"aria-label":"Jatah brainstorming",type:"number",min:0,max:1e3,placeholder:"mis. 3",style:{width:110},defaultValue:q.brainstormPerMonth,onBlur:l=>ke({brainstormPerMonth:Ue(l.target.value,q.brainstormPerMonth)},"Jatah brainstorming → "+l.target.value)})}),e.jsx(v,{title:"Jatah pertanyaan",desc:"Berapa sesi tanya-jawab per project tiap bulan. 0 = tertutup.",children:e.jsx(Y,{"aria-label":"Jatah pertanyaan",type:"number",min:0,max:1e4,placeholder:"mis. 30",style:{width:110},defaultValue:q.askPerMonth,onBlur:l=>ke({askPerMonth:Ue(l.target.value,q.askPerMonth)},"Jatah pertanyaan → "+l.target.value)})}),e.jsx(v,{title:"Model",desc:"Mesin obrolan selalu Claude — gerbang tool yang menjaganya tak ada di runtime lain.",children:e.jsx(M,{size:"sm","aria-label":"Model obrolan portal",value:q.model,style:{width:190},options:ye(q.model),onChange:l=>ke({model:l.target.value,...ve(l.target.value,q.effort)},"Model obrolan portal → "+l.target.value)})}),e.jsx(v,{title:"Effort",children:e.jsx(M,{size:"sm","aria-label":"Effort obrolan portal",value:q.effort,style:{width:130},options:je(q.model),onChange:l=>ke({effort:l.target.value},"Effort obrolan portal → "+l.target.value)})}),e.jsx(v,{title:"Batas waktu satu jawaban",last:!0,desc:"Detik. Lewat dari ini klien menerima kalimat 'coba lagi sebentar', bukan layar menggantung.",children:e.jsx(Y,{"aria-label":"Batas waktu obrolan portal",type:"number",min:10,max:900,placeholder:"mis. 120",style:{width:110},defaultValue:q.timeoutSec,onBlur:l=>ke({timeoutSec:Ue(l.target.value,q.timeoutSec)},"Batas waktu obrolan → "+l.target.value)})})]})]})]})}return e.jsxs(e.Fragment,{children:[e.jsxs(C,{eyebrow:"sesi",title:"Sesi & notifikasi",children:[e.jsx(v,{title:"Notifikasi backlog selesai",desc:"Toast + sound saat sebuah backlog mencapai stage done. Daftar lonceng tetap terisi meski dimatikan.",children:e.jsx(N,{checked:t.notifyDone,onChange:de("notifyDone","Notifikasi backlog selesai")})}),e.jsx(v,{title:"Sound notifikasi",desc:"Durasi nada saat backlog selesai.",children:e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8},children:[e.jsx(M,{size:"sm",value:t.notifySound,options:ga,style:{width:160},onChange:E=>H({notifySound:E.target.value},"Sound → "+E.target.value)}),e.jsx(y,{size:"sm",variant:"ghost",leftIcon:"volume-2",disabled:t.notifySound==="off",onClick:()=>ia(t.notifySound),children:"Preview"})]})}),e.jsx(v,{title:"Notifikasi butuh keputusan",desc:"Toast + sound saat sesi Claude berhenti menunggu keputusanmu. Nada sengaja beda dari selesai.",children:e.jsx(N,{checked:t.notifyDecision,onChange:de("notifyDecision","Notifikasi keputusan")})}),e.jsx(v,{title:"Sound keputusan",desc:"Nada saat sebuah sesi menunggu keputusan.",children:e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8},children:[e.jsx(M,{size:"sm",value:t.notifyDecisionSound,options:ga,style:{width:160},onChange:E=>H({notifyDecisionSound:E.target.value},"Sound keputusan → "+E.target.value)}),e.jsx(y,{size:"sm",variant:"ghost",leftIcon:"volume-2",disabled:t.notifyDecisionSound==="off",onClick:()=>ia(t.notifyDecisionSound),children:"Preview"})]})}),e.jsx(v,{title:"Notifikasi saat sesi gagal",last:!0,desc:"Kirim notifikasi ketika sesi Claude Code berakhir dengan error.",children:e.jsx(N,{checked:t.notifyFail,onChange:de("notifyFail","Notifikasi gagal")})})]}),e.jsxs(C,{eyebrow:"goal",title:"Mode goal — sesi backlog",children:[e.jsxs("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginBottom:10,lineHeight:1.5},children:["Sesi backlog lahir dengan gate ",e.jsx("code",{children:"Stop"}),": ia menolak berhenti sampai kondisinya terbukti di transkrip. Interupsi manusia (",e.jsx("code",{children:"Esc"}),") tetap bekerja; melepas gate sepenuhnya = hentikan sesinya. Sesi scheduler mengikuti setelan ini."]}),e.jsx(v,{title:"Aktif sebagai default",desc:"Sesi backlog baru lahir dengan mode goal. Masih bisa dimatikan per sesi saat Start.",children:e.jsx(N,{"aria-label":"Mode goal default",checked:t.goal.enabled,onChange:E=>H({goal:{...t.goal,enabled:E}},"Mode goal"+(E?" · aktif":" · nonaktif"))})}),e.jsx(v,{title:"Kondisi (template global)",last:!0,desc:"Kosong = kondisi bawaan hanoman: semua fase tercatat di phase file, plan tak menyisakan task, push sukses.",children:e.jsx("div",{style:{width:320},children:e.jsx(kt,{value:t.goal.condition,rows:4,mono:!0,"aria-label":"Kondisi mode goal",placeholder:"Kosong = kondisi bawaan hanoman · mis. semua fase tercatat & plan tanpa - [ ]",onChange:E=>W({...t,goal:{...t.goal,condition:E.target.value}})})})})]}),e.jsxs(C,{eyebrow:"verifikasi",title:"Scope verifikasi — sesi backlog",children:[e.jsxs("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginBottom:10,lineHeight:1.5},children:["Beberapa sesi berjalan bersamaan di mesin ini. ",e.jsx("b",{children:"Hanya yang berubah"})," menyuruh sesi menguji berkas yang benar-benar ia sentuh (",e.jsx("code",{children:"vitest --changed"}),", typecheck per paket, lint per berkas) alih-alih seluruh project — RAM & CPU tetap tersisa untuk sesi lain. Suite penuh, lint penuh, dan build penuh tetap dijalankan manusia sebelum merge."]}),e.jsx(v,{title:"Scope default",last:!0,desc:"Sesi backlog baru lahir dengan scope ini. Masih bisa diubah per sesi saat Start.",children:e.jsx(M,{size:"sm","aria-label":"Scope verifikasi default",value:t.verifyScope??"changed",style:{width:220},options:[{value:"changed",label:"Hanya yang berubah"},{value:"full",label:"Seluruh project"}],onChange:E=>H({verifyScope:E.target.value},"Scope verifikasi → "+E.target.value)})})]}),e.jsxs(C,{eyebrow:"metode",title:"Metode workflow — sesi backlog",children:[e.jsxs("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginBottom:10,lineHeight:1.5},children:["Metode menentukan ",e.jsx("b",{children:"skill mana yang dimuat di tiap fase"})," dan ",e.jsx("b",{children:"di direktori mana plan & spec ditulis"}),". Fase-fasenya sendiri tak berubah. Gerbang plan memindai direktori SEMUA metode, jadi item yang berpindah metode tak pernah lolos lewat direktori kosong."]}),e.jsx(v,{title:"Metode default",last:!0,desc:"Sesi backlog baru lahir dengan metode ini. Masih bisa diubah per sesi saat Start.",children:e.jsx(M,{size:"sm","aria-label":"Metode default",style:{width:220},value:xt(t.method).id,options:la.map(E=>({value:E,label:sa[E].label})),onChange:E=>H({method:E.target.value},"Metode → "+E.target.value)})}),ge&&e.jsxs("div",{"data-testid":"method-status",style:{marginTop:4,display:"grid",gap:10},children:[la.map(E=>e.jsxs("div",{style:{display:"grid",gap:6},children:[e.jsx("div",{style:{fontSize:12.5,fontWeight:600},children:sa[E].label}),ge.methods.filter(I=>I.method===E).map(I=>e.jsxs("div",{"data-testid":`method-status-${I.method}-${I.agent}`,style:{display:"flex",alignItems:"flex-start",gap:8,fontSize:12,lineHeight:1.5},children:[e.jsx(ne,{tone:I.ready?"ok":"warn",children:I.ready?"siap":"belum siap"}),e.jsxs("div",{style:{flex:"1 1 auto",minWidth:0},children:[e.jsx("b",{children:T[I.agent]}),!I.ready&&e.jsxs("div",{style:{color:"var(--text-muted)",overflowWrap:"anywhere"},children:[I.missingPackages.length>0&&e.jsxs("div",{children:["paket kurang: ",e.jsx("code",{children:I.missingPackages.join(" · ")})]}),I.missingSkills.length>0&&e.jsxs("div",{children:["skill kurang: ",e.jsx("code",{children:I.missingSkills.join(" · ")})]})]})]}),!I.ready&&e.jsx(y,{size:"sm",variant:"ghost",leftIcon:"download",disabled:!ie,onClick:()=>Pe(I),children:"Pasang"})]},I.agent))]},E)),e.jsx("div",{style:{fontSize:12,color:"var(--text-muted)",lineHeight:1.5},children:ie?e.jsxs(e.Fragment,{children:["Pemasangan berjalan di ",e.jsx("b",{children:"sesi terminal"})," project ",e.jsx("code",{children:ie})," — outputnya bisa ditonton di Terminal. hanoman sendiri tak pernah memasang apa pun."]}):e.jsx(e.Fragment,{children:"Pemasangan butuh satu project yang sudah di-bind ke checkout lokal: perintahnya dijalankan di sesi terminal project itu."})})]})]})]})}const Ea=g==="setup"?e.jsx(rn,{}):g==="akun"?e.jsx(qt,{me:n,onLoggedOut:i,onToast:a}):g==="users"?e.jsx(Qt,{me:n,onToast:a}):g==="akses-klien"?e.jsx(At,{}):g==="perangkat"?e.jsx(Zt,{onToast:a}):g==="kendali-jarak-jauh"?e.jsx(It,{onToast:a}):g==="agent"?e.jsx(sn,{onToast:a}):g==="custom-agent"?e.jsxs(C,{eyebrow:"agen",title:"Custom agent — global",children:[e.jsxs("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginBottom:12,lineHeight:1.5},children:["Persona yang tersedia untuk ",e.jsx("b",{children:"setiap sesi baru"})," di semua project. Sesi ",e.jsx("b",{children:"claude"}),"dan ",e.jsx("b",{children:"codex"})," menerima child native dengan konteks terpisah. Agen boleh saling memanggil lewat ",e.jsx("i",{children:"Mention"}),", dan grafnya wajib asiklik — agen tanpa mention tak diberi alat delegasi sama sekali."]}),e.jsx(rt,{projectId:null,runtime:t==null?void 0:t.agent,onToast:a})]}):g==="aktivitas"?e.jsx(en,{onToast:a}):g==="konfigurasi"?e.jsx(tn,{onToast:a}):g==="webhook"?R?e.jsx(Rt,{onBack:()=>F(!1)}):e.jsx(Lt,{onToast:a,onOpenDocs:()=>F(!0)}):Ca();return e.jsxs("div",{className:"hn-settings-layout","data-layout":k,style:{display:"grid",gridTemplateColumns:z?"minmax(0, 1fr)":k==="tablet"?"160px minmax(0, 1fr)":"196px minmax(0, 1fr)",gap:k==="tablet"?16:24,alignItems:"start",maxWidth:920},children:[z?e.jsx(M,{"aria-label":"Bagian pengaturan",value:g,onChange:W=>u(W.target.value),options:pa.map(W=>({value:W.key,label:W.label}))}):e.jsx("nav",{"aria-label":"Navigasi pengaturan",style:{display:"flex",flexDirection:"column",gap:2,position:"sticky",top:0},children:pa.map(W=>{const H=W.key===g;return e.jsxs("button",{"aria-current":H?"page":void 0,onClick:()=>u(W.key),style:{display:"flex",alignItems:"center",gap:10,width:"100%",padding:"9px 12px",border:"none",cursor:"pointer",textAlign:"left",borderRadius:"var(--radius-sm)",background:H?"var(--brass-100)":"transparent",color:H?"var(--brass-700)":"var(--text-muted)",fontWeight:H?600:500,fontFamily:"var(--font-ui)",fontSize:13.5},children:[e.jsx(Fe,{name:W.icon,size:16,color:H?"var(--brass-700)":"var(--text-subtle)"}),W.label]},W.key)})}),e.jsx("div",{style:{display:"flex",flexDirection:"column",gap:20,minWidth:0},children:Ea})]})}export{sn as AgentAccessPanel,Zt as DeviceTokensPanel,hn as SettingsScreen};
|
|
62
|
+
claude mcp add hanoman --env HANOMAN_HOST=${n} --env HANOMAN_AGENT_TOKEN=hnm_agt_… -- hanoman mcp${r?" "+r:""}`:a==="cursor"?c.replace('"mcpServers"','"servers"'):c}const Gt={margin:0,padding:12,fontSize:12,lineHeight:1.5,overflowX:"auto",background:"var(--bone-100)",border:"1px solid var(--ink-200)",borderRadius:"var(--radius-sm)",whiteSpace:"pre"},Ae={fontSize:12.5,color:"var(--text-muted)",lineHeight:1.55,marginBottom:10},fe={padding:"6px 10px 6px 0",borderBottom:"1px solid var(--ink-100)",verticalAlign:"top",fontSize:12.5};function Ut(){const[a,n]=o.useState("claude-code"),[i,r]=o.useState("default"),[t,c]=o.useState(!1),d=typeof window>"u"?"http://localhost:8787":window.location.origin,h=$t(a,d,i),f=ca.find(u=>u.id===a),g=()=>{var u;(u=navigator.clipboard)==null||u.writeText(h).then(()=>{c(!0),setTimeout(()=>c(!1),1500)},()=>{})};return e.jsxs(e.Fragment,{children:[e.jsxs(C,{eyebrow:"mcp",title:"MCP server",children:[e.jsxs("div",{style:Ae,children:["Agen AI mana pun yang berbicara MCP bisa memakai hanoman lewat ",e.jsx("code",{children:"hanoman mcp"})," — tanpa pembungkus khusus per-klien. Ia memakai ",e.jsx("b",{children:"agent token yang sama"})," dengan REST dan capability yang sama; buat tokennya di kartu di atas, lalu tempel di konfigurasi klien."," ",e.jsxs("b",{children:["Skema tool versi ",At,"."]})]}),e.jsxs("div",{style:Ae,children:["Prasyarat: ",e.jsx("code",{children:"npm i -g hanoman"})," di mesin tempat klien AI-nya jalan. Host di bawah diisi otomatis dengan instance ini — ",e.jsx("b",{children:"agent token diterbitkan per-instance"}),", jadi token dari instance lain akan selalu ditolak 401 di sini."]}),e.jsxs("div",{style:Ae,children:["Tool berbahaya — membuka sesi agen di worktree, perintah VPS, merge/rebase, penghapusan backlog, perubahan stage — ",e.jsx("b",{children:"hanya muncul"})," pada tingkat ",e.jsx("code",{children:"--danger"}),". Tingkat itu ",e.jsx("b",{children:"bukan kontrol keamanan"}),": ia menentukan tool mana yang terlihat, sementara yang menahan sungguhan adalah capability berlabel ",e.jsx("b",{children:"berbahaya"})," yang kamu centang di kartu di atas. Sebuah tool bisa terlihat dan tetap menjawab 403."]}),e.jsxs("div",{role:"group","aria-label":"Klien MCP",style:{display:"flex",flexWrap:"wrap",gap:8,marginBottom:10},children:[ca.map(u=>e.jsx(y,{size:"sm",variant:u.id===a?"primary":"ghost",onClick:()=>n(u.id),children:u.label},u.id)),e.jsx(y,{size:"sm",variant:i==="read-only"?"primary":"ghost",leftIcon:"eye",onClick:()=>r(u=>u==="read-only"?"default":"read-only"),children:"Mode baca-saja"}),e.jsx(y,{size:"sm",variant:i==="danger"?"primary":"ghost",leftIcon:"alert-triangle",onClick:()=>r(u=>u==="danger"?"default":"danger"),children:"Tampilkan tool berbahaya"})]}),e.jsx("div",{style:{...Ae,marginBottom:8},children:f.hint}),e.jsx("pre",{"data-testid":"mcp-snippet",style:Gt,children:h}),e.jsx("div",{style:{marginTop:10},children:e.jsx(y,{size:"sm",leftIcon:"copy",onClick:g,children:t?"Tersalin":"Salin"})})]}),e.jsxs(C,{eyebrow:"tool",title:"Tool yang tersedia",children:[e.jsxs("div",{style:Ae,children:["Centang capability di bawah pada token yang dipakai. Mode baca-saja menyembunyikan seluruh tool bertanda ",e.jsx("i",{children:"tulis"}),"."]}),e.jsx("div",{className:"hn-local-overflow",style:{overflowX:"auto"},children:e.jsxs("table",{"data-testid":"mcp-tools",style:{borderCollapse:"collapse",width:"100%"},children:[e.jsx("thead",{children:e.jsxs("tr",{children:[e.jsx("th",{style:{...fe,textAlign:"left",fontSize:11.5,color:"var(--text-subtle)"},children:"Tool"}),e.jsx("th",{style:{...fe,textAlign:"left",fontSize:11.5,color:"var(--text-subtle)"},children:"Mode"}),e.jsx("th",{style:{...fe,textAlign:"left",fontSize:11.5,color:"var(--text-subtle)"},children:"Capability"})]})}),e.jsx("tbody",{children:Et.map(u=>e.jsxs("tr",{children:[e.jsxs("td",{style:fe,children:[e.jsx("code",{children:u.name}),e.jsx("div",{style:{fontSize:11.5,color:"var(--text-subtle)"},children:u.title})]}),e.jsx("td",{style:fe,children:u.mode==="read"?"baca":"tulis"}),e.jsx("td",{style:fe,children:u.capability?e.jsx("code",{children:u.capability}):e.jsx("span",{style:{color:"var(--text-subtle)"},children:"—"})})]},u.name))})]})})]})]})}const Tt="https://github.com/denameidina/hanoman/blob/main/docs/agent-integration.md";function Kt({onToast:a}){const[n,i]=o.useState(null),[r,t]=o.useState(!1),[c,d]=o.useState(!1),[h,f]=o.useState(!1),g=`${window.location.origin}${Qa.agentDoc}`;async function u(){if(!h){if(n){t(!0);return}f(!0),d(!1);try{i(await j.agentDoc()),t(!0)}catch{d(!0)}finally{f(!1)}}}return e.jsxs(e.Fragment,{children:[e.jsxs(C,{eyebrow:"dokumentasi",title:"Dokumentasi AI Agent",children:[e.jsxs("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginBottom:12,lineHeight:1.5},children:["Satu halaman yang membuat agen mana pun bisa langsung bekerja — cukup berikan"," ",e.jsx("b",{children:"tautan ini + satu agent token"}),". Markdown mentah, bisa diambil agen lewat HTTP biasa, ",e.jsx("b",{children:"tanpa auth"}),"."]}),e.jsx("code",{style:{display:"block",wordBreak:"break-all",fontSize:12,marginBottom:10},children:g}),e.jsxs("div",{style:{display:"flex",gap:8,flexWrap:"wrap",alignItems:"center"},children:[e.jsx(y,{size:"sm",leftIcon:"copy",onClick:()=>{var k;(k=navigator.clipboard)==null||k.writeText(g),a==null||a("Tautan disalin","ok","copy")},children:"Salin tautan"}),e.jsx(y,{size:"sm",variant:"ghost",leftIcon:"book-open",disabled:h,onClick:()=>void u(),children:h?"Memuat…":"Buka"}),e.jsx("a",{href:Tt,target:"_blank",rel:"noreferrer",children:e.jsx(y,{size:"sm",variant:"ghost",leftIcon:"external-link",children:"Lihat di GitHub"})})]}),c&&e.jsx("div",{style:{marginTop:12},children:e.jsx(V,{kind:"error",compact:!0,title:"Gagal memuat dokumentasi",hint:g,action:()=>void u(),actionLabel:"Coba lagi"})})]}),r&&n!==null&&e.jsx(Ct,{path:"agent-integration.md",text:n,eyebrow:"hanoman · panduan AI agent",onClose:()=>t(!1)})]})}const Ve={feature:"Brief (feature)",qa:"QA",scaffold:"Scaffold",reverse:"Reverse docs",prd:"PRD",audit:"Audit",breakdown:"Breakdown PRD",goal:"Goal",no_effort:"No effort"},ua=[{id:"claude",label:"Claude Code"},{id:"codex",label:"Codex CLI"}],Jt=[{value:"inline",label:"Inline — agen fase mengerjakan task sendiri (cepat)"},{value:"subagent",label:"Subagent per task — implementer + review tiap task (lebih lambat)"}],Vt={model:null,effort:null},He={padding:"6px 8px",borderTop:"1px solid var(--border-hair)",verticalAlign:"top"};function Yt({flow:a,phase:n,runtime:i,cell:r,onPick:t}){const c=tt(i),d=nt(i,r.model??""),h=i==="codex"?te:fa,f=[{value:"",label:"— warisi orchestrator"},...r.model&&!c.some(u=>u.id===r.model)?[{value:r.model,label:r.model}]:[],...c.map(u=>({value:u.id,label:u.label}))],g=[{value:"",label:"— warisi"},...r.effort&&!d.includes(r.effort)?[{value:r.effort,label:r.effort}]:[],...d.map(u=>({value:u,label:u}))];return e.jsxs("div",{style:{display:"flex",gap:6,flexWrap:"wrap"},children:[e.jsx(M,{size:"sm","aria-label":`Model ${a} ${n} ${i}`,value:r.model??"",style:{minWidth:150},options:f,onChange:u=>{const k=u.target.value||null;t({model:k,effort:k&&r.effort?h(k,r.effort):r.effort},`${n} (${i}) → ${k??"warisi"}`)}}),e.jsx(M,{size:"sm","aria-label":`Effort ${a} ${n} ${i}`,value:r.effort??"",style:{minWidth:96},options:g,onChange:u=>t({...r,effort:u.target.value||null},`${n} (${i}) effort → ${u.target.value||"warisi"}`)})]})}function Xt({orchestration:a,onChange:n}){const i=a??Ye,r=c=>i[c]??Ye[c],t=(c,d,h)=>n({...i,[c]:d},h);return e.jsxs(e.Fragment,{children:[e.jsx(C,{eyebrow:"orkestrasi",title:"Orkestrasi subagent per fase",children:e.jsxs("div",{style:{fontSize:12.5,color:"var(--text-muted)",lineHeight:1.5},children:["Sesi utama menjadi ",e.jsx("b",{children:"orchestrator"}),"; setiap fase dikerjakan subagent dengan model & effort di bawah. ",e.jsx("b",{children:"Warisi"})," memakai model/effort orchestrator yang dipilih saat ",e.jsx("b",{children:"Start"}),". Subagent selalu berjalan di runtime orchestrator: sesi claude memakai kolom Claude Code, sesi codex kolom Codex CLI. Flow yang dimatikan berjalan sebagai sesi tunggal seperti sebelumnya."]})}),Za.map(c=>{const d=r(c);return e.jsx("div",{"data-testid":`orch-flow-${c}`,children:e.jsxs(C,{eyebrow:"flow",title:Ve[c],children:[e.jsxs("div",{style:{display:"flex",alignItems:"center",justifyContent:"space-between",gap:12,marginBottom:10},children:[e.jsx("span",{style:{fontSize:12.5,color:"var(--text-muted)"},children:d.enabled?"Fase dikerjakan subagent":"Sesi tunggal (orkestrasi mati)"}),e.jsx(N,{checked:d.enabled,"aria-label":`Orkestrasi ${c}`,onChange:h=>t(c,{...d,enabled:h},`Orkestrasi ${Ve[c]} · ${h?"aktif":"nonaktif"}`)})]}),et.has(c)&&e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:10,flexWrap:"wrap",marginBottom:10,opacity:d.enabled?1:.55},children:[e.jsx("span",{style:{fontSize:12.5,color:"var(--text-strong)",fontWeight:600},children:"Mode Execute"}),e.jsx(M,{size:"sm","aria-label":`Mode Execute ${c}`,value:d.executeMode??"inline",style:{minWidth:260,maxWidth:"100%"},options:Jt,onChange:h=>{const f=h.target.value==="subagent"?"subagent":"inline";t(c,{...d,executeMode:f},`Mode Execute ${Ve[c]} · ${f==="subagent"?"subagent per task":"inline"}`)}}),e.jsx("span",{style:{fontSize:11.5,color:"var(--text-muted)"},children:"khusus sesi Claude · Codex selalu inline"})]}),e.jsx("div",{style:{overflowX:"auto",opacity:d.enabled?1:.55},children:e.jsxs("table",{style:{width:"100%",minWidth:560,borderCollapse:"collapse",fontSize:12.5},children:[e.jsx("thead",{children:e.jsxs("tr",{children:[e.jsx("th",{style:{...He,borderTop:"none",textAlign:"left"},children:"Fase"}),ua.map(h=>e.jsx("th",{style:{...He,borderTop:"none",textAlign:"left"},children:h.label},h.id))]})}),e.jsx("tbody",{children:at[c].map(h=>e.jsxs("tr",{children:[e.jsx("td",{style:He,children:h}),ua.map(f=>e.jsx("td",{style:He,children:e.jsx(Yt,{flow:c,phase:h,runtime:f.id,cell:d[f.id][h]??Vt,onPick:(g,u)=>t(c,{...d,[f.id]:{...d[f.id],[h]:g}},u)})},f.id))]},h))})]})})]})},c)})]})}const ze=a=>!!a&&typeof a=="object"&&!Array.isArray(a)&&Object.getPrototypeOf(a)===Object.prototype,qe=(a,n)=>{if(a===n)return!0;if(Array.isArray(a)&&Array.isArray(n))return a.length===n.length&&a.every((i,r)=>qe(i,n[r]));if(ze(a)&&ze(n)){const i=Object.keys(a).filter(t=>a[t]!==void 0),r=Object.keys(n).filter(t=>n[t]!==void 0);return i.length===r.length&&i.every(t=>qe(a[t],n[t]))}return!1};function Ze(a,n,i){if(qe(n,a)&&i!==void 0)return i;if(!ze(a)||!ze(n)||!ze(i))return n;const r={...i};for(const t of Object.keys(n))r[t]=Ze(a[t],n[t],i[t]);return r}const je=a=>jt(a).map(n=>({value:n,label:n==="xhigh"?"x-high":n})),ye=a=>wa(St,a),ve=(a,n)=>{const i=fa(a,n);return i===n?{}:{effort:i}},ga=[{value:"blip",label:"Blip · 0.1s"},{value:"pop",label:"Pop · 0.1s"},{value:"short",label:"Short · 0.15s"},{value:"ping",label:"Ping · 0.2s"},{value:"coin",label:"Coin · 0.3s"},{value:"alert",label:"Alert · 0.3s"},{value:"medium",label:"Medium · 0.4s"},{value:"chime",label:"Chime · 0.4s"},{value:"success",label:"Success · 0.4s"},{value:"bell",label:"Bell · 0.5s"},{value:"marimba",label:"Marimba · 0.6s"},{value:"long",label:"Long · 0.8s"},{value:"fanfare",label:"Fanfare · 0.9s"},{value:"off",label:"Senyap"}],qt={model:"sonnet",effort:"medium",autoDefault:!0,autoScaffold:!0,notifyFail:!0,notifyDone:!0,notifySound:"short",notifyDecision:!0,notifyDecisionSound:"alert",agentAccessEnabled:!1,scheduler:kt,goal:bt,agent:"claude",codex:ja,verifyScope:"changed",method:pt,conflict:Sa,lead:Re,telegram:Se,changelog:va,portalChat:ya,orchestration:Ye,remoteControl:mt,logShipping:ht,logRetention:gt,builtinAgents:{},builtinAgentPolicies:{},builtinRuntimeDefaults:ut},T={claude:"Claude Code",codex:"Codex CLI"};function ha({id:a,label:n,active:i}){return e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8,paddingTop:4},children:[e.jsx("div",{style:{fontSize:13.5,fontWeight:700,color:"var(--text-strong)"},children:n}),e.jsx("span",{"data-testid":`agent-badge-${a}`,style:{fontSize:11,fontFamily:"var(--font-mono)",padding:"2px 7px",borderRadius:999,background:i?"var(--brass-100)":"transparent",color:i?"var(--brass-700)":"var(--text-subtle)",border:i?"none":"1px solid var(--border-hair)"},children:i?"dipakai sesi baru":"tidak dipakai sekarang"})]})}function v({title:a,desc:n,children:i,last:r}){return e.jsxs("div",{className:"hn-setting-row",style:{display:"flex",alignItems:"center",gap:16,padding:"14px 0",borderBottom:r?"none":"1px solid var(--border-hair)"},children:[e.jsxs("div",{style:{flex:1,minWidth:0},children:[e.jsx("div",{style:{fontSize:14,fontWeight:600,color:"var(--text-strong)"},children:a}),n&&e.jsx("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginTop:3,lineHeight:1.5},children:n})]}),e.jsx("div",{className:"hn-setting-control",style:{flex:"0 0 auto"},children:i})]})}function Qt({me:a,onLoggedOut:n,onToast:i}){const[r,t]=o.useState(""),[c,d]=o.useState(""),[h,f]=o.useState(!1),g=r.length>=1&&c.length>=8&&!h;async function u(){if(g){f(!0);try{await j.changePassword({currentPassword:r,newPassword:c}),t(""),d(""),i==null||i("Password diganti · perangkat lain ter-logout","ok","key-round")}catch(z){i==null||i(z instanceof Xe&&z.status===400?"Password lama salah":"Gagal ganti password","err","x-circle")}finally{f(!1)}}}async function k(){try{await j.logout()}finally{n()}}return e.jsxs(C,{eyebrow:"akun",title:"Akun",actions:e.jsx(y,{size:"sm",variant:"ghost",leftIcon:"log-out",onClick:k,children:"Logout"}),children:[e.jsx(v,{title:"Masuk sebagai",desc:a.email,children:e.jsx("span",{style:{fontFamily:"var(--font-mono)",fontSize:12,color:"var(--text-subtle)"},children:a.id})}),e.jsxs("div",{style:{paddingTop:14},children:[e.jsx("div",{style:{fontSize:14,fontWeight:600,color:"var(--text-strong)",marginBottom:10},children:"Ganti password"}),e.jsxs("div",{className:"hn-grid-mobile",style:{display:"grid",gridTemplateColumns:"1fr 1fr auto",gap:10,alignItems:"end"},children:[e.jsx(ee,{label:"Password lama",children:e.jsx(Y,{type:"password",autoComplete:"current-password",placeholder:"••••••••",value:r,onChange:z=>t(z.target.value),style:{width:"100%"}})}),e.jsx(ee,{label:e.jsxs(e.Fragment,{children:["Password baru ",e.jsx("span",{style:{fontWeight:400,color:"var(--text-subtle)"},children:"· min 8"})]}),children:e.jsx(Y,{type:"password",autoComplete:"new-password",placeholder:"minimal 8 karakter",value:c,onChange:z=>d(z.target.value),style:{width:"100%"}})}),e.jsx(y,{size:"sm",leftIcon:"key-round",disabled:!g,onClick:u,style:{marginBottom:14},children:"Ganti"})]})]})]})}function Zt({me:a,onToast:n}){const{confirm:i,dialog:r}=$e(),[t,c]=o.useState(null),[d,h]=o.useState(""),[f,g]=o.useState(""),[u,k]=o.useState(!1),z=o.useCallback(()=>{j.listUsers().then(c).catch(()=>c([]))},[]);o.useEffect(()=>{z()},[z]);const R=/\S+@\S+\.\S+/.test(d)&&f.length>=8&&!u;async function F(){if(R){k(!0);try{await j.inviteUser({email:d,password:f}),h(""),g(""),z(),n==null||n("User "+d+" diundang","ok","user-plus")}catch(m){n==null||n(m instanceof Xe&&m.status===409?"Email sudah dipakai":"Gagal mengundang user","err","x-circle")}finally{k(!1)}}}async function A(m){try{if(!await i({title:`Hapus user "${m.email}"?`,message:"User ini kehilangan akses ke dashboard seketika.",impact:["Semua sesi login miliknya ikut dicabut.","Tindakan ini tak bisa dibatalkan."],confirmLabel:"Hapus user",run:()=>j.deleteUser(m.id)}))return;z(),n==null||n("User "+m.email+" dihapus","warn","trash-2")}catch(x){n==null||n(x instanceof Xe&&x.status===400?"Tak bisa hapus user terakhir":"Gagal hapus user","err","x-circle")}}return e.jsxs(C,{eyebrow:"users",title:"Users",children:[e.jsx("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginBottom:12,lineHeight:1.5},children:"Undang user lain dengan menetapkan password-nya langsung — tanpa email undangan."}),t===null?e.jsx(V,{kind:"loading",compact:!0,title:"Memuat users…"}):t.map((m,x)=>e.jsx(v,{title:m.email,last:x===t.length-1,desc:"dibuat "+new Date(m.createdAt).toLocaleDateString("id-ID")+(m.id===a.id?" · kamu":""),children:e.jsx(y,{size:"sm",variant:"ghost",leftIcon:"trash-2",disabled:t.length<=1,onClick:()=>A(m),children:"Hapus"})},m.id)),e.jsxs("div",{style:{paddingTop:14},children:[e.jsx("div",{style:{fontSize:14,fontWeight:600,color:"var(--text-strong)",marginBottom:10},children:"Invite user"}),e.jsxs("div",{className:"hn-grid-mobile",style:{display:"grid",gridTemplateColumns:"1fr 1fr auto",gap:10,alignItems:"end"},children:[e.jsx(ee,{label:"Email",children:e.jsx(Y,{type:"email",value:d,placeholder:"user@nafanesia.id",onChange:m=>h(m.target.value),style:{width:"100%"}})}),e.jsx(ee,{label:e.jsxs(e.Fragment,{children:["Password ",e.jsx("span",{style:{fontWeight:400,color:"var(--text-subtle)"},children:"· min 8"})]}),children:e.jsx(Y,{type:"password",autoComplete:"new-password",placeholder:"minimal 8 karakter",value:f,onChange:m=>g(m.target.value),style:{width:"100%"}})}),e.jsx(y,{size:"sm",leftIcon:"user-plus",disabled:!R,onClick:F,style:{marginBottom:14},children:"Invite"})]})]}),r]})}function en({onToast:a}){const{confirm:n,dialog:i}=$e(),[r,t]=o.useState(null),[c,d]=o.useState(""),[h,f]=o.useState(null),[g,u]=o.useState(!1),k=o.useCallback(()=>{j.listDeviceTokens().then(t).catch(()=>t([]))},[]);o.useEffect(()=>{k()},[k]);async function z(){if(!(c.trim().length<1||g)){u(!0);try{const A=await j.createDeviceToken({name:c.trim()});f({name:A.name,token:A.token}),d(""),k(),a==null||a("Token perangkat dibuat — salin sekarang","ok","key-round")}catch{a==null||a("Gagal membuat token","err","x-circle")}finally{u(!1)}}}async function R(A){try{if(!await n({title:`Cabut token perangkat "${A.name}"?`,message:"Perangkat itu tak bisa sync lagi sampai token baru dibuat.",confirmLabel:"Cabut token",icon:"key-round",run:()=>j.revokeDeviceToken(A.id)}))return;k(),a==null||a("Token dicabut","warn","trash-2")}catch{a==null||a("Gagal mencabut token","err","x-circle")}}const F=(r??[]).filter(A=>!A.revokedAt);return e.jsxs(C,{eyebrow:"perangkat",title:"Device tokens",children:[e.jsxs("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginBottom:12,lineHeight:1.5},children:["Token per-perangkat untuk menyinkronkan instance lokal ke hub (Bearer). Tempel di",e.jsx("code",{style:{margin:"0 4px"},children:"SYNC_DEVICE_TOKEN"})," pada instance client. Plaintext hanya tampil sekali."]}),h&&e.jsxs("div",{style:{padding:12,marginBottom:12,border:"1px solid var(--brass-300)",borderRadius:"var(--radius-sm)",background:"var(--brass-100)"},children:[e.jsxs("div",{style:{fontSize:12.5,fontWeight:600,marginBottom:6},children:["Token untuk “",h.name,"” — salin sekarang, tak akan ditampilkan lagi:"]}),e.jsx("code",{style:{display:"block",wordBreak:"break-all",fontSize:12},children:h.token}),e.jsxs("div",{style:{marginTop:8,display:"flex",gap:8},children:[e.jsx(y,{size:"sm",leftIcon:"copy",onClick:()=>{var A;(A=navigator.clipboard)==null||A.writeText(h.token),a==null||a("Disalin","ok","copy")},children:"Salin"}),e.jsx(y,{size:"sm",variant:"ghost",onClick:()=>f(null),children:"Tutup"})]})]}),r===null?e.jsx(V,{kind:"loading",compact:!0,title:"Memuat token…"}):F.length===0?e.jsx("div",{style:{fontSize:13,color:"var(--text-subtle)",padding:"8px 0"},children:"Belum ada token perangkat."}):F.map((A,m)=>e.jsx(v,{title:A.name,last:m===F.length-1,desc:"dibuat "+new Date(A.createdAt).toLocaleDateString("id-ID")+(A.lastSeenAt?" · terlihat "+new Date(A.lastSeenAt).toLocaleString("id-ID"):" · belum dipakai"),children:e.jsx(y,{size:"sm",variant:"ghost",leftIcon:"trash-2",onClick:()=>R(A),children:"Cabut"})},A.id)),e.jsxs("div",{className:"hn-grid-mobile",style:{paddingTop:14,display:"grid",gridTemplateColumns:"1fr auto",gap:10,alignItems:"end"},children:[e.jsx(ee,{label:"Nama perangkat",children:e.jsx(Y,{value:c,placeholder:"laptop-dena",onChange:A=>d(A.target.value),style:{width:"100%"}})}),e.jsx(y,{size:"sm",leftIcon:"plus",disabled:c.trim().length<1||g,onClick:z,style:{marginBottom:14},children:"Buat token"})]}),i]})}function an({onToast:a}){const{confirm:n,dialog:i}=$e(),[r,t]=o.useState(""),[c,d]=o.useState(null),h=o.useCallback(()=>{j.listSessionResults(r||void 0).then(d).catch(()=>d([]))},[r]);o.useEffect(()=>{h()},[h]);async function f(){if(!r){a==null||a("Isi project id untuk purge","warn","alert-triangle");return}try{if(!await n({title:`Purge activity log project "${r}"?`,message:"Seluruh entri hasil sesi project ini dihapus dari device ini.",impact:["Log bersifat append-only — entri yang dihapus tak bisa dipulihkan."],confirmLabel:"Purge",run:async()=>{const g=await j.purgeSessionResults(r);a==null||a(`${g.purged} entri dihapus`,"warn","trash-2")}}))return;h()}catch{a==null||a("Gagal purge","err","x-circle")}}return e.jsxs(C,{eyebrow:"aktivitas",title:"Activity log",children:[e.jsx("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginBottom:12,lineHeight:1.5},children:"Ringkasan hasil sesi lintas device (transisi stage, commit, PR) — append-only. Filter per project."}),e.jsxs("div",{className:"hn-grid-mobile",style:{display:"grid",gridTemplateColumns:"1fr auto",gap:10,alignItems:"end",marginBottom:12},children:[e.jsx(ee,{label:"Project id (opsional)",children:e.jsx(Y,{value:r,placeholder:"mis. hanoman — kosong = semua project",onChange:g=>t(g.target.value),style:{width:"100%"}})}),e.jsx(y,{size:"sm",variant:"ghost",leftIcon:"trash-2",disabled:!r,onClick:f,style:{marginBottom:14},children:"Purge"})]}),c===null?e.jsx(V,{kind:"loading",compact:!0,title:"Memuat aktivitas…"}):c.length===0?e.jsx("div",{style:{fontSize:13,color:"var(--text-subtle)",padding:"8px 0"},children:"Belum ada aktivitas."}):c.map((g,u)=>e.jsx(v,{last:u===c.length-1,title:`${g.specId??g.projectId}${g.oldStage&&g.newStage?` · ${g.oldStage} → ${g.newStage}`:""}`,desc:[g.status,g.commitSha?g.commitSha.slice(0,8):null,g.branch,g.author,new Date(g.createdAt).toLocaleString("id-ID")].filter(Boolean).join(" · "),children:g.prUrl&&e.jsx("a",{href:g.prUrl,target:"_blank",rel:"noreferrer",children:e.jsx(y,{size:"sm",variant:"ghost",leftIcon:"external-link",children:"PR"})})},g.id)),i]})}const tn={sync:"Sync",claude:"Claude",vps:"VPS",runtime:"Runtime",bootstrap:"Bootstrap (read-only)"};function nn({onToast:a}){const[n,i]=o.useState(null),[r,t]=o.useState({}),c=o.useCallback(()=>{j.getConfig().then(i).catch(()=>i(null))},[]);if(o.useEffect(()=>{c()},[c]),!n)return e.jsx(V,{kind:"loading",title:"Memuat konfigurasi…"});const d=u=>t(k=>{const z={...k};return delete z[u],z}),h=async u=>{const k=r[u.key]??"";try{await j.putConfig(u.key,k),d(u.key),c(),a==null||a(`${u.label} disimpan`,"ok","check-circle-2")}catch{a==null||a(`Gagal menyimpan ${u.label}`,"err","x-circle")}},f=async u=>{try{await j.deleteConfig(u.key),d(u.key),c(),a==null||a(`${u.label} direset`,"warn","rotate-ccw")}catch{a==null||a("Gagal reset","err","x-circle")}},g=[...new Set(n.entries.map(u=>u.group))];return e.jsx(e.Fragment,{children:g.map(u=>e.jsxs(C,{eyebrow:u,title:tn[u]??u,children:[u==="sync"&&e.jsx("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginBottom:8},children:n.sync.running?n.sync.connected?"● Tersambung ke hub":"◐ Sync aktif, menyambung…":"○ Tidak sync (HUB murni)"}),n.entries.filter(k=>k.group===u).map(k=>e.jsx(v,{title:k.label,desc:k.help,children:e.jsx(ln,{entry:k,draft:r[k.key],onDraft:z=>t(R=>({...R,[k.key]:z})),onSave:()=>h(k),onReset:()=>f(k)})},k.key))]},u))})}function ln({entry:a,draft:n,onDraft:i,onSave:r,onReset:t}){var d,h;const c=e.jsxs("span",{style:{fontSize:10.5,fontFamily:"var(--font-mono)",color:"var(--text-subtle)",marginRight:8},children:[a.source," · ",a.apply]});if(!a.editable)return e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8},children:[c,e.jsx("code",{style:{fontSize:12},children:a.masked??a.value??"—"})]});if(a.kind==="secret")return e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8},children:[c,a.hasValue&&n===void 0?e.jsxs(e.Fragment,{children:[e.jsx("code",{style:{fontSize:12},children:a.masked}),e.jsx(y,{size:"sm",variant:"ghost",leftIcon:"pencil",onClick:()=>i(""),children:"Ganti"}),e.jsx(y,{size:"sm",variant:"ghost",leftIcon:"trash-2",onClick:t,children:"Hapus"})]}):e.jsxs(e.Fragment,{children:[e.jsx(Y,{"aria-label":a.label,type:"password",placeholder:a.hasValue?"biarkan kosong = pertahankan":((d=We(a.key))==null?void 0:d.example)??"tempel token…",value:n??"",onChange:f=>i(f.target.value),style:{width:240}}),e.jsx(y,{size:"sm",leftIcon:"save",onClick:r,children:"Simpan"})]})]});if(a.kind==="bool"){const f=(n??a.value)==="1";return e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8},children:[c,e.jsx(N,{checked:f,onChange:g=>{i(g?"1":"0")}}),n!==void 0&&e.jsx(y,{size:"sm",leftIcon:"save",onClick:r,children:"Simpan"})]})}return e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8},children:[c,e.jsx(Y,{"aria-label":a.label,type:a.kind==="int"?"number":"text",placeholder:(h=We(a.key))==null?void 0:h.example,value:n??a.value??"",onChange:f=>i(f.target.value),style:{width:240}}),e.jsx(y,{size:"sm",leftIcon:"save",onClick:r,children:"Simpan"}),a.source==="db"&&e.jsx(y,{size:"sm",variant:"ghost",leftIcon:"rotate-ccw",onClick:t,children:"Reset"})]})}const sn=[{had:"sessions:write",needs:"sessions:spawn",sentence:"dulu bisa membuka sesi baru"},{had:"ide:write",needs:"ide:git",sentence:"dulu bisa merge/rebase & menghapus branch"},{had:"backlog:write",needs:"backlog:lifecycle",sentence:"dulu bisa integrate & menghapus backlog"},{had:"vps:write",needs:"vps:exec",sentence:"dulu bisa menjalankan perintah di VPS"}];function ma(a){return sn.filter(n=>a.includes(n.had)&&!a.includes(n.needs)).map(n=>n.sentence)}function _e({ids:a,picked:n,onToggle:i,ariaLabel:r,label:t}){const c=o.useRef(null),d=a.filter(f=>n.includes(f)).length,h=a.length>0&&d===a.length;return o.useEffect(()=>{c.current&&(c.current.indeterminate=d>0&&!h)},[d,h]),a.length===0?null:e.jsxs("label",{style:{display:"inline-flex",alignItems:"center",gap:4,cursor:"pointer",fontSize:11.5,color:"var(--text-subtle)",marginLeft:t?0:5},children:[t&&e.jsx("span",{children:t}),e.jsx("input",{ref:c,type:"checkbox","aria-label":r,checked:h,onChange:()=>i(a,!h)})]})}function rn({onToast:a}={}){const{confirm:n,dialog:i}=$e(),[r,t]=o.useState([]),[c,d]=o.useState(null),[h,f]=o.useState(null),[g,u]=o.useState(""),[k,z]=o.useState([]),[R,F]=o.useState(null),[A,m]=o.useState(!1),x=o.useCallback(()=>{j.listAgentTokens().then(b=>d(b.items)).catch(()=>d([]))},[]);o.useEffect(()=>{j.getSettings().then(f).catch(()=>{}),j.getAgentCapabilities().then(b=>t(b.capabilities)).catch(()=>{}),x()},[x]);async function O(b){if(!h)return;const B={...h,agentAccessEnabled:b};f(B);try{const s=await j.getSettings().catch(()=>{});await j.putSettings(Ze(h,B,s)),a==null||a("Akses AI agent "+(b?"aktif":"nonaktif"),b?"ok":"warn","bot")}catch{a==null||a("Gagal menyimpan","err","x-circle"),f(h)}}const K=b=>z(B=>B.includes(b)?B.filter(s=>s!==b):[...B,b]),U=(b,B)=>z(s=>B?Array.from(new Set([...s,...b])):s.filter(w=>!b.includes(w)));async function ge(){if(!(g.trim().length<1||A)){m(!0);try{const b=await j.createAgentToken({name:g.trim(),capabilities:k});F({name:b.name,token:b.token}),u(""),z([]),x(),a==null||a("Agent token dibuat — salin sekarang","ok","key-round")}catch{a==null||a("Gagal membuat token","err","x-circle")}finally{m(!1)}}}async function pe(b){try{if(!await n({title:`Cabut agent token "${b.name}"?`,message:"Agen yang memakainya langsung kehilangan akses.",confirmLabel:"Cabut token",icon:"key-round",run:()=>j.revokeAgentToken(b.id)}))return;x(),a==null||a("Token dicabut","warn","trash-2")}catch{a==null||a("Gagal mencabut token","err","x-circle")}}async function ie(b,B){try{await j.patchAgentToken(b.id,{enabled:B}),x()}catch{a==null||a("Gagal mengubah token","err","x-circle")}}const we=Array.from(new Set(r.map(b=>b.domain))),le=b=>r.filter(B=>B.access===b).map(B=>B.id),ae=r.map(b=>b.id),se=(c??[]).filter(b=>!b.revokedAt);return e.jsxs(e.Fragment,{children:[e.jsx(Kt,{onToast:a}),e.jsxs(C,{eyebrow:"ai agent",title:"Akses AI Agent",children:[e.jsxs("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginBottom:12,lineHeight:1.5},children:["Beri AI agent eksternal kendali hanoman lewat token (header ",e.jsx("code",{children:"Authorization: Bearer"}),"). Tiap fitur dibuka per ",e.jsx("b",{children:"capability"}),". Selagi master switch mati, semua token ditolak."]}),e.jsx(v,{title:"Aktifkan akses AI agent",last:!0,desc:"Master switch. Nonaktif → semua agent token 401, apa pun capability-nya.",children:e.jsx(N,{checked:!!(h!=null&&h.agentAccessEnabled),onChange:b=>void O(b)})})]}),e.jsxs(C,{eyebrow:"token",title:"Agent tokens",children:[R&&e.jsxs("div",{style:{padding:12,marginBottom:12,border:"1px solid var(--brass-300)",borderRadius:"var(--radius-sm)",background:"var(--brass-100)"},children:[e.jsxs("div",{style:{fontSize:12.5,fontWeight:600,marginBottom:6},children:["Token “",R.name,"” — salin sekarang, tak akan ditampilkan lagi:"]}),e.jsx("code",{style:{display:"block",wordBreak:"break-all",fontSize:12},children:R.token}),e.jsxs("div",{style:{marginTop:8,display:"flex",gap:8},children:[e.jsx(y,{size:"sm",leftIcon:"copy",onClick:()=>{var b;(b=navigator.clipboard)==null||b.writeText(R.token),a==null||a("Disalin","ok","copy")},children:"Salin"}),e.jsx(y,{size:"sm",variant:"ghost",onClick:()=>F(null),children:"Tutup"})]})]}),c===null?e.jsx(V,{kind:"loading",compact:!0,title:"Memuat token…"}):se.length===0?e.jsx("div",{style:{fontSize:13,color:"var(--text-subtle)",padding:"8px 0"},children:"Belum ada agent token."}):se.map((b,B)=>e.jsx(v,{title:b.name,last:B===se.length-1,desc:`${b.tokenPrefix}… · ${b.capabilities.length} capability · `+(b.lastUsedAt?"terpakai "+new Date(b.lastUsedAt).toLocaleString("id-ID"):"belum dipakai")+(ma(b.capabilities).length?` — token ini ${ma(b.capabilities).join(", ")}; sekarang tidak, sampai capability berbahayanya dicentang. Cabut lalu buat ulang bila memang perlu.`:""),children:e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:10},children:[e.jsx(N,{size:"sm",checked:b.enabled,onChange:s=>void ie(b,s)}),e.jsx(y,{size:"sm",variant:"ghost",leftIcon:"trash-2",onClick:()=>pe(b),children:"Cabut"})]})},b.id)),e.jsxs("div",{style:{paddingTop:14},children:[e.jsx(ee,{label:"Nama token",children:e.jsx(Y,{value:g,placeholder:"mis. agent-ci",onChange:b=>u(b.target.value),style:{width:"100%"}})}),e.jsx("div",{style:{marginTop:12,fontSize:12.5,fontWeight:600,color:"var(--text-strong)"},children:"Capability"}),e.jsxs("div",{className:"hn-grid-mobile",style:{marginTop:8,display:"grid",gridTemplateColumns:"1fr auto auto auto",gap:"6px 14px",alignItems:"center"},children:[e.jsx("div",{style:{textAlign:"right"},children:e.jsx(_e,{label:"Pilih semua capability",ariaLabel:"pilih semua capability",ids:ae,picked:k,onToggle:U})}),e.jsxs("div",{style:{fontSize:11.5,color:"var(--text-subtle)",textAlign:"center"},children:["baca",e.jsx(_e,{ariaLabel:"pilih semua baca",ids:le("read"),picked:k,onToggle:U})]}),e.jsxs("div",{style:{fontSize:11.5,color:"var(--text-subtle)",textAlign:"center"},children:["tulis",e.jsx(_e,{ariaLabel:"pilih semua tulis",ids:le("write"),picked:k,onToggle:U})]}),e.jsxs("div",{style:{fontSize:11.5,color:"var(--status-err)",textAlign:"center"},children:["berbahaya",e.jsx(_e,{ariaLabel:"pilih semua berbahaya",ids:le("danger"),picked:k,onToggle:U})]}),we.map(b=>{const B=r.find(_=>_.domain===b&&_.access==="read"),s=r.find(_=>_.domain===b&&_.access==="write"),w=r.find(_=>_.domain===b&&_.access==="danger"),L=it.find(_=>_.domain===b);return e.jsxs(o.Fragment,{children:[e.jsxs("div",{style:{fontSize:13,color:"var(--text-muted)"},children:[e.jsxs("div",{style:{fontWeight:600,color:"var(--text-strong)"},children:[(L==null?void 0:L.label)??b,s!=null&&s.risk||w!=null&&w.risk?" ⚠":""]}),(L==null?void 0:L.desc)&&e.jsx("div",{style:{fontSize:11.5,color:"var(--text-subtle)",lineHeight:1.4,marginTop:1},children:L.desc}),w&&e.jsxs("div",{style:{fontSize:11.5,color:"var(--status-err)",lineHeight:1.4,marginTop:2},children:[w.label.replace(/^[^—]+—\s*/,"berbahaya: ")," — ",w.desc]})]}),e.jsx("div",{style:{textAlign:"center"},children:B&&e.jsx("input",{type:"checkbox","aria-label":B.id,checked:k.includes(B.id),onChange:()=>K(B.id)})}),e.jsx("div",{style:{textAlign:"center"},children:s&&e.jsx("input",{type:"checkbox","aria-label":s.id,checked:k.includes(s.id),onChange:()=>K(s.id)})}),e.jsx("div",{style:{textAlign:"center"},children:w&&e.jsx("input",{type:"checkbox","aria-label":w.id,checked:k.includes(w.id),onChange:()=>K(w.id)})})]},b)})]}),e.jsx("div",{style:{marginTop:14},children:e.jsx(y,{size:"sm",leftIcon:"plus",disabled:g.trim().length<1||A,onClick:()=>void ge(),children:"Buat token"})})]}),i]}),e.jsx(Ut,{})]})}function dn(){const[a,n]=o.useState(null),[i,r]=o.useState(!1),t=o.useCallback(()=>{j.setupStatus().then(n).catch(()=>n(null))},[]);return o.useEffect(t,[t]),a?i?e.jsx(ct,{status:a,onDone:()=>{r(!1),t()}}):e.jsx("div",{"data-testid":"setup-card",children:e.jsxs(C,{eyebrow:"setup",title:"Setup awal",children:[e.jsxs("div",{style:{fontSize:12.5,lineHeight:1.6,marginBottom:12},children:["Peruntukan: ",a.deployment==="local"?"Device saya sendiri":"Diakses orang lain",e.jsx("br",{}),a.hardening?"Hardening menyala":"Hardening mati",a.hardeningLocked?" — dipasang lewat env, tak bisa diubah dari sini":""]}),e.jsx(y,{onClick:()=>r(!0),children:"Jalankan ulang setup"})]})}):e.jsx(V,{kind:"loading",title:"Memuat setup…"})}const pa=[{key:"akun",label:"Akun",icon:"user-round"},{key:"users",label:"Users",icon:"users"},{key:"akses-klien",label:"Akses klien",icon:"user-check"},{key:"perangkat",label:"Perangkat",icon:"key-round"},{key:"kendali-jarak-jauh",label:"Kendali jarak jauh",icon:"shield"},{key:"agent",label:"Akses AI Agent",icon:"bot"},{key:"custom-agent",label:"Custom agent",icon:"bot"},{key:"aktivitas",label:"Aktivitas",icon:"activity"},{key:"konfigurasi",label:"Konfigurasi",icon:"sliders"},{key:"setup",label:"Setup awal",icon:"shield"},{key:"telegram",label:"Telegram",icon:"send"},{key:"webhook",label:"Webhook",icon:"webhook"},{key:"umum",label:"Umum",icon:"sliders-horizontal"},{key:"model",label:"Model sesi",icon:"cpu"},{key:"orkestrasi",label:"Orkestrasi",icon:"layers"},{key:"sesi",label:"Sesi",icon:"bell"}];function mn({onToast:a,me:n,onLoggedOut:i}){const r=lt(),[t,c]=o.useState(null),[d,h]=o.useState(!1),f=o.useRef(Promise.resolve()),[g,u]=st("settings","tab","akun",ot),k=rt(),z=k==="mobile",[R,F]=o.useState(!1),[A,m]=o.useState(null),[x,O]=o.useState(!1),[K,U]=o.useState(null);o.useEffect(()=>{j.getCodexVersion().then(U).catch(()=>{})},[]);const[ge,pe]=o.useState(null),[ie,we]=o.useState("");o.useEffect(()=>{g==="sesi"&&(j.getMethodStatus().then(pe).catch(()=>pe(null)),j.listProjects({limit:100}).then(W=>we(H=>{var de;return H||(((de=W.items.find(Pe=>Pe.binding??Pe.repoDir))==null?void 0:de.id)??"")})).catch(()=>{}))},[g]);const le=o.useCallback(()=>{h(!1),c(null),j.getSettings().then(c).catch(()=>h(!0))},[]);o.useEffect(()=>{le()},[le]);const ae=o.useCallback(()=>{O(!1),m(null),j.getTelegramStatus().then(m).catch(()=>O(!0))},[]);o.useEffect(()=>{g==="telegram"&&ae()},[g,ae]);const[se,b]=o.useState(null),[B,s]=o.useState({}),[w,L]=o.useState(null),[_,Ge]=o.useState(!1),Ie=o.useCallback(()=>{j.getTelegramCredentials().then(W=>{b(W),s({})}).catch(()=>b(null))},[]);o.useEffect(()=>{g==="telegram"&&Ie()},[g,Ie]);function Ca(){var ea,aa,ta;if(d)return e.jsx(V,{kind:"error",title:"Gagal memuat pengaturan",hint:"Pengaturan tidak ditampilkan agar tidak menimpa nilai di server.",action:le});if(!t)return e.jsx(V,{kind:"loading",title:"Memuat pengaturan…"});const W=(E,I,D,oe)=>{const X=t;c(E),f.current=f.current.then(async()=>{const he=await j.getSettings().catch(()=>{});await j.putSettings(Ze(X,E,he))}).catch(()=>{}),I&&a&&a(I,D||"ok",oe||"check-circle-2")},H=(E,I)=>W({...t,...E},I),de=(E,I)=>D=>H({[E]:D},I+(D?" · aktif":" · nonaktif")),Pe=async E=>{if(ie)try{await j.createShell(ie,{method:E.method,agent:E.agent}),a==null||a(`Pemasangan ${E.label} · ${T[E.agent]} berjalan di Terminal`,"ok","terminal")}catch{a==null||a("Gagal membuka sesi terminal pemasangan","err","alert-triangle")}},Q=t.codex??ja,Be=t.agent??"claude";if(g==="telegram"){const E=t.telegram??Se,I=(A==null?void 0:A.readiness)??"memuat",D=S=>S==="db"?e.jsx(ne,{tone:"ok",children:"tersimpan"}):S==="env"?e.jsx(ne,{tone:"warn",children:"dari .env · deprecated"}):e.jsx(ne,{children:"belum diisi"}),oe=()=>{const S=Object.fromEntries(Object.entries(B).filter(([,Z])=>Z!==""));if(!Object.keys(S).length){a==null||a("Tak ada perubahan","info","info");return}j.putTelegramCredentials(S).then(Z=>{b(Z),s({}),L(null),a==null||a("Kredensial Telegram disimpan","ok","check-circle-2"),ae()}).catch(Z=>a==null?void 0:a(Z.message||"Gagal menyimpan","err","alert-triangle"))},X=()=>{L("sending"),j.testTelegramConnection().then(L).catch(S=>L({ok:!1,error:S.message||"Gagal menghubungi server",inbound:{ok:!1,reason:"Status jalur masuk tak terbaca — server tak menjawab.",missingCapabilities:[],polling:!1}}))},he=()=>{Ge(!1),j.deleteTelegramCredentials().then(S=>{a==null||a(S.envFallback.length?`Kredensial dihapus — ${S.envFallback.length} nilai masih datang dari .env`:"Kredensial Telegram dihapus","ok","check-circle-2"),L(null),Ie(),ae()}).catch(S=>a==null?void 0:a(S.message||"Gagal menghapus","err","alert-triangle"))};return e.jsxs(e.Fragment,{children:[e.jsxs(C,{eyebrow:"telegram",title:"Kredensial Telegram",children:[e.jsxs("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginBottom:10,lineHeight:1.5},children:["Disimpan terenkripsi di database dan berlaku langsung — tanpa mengedit ",e.jsx("code",{children:".env"}),", tanpa restart. Nilai ",e.jsx("code",{children:".env"})," lama tetap dipakai selama field-nya masih kosong."]}),se?e.jsxs(e.Fragment,{children:[se.fields.map((S,Z)=>{var Ce,$;return e.jsx(v,{title:S.label,desc:S.help,last:Z===se.fields.length-1,children:e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8},children:[D(S.source),e.jsx(Y,{"aria-label":S.label,mono:!0,type:S.kind==="secret"?"password":"text",placeholder:S.kind==="secret"?S.masked??((Ce=We(S.key))==null?void 0:Ce.example)??"belum diisi":(($=We(S.key))==null?void 0:$.example)??"belum diisi",value:B[S.key]??(S.kind==="secret"?"":S.value??""),onChange:be=>s(G=>({...G,[S.key]:be.target.value})),style:{width:300}})]})},S.key)}),e.jsx("div",{style:{display:"flex",gap:8,marginTop:12},children:e.jsx(y,{size:"sm",leftIcon:"save",onClick:oe,children:"Simpan kredensial"})})]}):e.jsx(V,{kind:"loading",compact:!0,title:"Memuat kredensial…"})]}),e.jsxs(C,{eyebrow:"uji",title:"Uji koneksi & hapus",actions:e.jsx(y,{size:"sm",variant:"ghost",leftIcon:"refresh-cw",onClick:Ie,children:"Refresh"}),children:[e.jsx(v,{title:"Test Connection",desc:"Mengirim satu pesan percobaan ke chat tujuan. Batas 10 detik — tak pernah menggantung.",children:e.jsx(y,{size:"sm",leftIcon:"send",disabled:w==="sending",onClick:X,children:"Test Connection"})}),w&&w!=="sending"&&e.jsxs("div",{style:{marginTop:10,display:"grid",gap:8},children:[w.ok?e.jsxs(ue,{tone:"ok",children:["Berhasil — bot @",w.botUsername??"?"," mengirim ke chat ",w.chatId,"."]}):e.jsx(ue,{tone:"err",children:w.error}),(ea=w.inbound)!=null&&ea.ok?e.jsx(ue,{tone:"ok",children:"Jalur masuk siap — gateway sedang long polling dan AgentToken-nya sah."}):e.jsxs(ue,{tone:"warn",children:["Jalur masuk BELUM siap: ",((aa=w.inbound)==null?void 0:aa.reason)??"status tak terbaca.",!!((ta=w.inbound)!=null&&ta.missingCapabilities.length)&&e.jsxs(e.Fragment,{children:[" Capability yang kurang: ",e.jsx("code",{children:w.inbound.missingCapabilities.join(", ")}),"."]})," ","Selama ini merah, pesan Telegram tidak akan pernah tertangkap."]})]}),e.jsx(v,{title:"Hapus kredensial",last:!0,desc:"Menghapus keempat nilai dari database. Bila .env lama masih terisi, nilainya kembali dipakai.",children:e.jsx(y,{size:"sm",variant:"danger",leftIcon:"trash-2",onClick:()=>Ge(!0),children:"Hapus kredensial"})}),e.jsx(ba,{open:_,title:"Hapus kredensial Telegram?",message:"Gateway berhenti kecuali nilai .env lama masih tersedia.",confirmLabel:"Hapus",onConfirm:he,onCancel:()=>Ge(!1)})]}),e.jsxs(C,{eyebrow:"gateway",title:"Gateway Telegram",actions:e.jsx(y,{size:"sm",variant:"ghost",leftIcon:"refresh-cw",onClick:ae,children:"Refresh"}),children:[e.jsx("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginBottom:10,lineHeight:1.5},children:"Satu private chat yang diizinkan terikat ke satu session operator Hanoman persisten. Bahasa natural tetap antarmuka utama; command hanya shortcut ke session yang sama."}),e.jsx(v,{title:"Gateway aktif",desc:"Menyalakan long polling in-process seketika. Mematikan tidak membunuh session tmux atau memory.",children:e.jsx(N,{label:"Gateway aktif",checked:E.enabled,onChange:S=>{W({...t,telegram:{...E,enabled:S}},`Gateway Telegram · ${S?"aktif":"nonaktif"}`)}})}),e.jsx(v,{title:"Kirim progress ringkas",desc:"Hanya progress eksplisit dan fakta status Hanoman; layar PTY/reasoning tidak pernah diteruskan.",children:e.jsx(N,{label:"Kirim progress ringkas",checked:E.progress,onChange:S=>{W({...t,telegram:{...E,progress:S}},`Progress Telegram · ${S?"aktif":"nonaktif"}`)}})}),x?e.jsx(V,{kind:"error",compact:!0,title:"Gagal membaca status Telegram",action:ae}):A?e.jsxs(e.Fragment,{children:[e.jsx(v,{title:`Readiness · ${I}`,desc:A.running?"Long poll aktif.":A.lastError??"Gateway belum polling.",children:e.jsx("span",{style:{fontFamily:"var(--font-mono)",fontSize:12},children:A.botUsername?`@${A.botUsername}`:"bot belum terverifikasi"})}),e.jsx(v,{title:"Allowlist",last:!A.missingCapabilities.length,desc:`${A.allowlistCount} Telegram numeric user id diizinkan.`,children:e.jsx("span",{children:A.configured?"kredensial lengkap":"kredensial belum lengkap"})}),A.missingCapabilities.length>0&&e.jsx(v,{title:"Capability kurang",last:!0,desc:A.missingCapabilities.join(", ")})]}):e.jsx(V,{kind:"loading",compact:!0,title:"Memuat status Telegram…"}),e.jsxs("div",{style:{marginTop:14,fontSize:12.5,color:"var(--text-muted)",lineHeight:1.7},children:[e.jsx("b",{children:"Onboarding"}),e.jsxs("ol",{style:{margin:"8px 0 0",paddingLeft:20},children:[e.jsx("li",{children:"Buat satu bot private-chat lewat BotFather, salin token-nya."}),e.jsx("li",{children:"Di Akses AI Agent, aktifkan master switch dan buat AgentToken dengan capability yang ditampilkan status."}),e.jsx("li",{children:"Isi keempat field di kartu Kredensial lalu Simpan."}),e.jsx("li",{children:"Tekan Test Connection sampai hijau."}),e.jsxs("li",{children:["Nyalakan gateway di atas dan kirim ",e.jsx("code",{children:"/status"})," dari Telegram."]})]})]})]})]})}if(g==="umum")return e.jsxs(e.Fragment,{children:[e.jsxs(C,{eyebrow:"general",title:"Umum",children:[e.jsx(v,{title:"Full-auto sebagai default",desc:"Run baru jalan sendiri sampai selesai. Manusia tetap bisa steer / interupsi kapan pun.",children:e.jsx(N,{checked:t.autoDefault,onChange:de("autoDefault","Full-auto default")})}),e.jsx(v,{title:"Auto-scaffold doc index",last:!0,desc:"Project from-scratch otomatis di-scaffold doc index-nya setelah objective terkunci.",children:e.jsx(N,{checked:t.autoScaffold,onChange:de("autoScaffold","Auto-scaffold")})})]}),e.jsxs("div",{style:{display:"flex",alignItems:"center",justifyContent:"space-between",gap:12,padding:"4px 2px",color:"var(--text-subtle)",fontSize:12,fontFamily:"var(--font-mono)"},children:[e.jsx("span",{children:"hanoman · nafanesia.id"}),e.jsx(y,{size:"sm",variant:"ghost",leftIcon:"rotate-ccw",onClick:()=>W(JSON.parse(JSON.stringify(qt)),"Pengaturan dikembalikan ke default","warn","rotate-ccw"),children:"Reset ke default"})]})]});if(g==="orkestrasi")return e.jsx(Xt,{orchestration:t.orchestration,onChange:(E,I)=>H({orchestration:E},I)});if(g==="model"){const E=l=>{var P;const p=(K==null?void 0:K.version)??null;return yt(l,p)?e.jsxs("div",{"data-testid":"codex-version-note",style:{fontSize:12,lineHeight:1.5,marginBottom:10,padding:"8px 10px",borderRadius:8,background:"var(--warn-bg, #fdf6e3)",color:"var(--text-muted)"},children:["Codex CLI terpasang ",e.jsx("b",{children:p}),", sedangkan ",e.jsx("b",{children:l})," butuh ",e.jsx("b",{children:(P=vt(l))==null?void 0:P.minClient}),". Sesi tetap boleh dijalankan, tapi modelnya belum tentu dikenali CLI ini. Perbarui dengan ",e.jsx("code",{children:"npm i -g @openai/codex@latest"}),"."]}):null},I=l=>wa(wt,l),D=t.conflict??Sa,oe=(l,p)=>H({conflict:{...D,...l}},p),X=Be==="codex"?{agent:"codex",model:Q.model,effort:Q.effort}:{agent:"claude",model:t.model,effort:t.effort},he=t.lead??Re,S=he.engine??Re.engine,Z=async(l,p)=>{const P=he;c({...t,lead:{...he,engine:{...S,...l}}});try{const re=await j.getLeadConfig(),Le=await j.putLeadConfig({...re,engine:{...re.engine??Re.engine,...l}});c(xe=>xe&&{...xe,lead:Le}),a==null||a(p,"ok","check-circle-2")}catch{c(re=>re&&{...re,lead:P}),a==null||a("Gagal menyimpan setelan lead","err","alert-triangle")}},Ce=t.telegram??Se,$=Ce.engine??Se.engine,be=async(l,p)=>{const P=t;c({...t,telegram:{...Ce,engine:{...$,...l}}});try{const re=await j.getSettings(),Le=re.telegram??Se,xe={...re,telegram:{...Le,engine:{...Le.engine??Se.engine,...l}}},Aa=await j.putSettings(xe);c(Aa??xe),a==null||a(p,"ok","check-circle-2")}catch{c(P),a==null||a("Gagal menyimpan setelan operator Telegram","err","alert-triangle")}},G=t.changelog??va,Me=(l,p)=>H({changelog:{...G,...l}},p),q=t.portalChat??ya,ke=(l,p)=>H({portalChat:{...q,...l}},p),Ue=(l,p)=>{const P=Math.floor(Number(l));return Number.isFinite(P)&&P>=0?P:p};return e.jsxs(e.Fragment,{children:[e.jsxs(C,{eyebrow:"agen",title:"Agen sesi",children:[e.jsx("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginBottom:10,lineHeight:1.5},children:"Mesin yang menjalankan sesi baru. Perilaku sesi identik — worktree terisolasi, fase, stage, review, integrate. Yang berbeda hanya CLI-nya, dan karenanya katalog model/effort-nya. Indikator limit hanya membaca kuota Anthropic, jadi sesi codex tak muncul di sana."}),e.jsx(v,{title:"Agen default",last:!0,desc:"Sesi backlog masih bisa memilih agen lain saat Start.",children:e.jsx(M,{size:"sm","aria-label":"Agen default",value:Be,style:{width:190},options:[{value:"claude",label:T.claude},{value:"codex",label:T.codex}],onChange:l=>H({agent:l.target.value},"Agen → "+l.target.value)})})]}),e.jsxs(C,{eyebrow:"model",title:"Model sesi — default global",children:[e.jsxs("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginBottom:10,lineHeight:1.5},children:["Default untuk sesi baru; bisa di-override per sesi saat ",e.jsx("b",{children:"Start"}),". Di terminal, ",e.jsx("code",{children:"/model"}),"mengubahnya kapan saja. Dengan orkestrasi aktif, model ini menjadi model ",e.jsx("b",{children:"orchestrator"}),"; model & effort tiap fase diatur di ",e.jsx("b",{children:"tab Orkestrasi"}),". Yang benar-benar dipakai adalah blok milik agen terpilih di atas — yang satunya tersimpan, menunggu giliran."]}),e.jsxs("div",{"data-testid":"agent-group-claude",children:[e.jsxs("div",{style:{padding:"8px 0",color:"var(--text-muted)",fontSize:12},children:["Katalog model diperiksa otomatis setiap 5 menit.",Object.entries(r.providers).map(([l,p])=>e.jsxs("div",{children:[l,": ",p.source==="bundled"?"daftar bawaan":p.source==="cache"?"cache terakhir":"dari CLI",p.checkedAt?" · diperiksa "+new Date(p.checkedAt).toLocaleTimeString():"",p.error?" · "+p.error:""]},l))]}),e.jsx(ha,{id:"claude",label:T.claude,active:Be==="claude"}),e.jsx(v,{title:"Model",desc:"Diteruskan apa adanya ke `claude --model`.",children:e.jsx(M,{size:"sm","aria-label":"Model claude",value:t.model,options:ye(t.model),style:{width:190},onChange:l=>H({model:l.target.value,...ve(l.target.value,t.effort)},"Model claude → "+l.target.value)})}),e.jsx(v,{title:"Effort",last:!0,desc:"Anggaran berpikir per giliran (`claude --effort`).",children:e.jsx(M,{size:"sm","aria-label":"Effort claude",value:t.effort,options:je(t.model),style:{width:130},onChange:l=>H({effort:l.target.value},"Effort claude → "+l.target.value)})})]}),e.jsxs("div",{"data-testid":"agent-group-codex",style:{borderTop:"1px solid var(--border-hair)",marginTop:6},children:[e.jsx(ha,{id:"codex",label:T.codex,active:Be==="codex"}),E(Q.model),e.jsx(v,{title:"Model",desc:"Diteruskan apa adanya ke `codex -m`.",children:e.jsx(M,{size:"sm","aria-label":"Model codex",value:Q.model,style:{width:190},options:I(Q.model),onChange:l=>{const p=l.target.value;H({codex:{model:p,effort:te(p,Q.effort)}},"Model codex → "+p)}})}),e.jsx(v,{title:"Effort",last:!0,desc:"Diteruskan ke `codex -c model_reasoning_effort`.",children:e.jsx(M,{size:"sm","aria-label":"Effort codex",value:Q.effort,style:{width:130},options:Ee(Q.model).map(l=>({value:l,label:l})),onChange:l=>H({codex:{...Q,effort:l.target.value}},"Effort codex → "+l.target.value)})})]})]}),e.jsxs(C,{eyebrow:"konflik",title:"Konflik rebase & merge",children:[e.jsx("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginBottom:10,lineHeight:1.5},children:"Saat rebase/merge berkonflik, hanoman menyerahkan worktree-nya ke satu sesi agen untuk dibereskan. Pekerjaannya sempit dan tak berfase, jadi ia boleh memakai model & effort yang berbeda dari sesi kerja. Berlaku untuk ketiga pintu integrasi: backlog, git graph, dan PRD."}),e.jsx(v,{title:"Pakai setelan sendiri",desc:"Mati = ikut default global di atas. Hidup = sesi konflik memakai pilihan di bawah.",children:e.jsx(N,{"aria-label":"Override agen konflik",checked:D.enabled,onChange:l=>oe({enabled:l},"Setelan konflik"+(l?" · aktif":" · ikut default global"))})}),D.enabled?e.jsxs(e.Fragment,{children:[e.jsx(v,{title:"Agen",desc:"Mesin yang membereskan konflik. Bisa beda dari agen sesi kerja.",children:e.jsx(M,{size:"sm","aria-label":"Agen konflik",value:D.agent,style:{width:190},options:[{value:"claude",label:T.claude},{value:"codex",label:T.codex}],onChange:l=>{const p=l.target.value,P=p==="codex"?Q:{model:t.model,effort:t.effort};oe({agent:p,model:P.model,effort:p==="codex"?te(P.model,P.effort):P.effort},"Agen konflik → "+p)}})}),D.agent==="codex"&&E(D.model),e.jsx(v,{title:"Model",children:e.jsx(M,{size:"sm","aria-label":"Model konflik",value:D.model,style:{width:190},options:D.agent==="codex"?I(D.model):ye(D.model),onChange:l=>{const p=l.target.value;oe({model:p,...D.agent==="codex"?{effort:te(p,D.effort)}:ve(p,D.effort)},"Model konflik → "+p)}})}),e.jsx(v,{title:"Effort",last:!0,desc:"Konflik biasanya mekanis — effort rendah sering cukup.",children:e.jsx(M,{size:"sm","aria-label":"Effort konflik",value:D.effort,style:{width:130},options:D.agent==="codex"?Ee(D.model).map(l=>({value:l,label:l})):je(D.model),onChange:l=>oe({effort:l.target.value},"Effort konflik → "+l.target.value)})})]}):e.jsxs("div",{"data-testid":"conflict-inherited",style:{fontSize:12.5,color:"var(--text-muted)",padding:"12px 0 2px",lineHeight:1.5},children:["Sesi konflik memakai default global: ",e.jsx("b",{children:T[X.agent]})," ·"," ",e.jsx("code",{children:X.model})," · ",e.jsx("code",{children:X.effort}),"."]})]}),e.jsxs(C,{eyebrow:"lead",title:"Agen hanoman-lead",children:[e.jsxs("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginBottom:10,lineHeight:1.5},children:["Mesin yang menjalankan agen pemimpin — panggilan sekali-jalan non-interaktif yang membaca docs, plan, kode, dan riwayat git sebelum memutuskan. Berlaku untuk ketiga pintu lead (kontrak, deteksi otomatis, denyut) dan dipakai putusan berikutnya, tanpa restart. Rem darurat, denyut, dan opt-in per project tetap diurus di layar ",e.jsx("b",{children:"Lead"}),"."]}),e.jsx(v,{title:"Pakai setelan sendiri",desc:"Mati = ikut default global di atas. Hidup = lead memakai pilihan di bawah.",children:e.jsx(N,{"aria-label":"Override agen lead",checked:S.enabled,onChange:l=>Z({enabled:l},"Setelan lead"+(l?" · aktif":" · ikut default global"))})}),S.enabled?e.jsxs(e.Fragment,{children:[e.jsx(v,{title:"Runtime",desc:"Mesin yang menjalankan lead. Bisa beda dari agen sesi kerja.",children:e.jsx(M,{size:"sm","aria-label":"Runtime lead",value:S.agent,style:{width:190},options:[{value:"claude",label:T.claude},{value:"codex",label:T.codex}],onChange:l=>{const p=l.target.value,P=p==="codex"?Q:{model:t.model,effort:t.effort};Z({agent:p,model:P.model,effort:p==="codex"?te(P.model,P.effort):P.effort},"Runtime lead → "+p)}})}),S.agent==="codex"&&E(S.model),e.jsx(v,{title:"Model",children:e.jsx(M,{size:"sm","aria-label":"Model lead",value:S.model,style:{width:190},options:S.agent==="codex"?I(S.model):ye(S.model),onChange:l=>{const p=l.target.value;Z({model:p,...S.agent==="codex"?{effort:te(p,S.effort)}:ve(p,S.effort)},"Model lead → "+p)}})}),e.jsx(v,{title:"Effort",last:!0,desc:"Putusan lead menuntut membaca bukti — SoT, ADR, plan, kode, riwayat git. Effort rendah memangkas kedalamannya.",children:e.jsx(M,{size:"sm","aria-label":"Effort lead",value:S.effort,style:{width:130},options:S.agent==="codex"?Ee(S.model).map(l=>({value:l,label:l})):je(S.model),onChange:l=>Z({effort:l.target.value},"Effort lead → "+l.target.value)})})]}):e.jsxs("div",{"data-testid":"lead-engine-inherited",style:{fontSize:12.5,color:"var(--text-muted)",padding:"12px 0 2px",lineHeight:1.5},children:["hanoman-lead memakai default global: ",e.jsx("b",{children:T[X.agent]})," ·"," ",e.jsx("code",{children:X.model})," · ",e.jsx("code",{children:X.effort}),"."]})]}),e.jsxs(C,{eyebrow:"telegram",title:"Agen operator Telegram",children:[e.jsxs("div",{"data-testid":"telegram-engine-desc",style:{fontSize:12.5,color:"var(--text-muted)",marginBottom:10,lineHeight:1.5},children:["Mesin yang menjalankan sesi operator Telegram — satu sesi persisten per chat, yang membaca API hanoman lalu merangkum. Berlaku untuk sesi operator ",e.jsx("b",{children:"berikutnya"})," yang dibuat; sesi yang sedang jalan tetap memakai setelan lamanya (satu proses, satu model seumur hidup) dan tidak di-restart diam-diam — tutup dari chat dengan ",e.jsx("code",{children:"/engine restart"}),". Setelan yang sama juga bisa diubah dari chat: ",e.jsx("code",{children:"/engine"}),", ",e.jsx("code",{children:"/runtime"}),","," ",e.jsx("code",{children:"/model"}),", ",e.jsx("code",{children:"/effort"}),". Berlaku global untuk semua chat."]}),e.jsx(v,{title:"Pakai setelan sendiri",desc:"Mati = ikut default global di atas. Hidup = sesi operator Telegram memakai pilihan di bawah.",children:e.jsx(N,{"aria-label":"Override agen Telegram",checked:$.enabled,onChange:l=>be({enabled:l},"Setelan operator Telegram"+(l?" · aktif":" · ikut default global"))})}),$.enabled?e.jsxs(e.Fragment,{children:[e.jsx(v,{title:"Runtime",desc:"Mesin yang menjalankan sesi operator. Bisa beda dari agen sesi kerja.",children:e.jsx(M,{size:"sm","aria-label":"Runtime Telegram",value:$.agent,style:{width:190},options:[{value:"claude",label:T.claude},{value:"codex",label:T.codex}],onChange:l=>{const p=l.target.value,P=p==="codex"?Q:{model:t.model,effort:t.effort};be({agent:p,model:P.model,effort:p==="codex"?te(P.model,P.effort):P.effort},"Runtime operator Telegram → "+p)}})}),$.agent==="codex"&&E($.model),e.jsx(v,{title:"Model",children:e.jsx(M,{size:"sm","aria-label":"Model Telegram",value:$.model,style:{width:190},options:$.agent==="codex"?I($.model):ye($.model),onChange:l=>{const p=l.target.value;be({model:p,...$.agent==="codex"?{effort:te(p,$.effort)}:ve(p,$.effort)},"Model operator Telegram → "+p)}})}),e.jsx(v,{title:"Effort",last:!0,desc:"Operator Telegram jarang menulis kode — effort rendah memangkas latensi balasan secara langsung.",children:e.jsx(M,{size:"sm","aria-label":"Effort Telegram",value:$.effort,style:{width:130},options:$.agent==="codex"?Ee($.model).map(l=>({value:l,label:l})):je($.model),onChange:l=>be({effort:l.target.value},"Effort operator Telegram → "+l.target.value)})})]}):e.jsxs("div",{"data-testid":"telegram-engine-inherited",style:{fontSize:12.5,color:"var(--text-muted)",padding:"12px 0 2px",lineHeight:1.5},children:["Sesi operator Telegram memakai default global: ",e.jsx("b",{children:T[X.agent]})," ·"," ",e.jsx("code",{children:X.model})," · ",e.jsx("code",{children:X.effort}),"."]})]}),e.jsxs(C,{eyebrow:"changelog",title:"Agen changelog",children:[e.jsxs("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginBottom:10,lineHeight:1.5},children:["Mesin yang menulis narasi changelog per project — panggilan sekali-jalan non-interaktif yang merangkum backlog selesai, rentang commit, atau isi sebuah rilis menjadi teks pendek berorientasi pemakai. Berlaku pada pembangkitan ",e.jsx("b",{children:"berikutnya"}),", tanpa restart. Agen yang gagal tak menggagalkan changelog: barisnya tetap lahir sebagai draf ringkas ber-catatan."]}),e.jsx(v,{title:"Pakai setelan sendiri",desc:"Mati = ikut default global di atas. Hidup = pembuat changelog memakai pilihan di bawah.",children:e.jsx(N,{"aria-label":"Override agen changelog",checked:G.enabled,onChange:l=>Me({enabled:l},"Setelan changelog"+(l?" · aktif":" · ikut default global"))})}),G.enabled?e.jsxs(e.Fragment,{children:[e.jsx(v,{title:"Runtime",desc:"Mesin yang menulis changelog. Bisa beda dari agen sesi kerja.",children:e.jsx(M,{size:"sm","aria-label":"Runtime changelog",value:G.agent,style:{width:190},options:[{value:"claude",label:T.claude},{value:"codex",label:T.codex}],onChange:l=>{const p=l.target.value,P=p==="codex"?Q:{model:t.model,effort:t.effort};Me({agent:p,model:P.model,effort:p==="codex"?te(P.model,P.effort):P.effort},"Runtime changelog → "+p)}})}),G.agent==="codex"&&E(G.model),e.jsx(v,{title:"Model",children:e.jsx(M,{size:"sm","aria-label":"Model changelog",value:G.model,style:{width:190},options:G.agent==="codex"?I(G.model):ye(G.model),onChange:l=>{const p=l.target.value;Me({model:p,...G.agent==="codex"?{effort:te(p,G.effort)}:ve(p,G.effort)},"Model changelog → "+p)}})}),e.jsx(v,{title:"Effort",last:!0,desc:"Merangkum judul jadi prosa pendek — effort rendah biasanya cukup dan memangkas ongkos setiap pembangkitan.",children:e.jsx(M,{size:"sm","aria-label":"Effort changelog",value:G.effort,style:{width:130},options:G.agent==="codex"?Ee(G.model).map(l=>({value:l,label:l})):je(G.model),onChange:l=>Me({effort:l.target.value},"Effort changelog → "+l.target.value)})})]}):e.jsxs("div",{"data-testid":"changelog-engine-inherited",style:{fontSize:12.5,color:"var(--text-muted)",padding:"12px 0 2px",lineHeight:1.5},children:["Pembuat changelog memakai default global: ",e.jsx("b",{children:T[X.agent]})," ·"," ",e.jsx("code",{children:X.model})," · ",e.jsx("code",{children:X.effort}),"."]})]}),e.jsxs(C,{eyebrow:"portal",title:"Obrolan portal klien",children:[e.jsxs("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginBottom:10,lineHeight:1.5},children:["Permukaan obrolan di portal klien: klien memilih ",e.jsx("b",{children:"Brainstorming"})," (digali sampai jelas, keluarannya PRD draft untuk Anda review) atau ",e.jsx("b",{children:"Bertanya"})," (dijawab langsung dari dokumen project). Jatah berlaku ",e.jsx("b",{children:"per project"})," dan dihitung bulanan; brainstorming dan pertanyaan punya jatah masing-masing. Mati = tab Obrolan tak muncul di portal."]}),e.jsx(v,{title:"Aktifkan obrolan",desc:"Mati = klien hanya punya Pekerjaan & Help desk, persis seperti sebelumnya.",children:e.jsx(N,{"aria-label":"Aktifkan obrolan portal",checked:q.enabled,onChange:l=>ke({enabled:l},"Obrolan portal"+(l?" · aktif":" · mati"))})}),q.enabled&&e.jsxs(e.Fragment,{children:[e.jsx(v,{title:"Jatah brainstorming",desc:"Berapa sesi brainstorming per project tiap bulan. 0 = tertutup.",children:e.jsx(Y,{"aria-label":"Jatah brainstorming",type:"number",min:0,max:1e3,placeholder:"mis. 3",style:{width:110},defaultValue:q.brainstormPerMonth,onBlur:l=>ke({brainstormPerMonth:Ue(l.target.value,q.brainstormPerMonth)},"Jatah brainstorming → "+l.target.value)})}),e.jsx(v,{title:"Jatah pertanyaan",desc:"Berapa sesi tanya-jawab per project tiap bulan. 0 = tertutup.",children:e.jsx(Y,{"aria-label":"Jatah pertanyaan",type:"number",min:0,max:1e4,placeholder:"mis. 30",style:{width:110},defaultValue:q.askPerMonth,onBlur:l=>ke({askPerMonth:Ue(l.target.value,q.askPerMonth)},"Jatah pertanyaan → "+l.target.value)})}),e.jsx(v,{title:"Model",desc:"Mesin obrolan selalu Claude — gerbang tool yang menjaganya tak ada di runtime lain.",children:e.jsx(M,{size:"sm","aria-label":"Model obrolan portal",value:q.model,style:{width:190},options:ye(q.model),onChange:l=>ke({model:l.target.value,...ve(l.target.value,q.effort)},"Model obrolan portal → "+l.target.value)})}),e.jsx(v,{title:"Effort",children:e.jsx(M,{size:"sm","aria-label":"Effort obrolan portal",value:q.effort,style:{width:130},options:je(q.model),onChange:l=>ke({effort:l.target.value},"Effort obrolan portal → "+l.target.value)})}),e.jsx(v,{title:"Batas waktu satu jawaban",last:!0,desc:"Detik. Lewat dari ini klien menerima kalimat 'coba lagi sebentar', bukan layar menggantung.",children:e.jsx(Y,{"aria-label":"Batas waktu obrolan portal",type:"number",min:10,max:900,placeholder:"mis. 120",style:{width:110},defaultValue:q.timeoutSec,onBlur:l=>ke({timeoutSec:Ue(l.target.value,q.timeoutSec)},"Batas waktu obrolan → "+l.target.value)})})]})]})]})}return e.jsxs(e.Fragment,{children:[e.jsxs(C,{eyebrow:"sesi",title:"Sesi & notifikasi",children:[e.jsx(v,{title:"Notifikasi backlog selesai",desc:"Toast + sound saat sebuah backlog mencapai stage done. Daftar lonceng tetap terisi meski dimatikan.",children:e.jsx(N,{checked:t.notifyDone,onChange:de("notifyDone","Notifikasi backlog selesai")})}),e.jsx(v,{title:"Sound notifikasi",desc:"Durasi nada saat backlog selesai.",children:e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8},children:[e.jsx(M,{size:"sm",value:t.notifySound,options:ga,style:{width:160},onChange:E=>H({notifySound:E.target.value},"Sound → "+E.target.value)}),e.jsx(y,{size:"sm",variant:"ghost",leftIcon:"volume-2",disabled:t.notifySound==="off",onClick:()=>ia(t.notifySound),children:"Preview"})]})}),e.jsx(v,{title:"Notifikasi butuh keputusan",desc:"Toast + sound saat sesi Claude berhenti menunggu keputusanmu. Nada sengaja beda dari selesai.",children:e.jsx(N,{checked:t.notifyDecision,onChange:de("notifyDecision","Notifikasi keputusan")})}),e.jsx(v,{title:"Sound keputusan",desc:"Nada saat sebuah sesi menunggu keputusan.",children:e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8},children:[e.jsx(M,{size:"sm",value:t.notifyDecisionSound,options:ga,style:{width:160},onChange:E=>H({notifyDecisionSound:E.target.value},"Sound keputusan → "+E.target.value)}),e.jsx(y,{size:"sm",variant:"ghost",leftIcon:"volume-2",disabled:t.notifyDecisionSound==="off",onClick:()=>ia(t.notifyDecisionSound),children:"Preview"})]})}),e.jsx(v,{title:"Notifikasi saat sesi gagal",last:!0,desc:"Kirim notifikasi ketika sesi Claude Code berakhir dengan error.",children:e.jsx(N,{checked:t.notifyFail,onChange:de("notifyFail","Notifikasi gagal")})})]}),e.jsxs(C,{eyebrow:"goal",title:"Mode goal — sesi backlog",children:[e.jsxs("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginBottom:10,lineHeight:1.5},children:["Sesi backlog lahir dengan gate ",e.jsx("code",{children:"Stop"}),": ia menolak berhenti sampai kondisinya terbukti di transkrip. Interupsi manusia (",e.jsx("code",{children:"Esc"}),") tetap bekerja; melepas gate sepenuhnya = hentikan sesinya. Sesi scheduler mengikuti setelan ini."]}),e.jsx(v,{title:"Aktif sebagai default",desc:"Sesi backlog baru lahir dengan mode goal. Masih bisa dimatikan per sesi saat Start.",children:e.jsx(N,{"aria-label":"Mode goal default",checked:t.goal.enabled,onChange:E=>H({goal:{...t.goal,enabled:E}},"Mode goal"+(E?" · aktif":" · nonaktif"))})}),e.jsx(v,{title:"Kondisi (template global)",last:!0,desc:"Kosong = kondisi bawaan hanoman: semua fase tercatat di phase file, plan tak menyisakan task, push sukses.",children:e.jsx("div",{style:{width:320},children:e.jsx(xt,{value:t.goal.condition,rows:4,mono:!0,"aria-label":"Kondisi mode goal",placeholder:"Kosong = kondisi bawaan hanoman · mis. semua fase tercatat & plan tanpa - [ ]",onChange:E=>W({...t,goal:{...t.goal,condition:E.target.value}})})})})]}),e.jsxs(C,{eyebrow:"verifikasi",title:"Scope verifikasi — sesi backlog",children:[e.jsxs("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginBottom:10,lineHeight:1.5},children:["Beberapa sesi berjalan bersamaan di mesin ini. ",e.jsx("b",{children:"Hanya yang berubah"})," menyuruh sesi menguji berkas yang benar-benar ia sentuh (",e.jsx("code",{children:"vitest --changed"}),", typecheck per paket, lint per berkas) alih-alih seluruh project — RAM & CPU tetap tersisa untuk sesi lain. Suite penuh, lint penuh, dan build penuh tetap dijalankan manusia sebelum merge."]}),e.jsx(v,{title:"Scope default",last:!0,desc:"Sesi backlog baru lahir dengan scope ini. Masih bisa diubah per sesi saat Start.",children:e.jsx(M,{size:"sm","aria-label":"Scope verifikasi default",value:t.verifyScope??"changed",style:{width:220},options:[{value:"changed",label:"Hanya yang berubah"},{value:"full",label:"Seluruh project"}],onChange:E=>H({verifyScope:E.target.value},"Scope verifikasi → "+E.target.value)})})]}),e.jsxs(C,{eyebrow:"metode",title:"Metode workflow — sesi backlog",children:[e.jsxs("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginBottom:10,lineHeight:1.5},children:["Metode menentukan ",e.jsx("b",{children:"skill mana yang dimuat di tiap fase"})," dan ",e.jsx("b",{children:"di direktori mana plan & spec ditulis"}),". Fase-fasenya sendiri tak berubah. Gerbang plan memindai direktori SEMUA metode, jadi item yang berpindah metode tak pernah lolos lewat direktori kosong."]}),e.jsx(v,{title:"Metode default",last:!0,desc:"Sesi backlog baru lahir dengan metode ini. Masih bisa diubah per sesi saat Start.",children:e.jsx(M,{size:"sm","aria-label":"Metode default",style:{width:220},value:ft(t.method).id,options:la.map(E=>({value:E,label:sa[E].label})),onChange:E=>H({method:E.target.value},"Metode → "+E.target.value)})}),ge&&e.jsxs("div",{"data-testid":"method-status",style:{marginTop:4,display:"grid",gap:10},children:[la.map(E=>e.jsxs("div",{style:{display:"grid",gap:6},children:[e.jsx("div",{style:{fontSize:12.5,fontWeight:600},children:sa[E].label}),ge.methods.filter(I=>I.method===E).map(I=>e.jsxs("div",{"data-testid":`method-status-${I.method}-${I.agent}`,style:{display:"flex",alignItems:"flex-start",gap:8,fontSize:12,lineHeight:1.5},children:[e.jsx(ne,{tone:I.ready?"ok":"warn",children:I.ready?"siap":"belum siap"}),e.jsxs("div",{style:{flex:"1 1 auto",minWidth:0},children:[e.jsx("b",{children:T[I.agent]}),!I.ready&&e.jsxs("div",{style:{color:"var(--text-muted)",overflowWrap:"anywhere"},children:[I.missingPackages.length>0&&e.jsxs("div",{children:["paket kurang: ",e.jsx("code",{children:I.missingPackages.join(" · ")})]}),I.missingSkills.length>0&&e.jsxs("div",{children:["skill kurang: ",e.jsx("code",{children:I.missingSkills.join(" · ")})]})]})]}),!I.ready&&e.jsx(y,{size:"sm",variant:"ghost",leftIcon:"download",disabled:!ie,onClick:()=>Pe(I),children:"Pasang"})]},I.agent))]},E)),e.jsx("div",{style:{fontSize:12,color:"var(--text-muted)",lineHeight:1.5},children:ie?e.jsxs(e.Fragment,{children:["Pemasangan berjalan di ",e.jsx("b",{children:"sesi terminal"})," project ",e.jsx("code",{children:ie})," — outputnya bisa ditonton di Terminal. hanoman sendiri tak pernah memasang apa pun."]}):e.jsx(e.Fragment,{children:"Pemasangan butuh satu project yang sudah di-bind ke checkout lokal: perintahnya dijalankan di sesi terminal project itu."})})]})]})]})}const Ea=g==="setup"?e.jsx(dn,{}):g==="akun"?e.jsx(Qt,{me:n,onLoggedOut:i,onToast:a}):g==="users"?e.jsx(Zt,{me:n,onToast:a}):g==="akses-klien"?e.jsx(zt,{}):g==="perangkat"?e.jsx(en,{onToast:a}):g==="kendali-jarak-jauh"?e.jsx(Pt,{onToast:a}):g==="agent"?e.jsx(rn,{onToast:a}):g==="custom-agent"?e.jsxs(C,{eyebrow:"agen",title:"Custom agent — global",children:[e.jsxs("div",{style:{fontSize:12.5,color:"var(--text-muted)",marginBottom:12,lineHeight:1.5},children:["Persona yang tersedia untuk ",e.jsx("b",{children:"setiap sesi baru"})," di semua project. Sesi ",e.jsx("b",{children:"claude"}),"dan ",e.jsx("b",{children:"codex"})," menerima child native dengan konteks terpisah. Agen boleh saling memanggil lewat ",e.jsx("i",{children:"Mention"}),", dan grafnya wajib asiklik — agen tanpa mention tak diberi alat delegasi sama sekali."]}),e.jsx(dt,{projectId:null,runtime:t==null?void 0:t.agent,onToast:a})]}):g==="aktivitas"?e.jsx(an,{onToast:a}):g==="konfigurasi"?e.jsx(nn,{onToast:a}):g==="webhook"?R?e.jsx(Ft,{onBack:()=>F(!1)}):e.jsx(Dt,{onToast:a,onOpenDocs:()=>F(!0)}):Ca();return e.jsxs("div",{className:"hn-settings-layout","data-layout":k,style:{display:"grid",gridTemplateColumns:z?"minmax(0, 1fr)":k==="tablet"?"160px minmax(0, 1fr)":"196px minmax(0, 1fr)",gap:k==="tablet"?16:24,alignItems:"start",maxWidth:920},children:[z?e.jsx(M,{"aria-label":"Bagian pengaturan",value:g,onChange:W=>u(W.target.value),options:pa.map(W=>({value:W.key,label:W.label}))}):e.jsx("nav",{"aria-label":"Navigasi pengaturan",style:{display:"flex",flexDirection:"column",gap:2,position:"sticky",top:0},children:pa.map(W=>{const H=W.key===g;return e.jsxs("button",{"aria-current":H?"page":void 0,onClick:()=>u(W.key),style:{display:"flex",alignItems:"center",gap:10,width:"100%",padding:"9px 12px",border:"none",cursor:"pointer",textAlign:"left",borderRadius:"var(--radius-sm)",background:H?"var(--brass-100)":"transparent",color:H?"var(--brass-700)":"var(--text-muted)",fontWeight:H?600:500,fontFamily:"var(--font-ui)",fontSize:13.5},children:[e.jsx(Fe,{name:W.icon,size:16,color:H?"var(--brass-700)":"var(--text-subtle)"}),W.label]},W.key)})}),e.jsx("div",{style:{display:"flex",flexDirection:"column",gap:20,minWidth:0},children:Ea})]})}export{rn as AgentAccessPanel,en as DeviceTokensPanel,mn as SettingsScreen};
|