hanoman 0.9.32 → 0.9.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/dist/build-info.json +3 -3
  2. package/dist/cli.js +240 -130
  3. package/dist/server.js +21995 -20480
  4. package/package.json +1 -1
  5. package/prisma/migrations/20261020120000_lead_session_inbox/migration.sql +42 -0
  6. package/prisma/schema.prisma +46 -0
  7. package/web/assets/{DalangHanomanScreen-DdsEmtBl.js → DalangHanomanScreen-DAXIQxrw.js} +1 -1
  8. package/web/assets/{DocPreviewModal-BbOqdfaH.js → DocPreviewModal-Cu0UFzPH.js} +1 -1
  9. package/web/assets/{DocsWorkspace-Cr7AXz9z.js → DocsWorkspace-DElNcoar.js} +1 -1
  10. package/web/assets/{IdeScreen-hdbQW-ui.js → IdeScreen-BBr567gf.js} +1 -1
  11. package/web/assets/LeadScreen-CYn8RS3H.js +1 -0
  12. package/web/assets/{ReviewScreen-BIkpEspG.js → ReviewScreen-zAvwSL50.js} +1 -1
  13. package/web/assets/{SchedulerScreen-3KVE-d_d.js → SchedulerScreen-BptlSAOx.js} +1 -1
  14. package/web/assets/{SettingsScreen-B0-W2TeR.js → SettingsScreen-a0Cx4M4j.js} +3 -3
  15. package/web/assets/{TeamScreen-DNdt2P1g.js → TeamScreen-Cm_phItS.js} +1 -1
  16. package/web/assets/{TerminalScreen-BjQF48XI.js → TerminalScreen-CFuNRq6d.js} +1 -1
  17. package/web/assets/{TriageScreen-CzM99CrG.js → TriageScreen-ChTidhCT.js} +1 -1
  18. package/web/assets/{VpsScreen-SGB_BpbS.js → VpsScreen-8vz0QHO-.js} +1 -1
  19. package/web/assets/{fields-of8G961J.js → fields-Bhl01LnE.js} +1 -1
  20. package/web/assets/{index-C5P4ykKO.js → index-R2HBkTbR.js} +207 -207
  21. package/web/assets/{live-DQen7wLm.js → live-V9nWiHoE.js} +1 -1
  22. package/web/assets/{sched-lead-shared-C34TvLVv.js → sched-lead-shared-Dr48bY7n.js} +1 -1
  23. package/web/assets/{settings-rebase-DHIWk4mV.js → settings-rebase-DOqNAynI.js} +1 -1
  24. package/web/index.html +1 -1
  25. package/web/assets/LeadScreen-BWE4mfJI.js +0 -1
@@ -1,4 +1,4 @@
1
- import{bp as ea,bq as aa,br as na,bs as ta,bt as ia,bu as sa,bv as la,bw as ra,bx as da,by as ca,bz as oa,bA as ua,bB as ha,bC as ma,bD as ga,bE as pa,bF as ka,bG as ba,bH as ja,bI as fa,bJ as xa,bK as va,bL as ya,h as y,i as K,j as e,f as q,bM as wa,bN as Sa,a7 as Ce,bO as Na,a6 as de,ae as Ca,bP as Ea,a8 as Ee,aI as M,b2 as J,g as T,B as x,bQ as Pe,bd as Aa,aY as be,a9 as Oa,aa as Pa,ab as Ma,bR as Ia,bS as Da,bT as _a,bU as Me,bV as Ie,n as X,H as La,bW as ae,bX as se,bY as Ba,bZ as we,d as ie,I as le,m as W,C as te,e as Z,aJ as oe,b_ as Ha,b$ as Ge,c0 as ge,c1 as Ue,c2 as Ra,c3 as ue,c4 as Ae,c5 as za,c6 as De,c7 as Fa,c8 as $a,c9 as Ka,ca as ke,$ as Ga,b as Te,cb as Ua,cc as Ta,cd as Wa,ah as Se,q as Ja,D as qa,ce as Va,cf as Ya}from"./index-C5P4ykKO.js";import{R as c}from"./react-DfGhg7ZW.js";import"./markdown-CWEY34jk.js";import{S as Oe,s as We,u as $,c as Je,a as E,b as _e,P as G,d as je,e as ne,A as Qa,f as qe,g as Ve,C as Xa,h as Za,F as pe,E as Ne,N as fe,B as he,i as en,n as Le,j as an,r as nn,D as tn}from"./fields-of8G961J.js";import{r as Ye,O as sn}from"./settings-rebase-DHIWk4mV.js";import{D as ln}from"./DocPreviewModal-BbOqdfaH.js";import"./xterm-3VOAfa_q.js";/* empty css */const rn=[...ea,...aa,...na,...ta,...ia,...sa,...la,...ra,...da,...ca,...oa,...ua,...ha,...ma,...ga,...pa,...ka,...ba,...ja,...fa,...xa,...va,...ya],dn=2,Be=()=>{};function cn(a){const[t,i]=c.useState(null),[r,d]=c.useState(!1),h=c.useRef(null),l=c.useRef(a);l.current=a;const s=c.useRef(Promise.resolve()),m=c.useCallback(g=>{h.current=g,i(g)},[]),j=c.useCallback(()=>{d(!1),m(null),y.getSettings().then(g=>m(g),()=>d(!0))},[m]);c.useEffect(()=>{j()},[j]);const k=c.useCallback((g,S,P,w)=>{const D=h.current;if(!D)return Promise.resolve(!1);const O=Object.keys(g).filter(I=>JSON.stringify(g[I])!==JSON.stringify(D[I]));m(g);const N=s.current.then(async()=>{const I=await y.getSettings().catch(()=>{});await y.putSettings(Ye(D,g,I))});return s.current=N.then(Be,Be),N.then(()=>{var I;return S&&((I=l.current)==null||I.call(l,S,P||"ok",w||"check-circle-2")),!0},I=>{var C;const z=Object.fromEntries(O.map(H=>[H,D[H]]));return m({...h.current??D,...z}),(C=l.current)==null||C.call(l,K("menyimpan pengaturan",I),"err","alert-triangle"),!1})},[m]),b=c.useCallback((g,S)=>{const P=h.current;return P?k({...P,...g},S):Promise.resolve(!1)},[k]),p=c.useCallback(g=>{const S=h.current;S&&m(g(S))},[m]),f=c.useCallback(()=>h.current,[]);return{s:t,failed:r,load:j,persist:k,save:b,update:p,current:f}}function on(a){const t=a.trim().toLowerCase();return t?Oe.flatMap(i=>i.sections).filter(i=>`${i.label} ${i.keywords}`.toLowerCase().includes(t)).map(i=>({key:i.key,path:We(i.key)})):[]}const xe=(a,t)=>(a??"").toLowerCase().includes(t);function un(a,t){if(!a)return 0;const i=t.trim().toLowerCase(),r=Array.from(a.querySelectorAll("[data-setting-row]")),d=Array.from(a.querySelectorAll("[data-setting-card]")),h=s=>{var m;return((m=s.parentElement)==null?void 0:m.closest("[data-setting-card]"))??null};let l=0;return i?(d.forEach(s=>{const m=xe(s.getAttribute("data-search"),i),j=Array.from(s.querySelectorAll("[data-setting-row]")).filter(b=>h(b)===s);if(j.length===0){s.hidden=!m,m&&l++;return}let k=!1;j.forEach(b=>{const p=m||xe(b.getAttribute("data-search"),i);b.hidden=!p,p&&(k=!0,l++)}),s.hidden=!(m||k)}),r.forEach(s=>{if(h(s))return;const m=xe(s.getAttribute("data-search"),i);s.hidden=!m,m&&l++}),l):(r.forEach(s=>{s.hidden=!1}),d.forEach(s=>{s.hidden=!1}),r.length+d.filter(s=>!s.querySelector("[data-setting-row]")).length)}function hn(a,t,i){const[r,d]=c.useState(0);return c.useLayoutEffect(()=>{const h=a.current,l=()=>d(un(h,t));if(l(),!h||typeof MutationObserver>"u")return;const s=new MutationObserver(l);return s.observe(h,{childList:!0,subtree:!0}),()=>s.disconnect()},[t,i,a]),r}function mn({value:a,onChange:t,hits:i,onPick:r}){return e.jsxs("div",{className:"hn-set-search",role:"search",children:[e.jsx(q,{type:"search","aria-label":"Cari pengaturan",placeholder:"mis. notifikasi, telegram, token",value:a,leftIcon:"search",className:"hn-set-full",onChange:d=>t(d.target.value),onKeyDown:d=>{d.key==="Escape"&&a&&t("")}}),a.trim()!==""&&i.length>0&&e.jsx("ul",{className:"hn-set-search-hits","aria-label":"Bagian yang cocok",children:i.map(d=>e.jsx("li",{children:e.jsx("button",{type:"button",className:"hn-set-search-hit",onClick:()=>r(d.key),children:d.path})},d.key))})]})}const gn={default:null,tampil:!0,sembunyi:!1};function re(a,t,i,r){return $({value:i,delayMs:0,save:d=>a.save({[t]:d},r+(d?" · aktif":" · nonaktif"))})}function pn({s:a,store:t,onToast:i,codexVer:r}){const d=wa(),[h,l]=c.useState(null),[s,m]=c.useState("");c.useEffect(()=>{y.getMethodStatus().then(l).catch(()=>l(null)),y.listProjects({limit:100}).then(n=>m(v=>{var F;return v||(((F=n.items.find(ce=>ce.binding??ce.repoDir))==null?void 0:F.id)??"")})).catch(n=>i==null?void 0:i(K("memuat daftar project",n),"err","alert-triangle"))},[]);const[j,k]=Sa(),b=j===null?"default":j?"tampil":"sembunyi",p=re(t,"notifyDone",a.notifyDone,"Notifikasi backlog selesai"),f=$({value:a.notifySound,delayMs:0,save:n=>t.save({notifySound:n},"Sound → "+n)}),g=re(t,"notifyDecision",a.notifyDecision,"Notifikasi keputusan"),S=re(t,"notifyDecisionTelegram",a.notifyDecisionTelegram,"Keputusan ke Telegram"),P=$({value:a.notifyDecisionSound,delayMs:0,save:n=>t.save({notifyDecisionSound:n},"Sound keputusan → "+n)}),w=re(t,"notifyFail",a.notifyFail,"Notifikasi gagal"),D=re(t,"autoDefault",a.autoDefault,"Full-auto default"),O=re(t,"autoScaffold",a.autoScaffold,"Auto-scaffold"),N=a.agent??"claude",I=a.codex??Ce,z=$({value:N,delayMs:0,save:n=>t.save({agent:n},"Agen → "+n)}),C=$({value:a.model,delayMs:0,save:n=>t.save({model:n,...Je(n,a.effort)},"Model claude → "+n)}),H=$({value:a.effort,delayMs:0,save:n=>t.save({effort:n},"Effort claude → "+n)}),L=$({value:I.model,delayMs:0,save:n=>t.save({codex:{model:n,effort:Na(n,I.effort)}},"Model codex → "+n)}),Q=$({value:I.effort,delayMs:0,save:n=>t.save({codex:{...I,effort:n}},"Effort codex → "+n)}),V=a.antigravity??de,Y=$({value:V.model,delayMs:0,save:n=>t.save({antigravity:{model:n,effort:Ca("antigravity",n,V.effort)}},"Model Antigravity → "+n)}),U=$({value:V.effort,delayMs:0,save:n=>t.save({antigravity:{...V,effort:n}},"Effort Antigravity → "+n)}),o=$({value:a.goal.enabled,delayMs:0,save:n=>t.save({goal:{...a.goal,enabled:n}},"Mode goal"+(n?" · aktif":" · nonaktif"))}),A=$({value:a.goal.condition,save:n=>t.save({goal:{...a.goal,condition:n}},"Kondisi mode goal disimpan")}),B=$({value:a.verifyScope??"changed",delayMs:0,save:n=>t.save({verifyScope:n},"Scope verifikasi → "+n)}),u=$({value:Ea(a.method).id,delayMs:0,save:n=>t.save({method:n},"Metode → "+n)}),_=async n=>{if(s)try{await y.createShell(s,{method:n.method,agent:n.agent}),i==null||i(`Pemasangan ${n.label} · ${ne[n.agent]} berjalan di Terminal`,"ok","terminal")}catch{i==null||i("Gagal membuka sesi terminal pemasangan","err","alert-triangle")}},R={agent:N,...Ee({claude:{model:a.model,effort:a.effort},codex:I,antigravity:a.antigravity??de},N)};return e.jsxs(e.Fragment,{children:[e.jsxs(M,{eyebrow:"sesi",title:"Sesi & notifikasi","data-setting-card":!0,"data-search":"sesi notifikasi suara telegram keputusan",children:[e.jsx(E,{title:"Notifikasi backlog selesai",desc:"Toast + sound saat sebuah backlog mencapai stage done. Daftar lonceng tetap terisi meski dimatikan.",save:p,children:e.jsx(J,{"aria-label":"Notifikasi backlog selesai",checked:p.value,onChange:n=>p.set(n)})}),e.jsx(E,{title:"Sound notifikasi",desc:"Durasi nada saat backlog selesai.",save:f,children:e.jsxs("div",{className:"hn-set-inline",children:[e.jsx(T,{size:"sm","aria-label":"Sound notifikasi",value:f.value,options:_e,className:"hn-set-w-sm",onChange:n=>f.set(n.target.value)}),e.jsx(x,{size:"sm",variant:"ghost",leftIcon:"volume-2",disabled:f.value==="off",onClick:()=>Pe(f.value),children:"Preview"})]})}),e.jsx(E,{title:"Notifikasi butuh keputusan",desc:"Toast + sound saat sesi Claude berhenti menunggu keputusanmu. Nada sengaja beda dari selesai.",save:g,children:e.jsx(J,{"aria-label":"Notifikasi keputusan",checked:g.value,onChange:n=>g.set(n)})}),e.jsx(E,{title:"Kirim keputusan ke Telegram",desc:"Pesan ke user id di allowlist Telegram saat sesi menunggu keputusanmu, supaya tak menunggu sampai dashboard dibuka. Perlu bot token + allowlist; gateway percakapan tak perlu berjalan.",save:S,children:e.jsx(J,{"aria-label":"Kirim keputusan ke Telegram",checked:S.value,onChange:n=>S.set(n)})}),e.jsx(E,{title:"Sound keputusan",desc:"Nada saat sebuah sesi menunggu keputusan.",save:P,children:e.jsxs("div",{className:"hn-set-inline",children:[e.jsx(T,{size:"sm","aria-label":"Sound keputusan",value:P.value,options:_e,className:"hn-set-w-sm",onChange:n=>P.set(n.target.value)}),e.jsx(x,{size:"sm",variant:"ghost",leftIcon:"volume-2",disabled:P.value==="off",onClick:()=>Pe(P.value),children:"Preview"})]})}),e.jsx(E,{title:"Notifikasi saat sesi gagal",last:!0,desc:"Kirim notifikasi ketika sesi Claude Code berakhir dengan error.",save:w,children:e.jsx(J,{"aria-label":"Notifikasi gagal",checked:w.value,onChange:n=>w.set(n)})})]}),e.jsxs(M,{eyebrow:"umum",title:"Perilaku sesi","data-setting-card":!0,"data-search":"perilaku sesi full-auto scaffold doc index",children:[e.jsx(E,{title:"Full-auto sebagai default",desc:"Run baru jalan sendiri sampai selesai. Manusia tetap bisa steer / interupsi kapan pun.",save:D,children:e.jsx(J,{"aria-label":"Full-auto sebagai default",checked:D.value,onChange:n=>D.set(n)})}),e.jsx(E,{title:"Auto-scaffold doc index",desc:"Project yang dibuat dari nol otomatis disiapkan indeks dokumennya setelah tujuannya terkunci.",save:O,children:e.jsx(J,{"aria-label":"Auto-scaffold",checked:O.value,onChange:n=>O.set(n)})}),e.jsx(E,{title:"Tampilkan Hanoman (pet)",last:!0,search:"pet hanoman maskot tampil sembunyi",desc:"Pendamping kecil di sudut layar. Pilihan ini hanya berlaku di browser ini. Default: aktif di desktop, mati di tablet & ponsel.",children:e.jsxs(Aa,{"aria-label":"Tampilkan Hanoman (pet)",direction:"row",value:b,onChange:n=>k(gn[n]??null),children:[e.jsx(be,{value:"default",label:"Default"}),e.jsx(be,{value:"tampil",label:"Tampilkan"}),e.jsx(be,{value:"sembunyi",label:"Sembunyikan"})]})})]}),e.jsxs(M,{eyebrow:"model",title:"Agen & model — default global","data-setting-card":!0,"data-search":"agen model default effort claude codex agen sesi baru",children:[e.jsxs(G,{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 bagian ",e.jsx("b",{children:"Orkestrasi"})," pada Agen & model. Yang benar-benar dipakai adalah blok milik agen terpilih di bawah — blok runtime lain tersimpan, menunggu giliran."]}),e.jsx(E,{title:"Agen default",desc:"Sesi backlog masih bisa memilih agen lain saat Start.",save:z,children:e.jsx(T,{size:"sm","aria-label":"Agen default",value:z.value,className:"hn-set-w-sm",options:Oa,onChange:n=>z.set(n.target.value)})}),e.jsxs("div",{"data-testid":"agent-group-antigravity",className:"hn-set-group",children:[e.jsx(je,{id:"antigravity",label:ne.antigravity,active:N==="antigravity"}),e.jsx(Qa,{}),e.jsx(E,{title:"Model",desc:"Model parent diteruskan ke agy --model.",save:Y,children:e.jsx(T,{size:"sm","aria-label":"Model antigravity",value:Y.value,options:Pa("antigravity",Y.value),onChange:n=>Y.set(n.target.value)})}),e.jsx(E,{title:"Effort",last:!0,desc:"Effort mengikuti kemampuan slug model parent.",save:U,children:e.jsx(T,{size:"sm","aria-label":"Effort antigravity",value:U.value,options:Ma("antigravity",Y.value).map(n=>({value:n,label:n})),onChange:n=>U.set(n.target.value)})})]}),e.jsxs("div",{"data-testid":"agent-group-claude",className:"hn-set-group",children:[e.jsxs("div",{className:"hn-set-catalog",children:["Katalog model diperiksa otomatis setiap 5 menit.",Object.entries(d.providers).map(([n,v])=>e.jsxs("div",{children:[n,": ",v.source==="bundled"?"daftar bawaan":v.source==="cache"?"cache terakhir":"dari CLI",v.checkedAt?" · diperiksa "+new Date(v.checkedAt).toLocaleTimeString():"",v.error?" · "+v.error:""]},n))]}),e.jsx(je,{id:"claude",label:ne.claude,active:N==="claude"}),e.jsx(E,{title:"Model",desc:"Diteruskan apa adanya ke `claude --model`.",save:C,children:e.jsx(T,{size:"sm","aria-label":"Model claude",value:C.value,options:qe(C.value),className:"hn-set-w-sm",onChange:n=>C.set(n.target.value)})}),e.jsx(E,{title:"Effort",last:!0,desc:"Anggaran berpikir per giliran (`claude --effort`).",save:H,children:e.jsx(T,{size:"sm","aria-label":"Effort claude",value:H.value,options:Ve(C.value),className:"hn-set-w-xs",onChange:n=>H.set(n.target.value)})})]}),e.jsxs("div",{"data-testid":"agent-group-codex",className:"hn-set-group hn-set-group--codex",children:[e.jsx(je,{id:"codex",label:ne.codex,active:N==="codex"}),e.jsx(Xa,{model:L.value,codexVer:r}),e.jsx(E,{title:"Model",desc:"Diteruskan apa adanya ke `codex -m`.",save:L,children:e.jsx(T,{size:"sm","aria-label":"Model codex",value:L.value,className:"hn-set-w-sm",options:Ia(Da,L.value),onChange:n=>L.set(n.target.value)})}),e.jsx(E,{title:"Effort",last:!0,desc:"Diteruskan ke `codex -c model_reasoning_effort`.",save:Q,children:e.jsx(T,{size:"sm","aria-label":"Effort codex",value:Q.value,className:"hn-set-w-xs",options:_a(L.value).map(n=>({value:n,label:n})),onChange:n=>Q.set(n.target.value)})})]}),e.jsxs("div",{className:"hn-set-inherited-note","data-testid":"session-default-summary",children:["Sesi baru memakai ",e.jsx("b",{children:ne[R.agent]})," · ",e.jsx("code",{children:R.model})," · ",e.jsx("code",{children:R.effort}),"."]})]}),e.jsxs(M,{eyebrow:"goal",title:"Mode goal — sesi backlog","data-setting-card":!0,"data-search":"mode goal kondisi template gate stop",children:[N==="antigravity"&&e.jsx(G,{children:"Mode goal native Antigravity belum didukung. Nilai global tetap disimpan; matikan mode goal secara eksplisit atau pilih runtime lain sebelum memulai sesi."}),e.jsxs(G,{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(E,{title:"Aktif sebagai default",desc:"Sesi backlog baru lahir dengan mode goal. Masih bisa dimatikan per sesi saat Start.",save:o,children:e.jsx(J,{"aria-label":"Mode goal default",disabled:N==="antigravity"&&!o.value,checked:o.value,onChange:n=>o.set(n)})}),e.jsx(E,{title:"Kondisi (template global)",last:!0,desc:"Kosong = kondisi bawaan hanoman: semua fase tercatat di phase file, plan tak menyisakan task, push sukses.",save:A,children:e.jsx("div",{className:"hn-set-w-field",children:e.jsx(kn,{value:A.value,onSet:A.set,onFlush:A.flush})})})]}),e.jsxs(M,{eyebrow:"verifikasi",title:"Scope verifikasi — sesi backlog","data-setting-card":!0,"data-search":"scope verifikasi hanya yang berubah seluruh project uji",children:[e.jsxs(G,{children:["Beberapa sesi berjalan bersamaan di perangkat ini. ",e.jsx("b",{children:"Hanya yang berubah"})," menyuruh sesi menguji hanya berkas yang ia ubah — misalnya dengan ",e.jsx("code",{children:"vitest --changed"})," (uji) dan typecheck per paket — alih-alih seluruh project. Dengan begitu RAM dan CPU tetap tersisa untuk sesi lain. Uji penuh, lint penuh, dan build penuh tetap dijalankan manusia sebelum merge."]}),e.jsx(E,{title:"Scope default",last:!0,desc:"Sesi backlog baru lahir dengan scope ini. Masih bisa diubah per sesi saat Start.",save:B,children:e.jsx(T,{size:"sm","aria-label":"Scope verifikasi default",value:B.value,className:"hn-set-w-md",options:[{value:"changed",label:"Hanya yang berubah"},{value:"full",label:"Seluruh project"}],onChange:n=>B.set(n.target.value)})})]}),e.jsxs(M,{eyebrow:"metode",title:"Metode workflow — sesi backlog","data-setting-card":!0,"data-search":"metode workflow skill direktori plan spec pemasangan",children:[e.jsxs(G,{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(E,{title:"Metode default",last:!0,desc:"Sesi backlog baru lahir dengan metode ini. Masih bisa diubah per sesi saat Start.",save:u,children:e.jsx(T,{size:"sm","aria-label":"Metode default",className:"hn-set-w-md",value:u.value,options:Me.map(n=>({value:n,label:Ie[n].label})),onChange:n=>u.set(n.target.value)})}),h&&e.jsxs("div",{"data-testid":"method-status",className:"hn-set-method-status",children:[Me.map(n=>e.jsxs("div",{className:"hn-set-method",children:[e.jsx("div",{className:"hn-set-method-name",children:Ie[n].label}),h.methods.filter(v=>v.method===n).map(v=>e.jsxs("div",{"data-testid":`method-status-${v.method}-${v.agent}`,className:"hn-set-method-row",children:[e.jsx(X,{tone:v.ready?"ok":"warn",children:v.ready?"siap":"belum siap"}),e.jsxs("div",{className:"hn-set-method-text",children:[e.jsx("b",{children:ne[v.agent]}),v.availabilityReason&&e.jsx("div",{children:v.availabilityReason}),!v.ready&&e.jsxs("div",{className:"hn-set-method-missing",children:[v.missingPackages.length>0&&e.jsxs("div",{children:["paket kurang: ",e.jsx("code",{children:v.missingPackages.join(" · ")})]}),v.missingSkills.length>0&&e.jsxs("div",{children:["skill kurang: ",e.jsx("code",{children:v.missingSkills.join(" · ")})]})]})]}),!v.ready&&e.jsx(x,{size:"sm",variant:"ghost",leftIcon:"download",disabled:!s||v.available===!1,onClick:()=>_(v),children:"Pasang"})]},v.agent))]},n)),e.jsx(Za,{children:s?e.jsxs(e.Fragment,{children:["Pemasangan berjalan di ",e.jsx("b",{children:"sesi terminal"})," project ",e.jsx("code",{children:s})," — 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."})})]})]})]})}function kn({value:a,onSet:t,onFlush:i}){return e.jsx("div",{onBlur:()=>i(),children:e.jsx(La,{value:a,rows:4,mono:!0,"aria-label":"Kondisi mode goal",placeholder:"mis. semua fase tercatat & plan tanpa - [ ]",onChange:r=>t(r.target.value)})})}async function He(a,t,i,r){var l;const d=((l=a.current())==null?void 0:l.lead)??se,h=s=>(s??se).engine??se.engine;a.update(s=>({...s,lead:{...s.lead??se,engine:{...h(s.lead),...i}}}));try{const s=await y.getLeadConfig(),m=await y.putLeadConfig({...s,engine:{...s.engine??se.engine,...i}});return a.update(j=>({...j,lead:m})),t==null||t(r,"ok","check-circle-2"),!0}catch{return a.update(s=>({...s,lead:d})),t==null||t("Gagal menyimpan setelan lead","err","alert-triangle"),!1}}async function Re(a,t,i,r){var l;const d=((l=a.current())==null?void 0:l.telegram)??ae,h=s=>s.engine??ae.engine;a.update(s=>({...s,telegram:{...s.telegram??ae,engine:{...h(s.telegram??ae),...i}}}));try{const s=await y.getSettings(),m=s.telegram??ae,j={...s,telegram:{...m,engine:{...m.engine??ae.engine,...i}}},k=await y.putSettings(j);return a.update(b=>({...b,telegram:(k??j).telegram??b.telegram})),t==null||t(r,"ok","check-circle-2"),!0}catch{return a.update(s=>({...s,telegram:d})),t==null||t("Gagal menyimpan setelan operator Telegram","err","alert-triangle"),!1}}function bn({s:a,store:t,onToast:i,codexVer:r,onNavigate:d}){const h=a.codex??Ce,l=a.agent??"claude",s={claude:{model:a.model,effort:a.effort},codex:{model:h.model,effort:h.effort},antigravity:a.antigravity??de},m={agent:l,...Ee({claude:{model:a.model,effort:a.effort},codex:h,antigravity:a.antigravity??de},l)},j=g=>e.jsxs(e.Fragment,{children:[g," ",e.jsx("b",{children:ne[m.agent]})," · ",e.jsx("code",{children:m.model})," · ",e.jsx("code",{children:m.effort}),"."]}),k=a.conflict??Ba,b=(g,S)=>t.save({conflict:{...k,...g}},S),f=(a.lead??se).engine??se.engine;return e.jsxs(e.Fragment,{children:[e.jsxs(M,{eyebrow:"override",title:"Override per fungsi","data-setting-card":!0,"data-search":"override per fungsi",children:[e.jsxs(G,{children:["Default global diatur di ",e.jsx("b",{children:"Sesi harian"}),". Tiap fungsi di bawah bisa memakai setelan sendiri; bila sakelarnya mati, ia ikut default global. Agen operator Telegram juga override, tetapi diatur di satu tempat bersama kredensial Telegram."]}),e.jsx("div",{className:"hn-set-inline",children:e.jsx(x,{size:"sm",variant:"ghost",leftIcon:"send",onClick:()=>d("telegram"),children:"Buka Telegram"})})]}),e.jsx(pe,{eyebrow:"konflik",title:"Konflik rebase & merge",desc:"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.",toggleDesc:"Mati = ikut default global. Hidup = sesi konflik memakai pilihan di bawah.",toggleAria:"Override agen konflik",enabled:k.enabled,onToggle:g=>b({enabled:g},"Setelan konflik"+(g?" · aktif":" · ikut default global")),inheritedTestId:"conflict-inherited",inherited:j("Sesi konflik memakai default global:"),children:e.jsx(Ne,{engine:{enabled:k.enabled,agent:k.agent,model:k.model,effort:k.effort},labels:{agentTitle:"Agen",agentDesc:"Mesin yang membereskan konflik. Bisa beda dari agen sesi kerja.",effortDesc:"Konflik biasanya mekanis — effort rendah sering cukup.",aria:{agent:"Agen konflik",model:"Model konflik",effort:"Effort konflik"},msg:{agent:"Agen konflik",model:"Model konflik",effort:"Effort konflik"}},defaults:s,codexVer:r,save:(g,S)=>b(g,S)})}),e.jsx(pe,{eyebrow:"lead",title:"Agen hanoman-lead",desc:e.jsxs(e.Fragment,{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"}),"."]}),toggleDesc:"Mati = ikut default global. Hidup = lead memakai pilihan di bawah.",toggleAria:"Override agen lead",enabled:f.enabled,onToggle:g=>He(t,i,{enabled:g},"Setelan lead"+(g?" · aktif":" · ikut default global")),inheritedTestId:"lead-engine-inherited",inherited:j("hanoman-lead memakai default global:"),children:e.jsx(Ne,{engine:f,labels:{agentTitle:"Runtime",agentDesc:"Mesin yang menjalankan lead. Bisa beda dari agen sesi kerja.",effortDesc:"Putusan lead menuntut membaca bukti — dokumen proyek, ADR, plan, kode, riwayat git. Effort rendah memangkas kedalamannya.",aria:{agent:"Runtime lead",model:"Model lead",effort:"Effort lead"},msg:{agent:"Runtime lead",model:"Model lead",effort:"Effort lead"}},defaults:s,codexVer:r,save:(g,S)=>He(t,i,g,S)})}),e.jsx(pe,{eyebrow:"portal",title:"Obrolan portal klien",desc:"Permukaan obrolan di portal klien: klien memilih Brainstorming (digali sampai jelas, keluarannya PRD draft untuk Anda review) atau Bertanya (dijawab langsung dari dokumen project). Jatah berlaku per project dan dihitung bulanan; brainstorming dan pertanyaan punya jatah masing-masing.",toggleTitle:"Aktifkan obrolan",toggleDesc:"Mati = klien hanya punya Pekerjaan & Help desk, persis seperti sebelumnya.",toggleAria:"Aktifkan obrolan portal",enabled:(a.portalChat??we).enabled,onToggle:g=>t.save({portalChat:{...a.portalChat??we,enabled:g}},"Obrolan portal"+(g?" · aktif":" · mati")),children:e.jsx(jn,{s:a,store:t,onToast:i})}),e.jsx(sn,{orchestration:a.orchestration,onChange:(g,S)=>{t.save({orchestration:g},S)}})]})}function jn({s:a,store:t,onToast:i}){const r=a.portalChat??we,d=(s,m)=>t.save({portalChat:{...r,...s}},m),h=$({value:r.model,delayMs:0,save:s=>d({model:s,...Je(s,r.effort)},"Model obrolan portal → "+s)}),l=$({value:r.effort,delayMs:0,save:s=>d({effort:s},"Effort obrolan portal → "+s)});return e.jsxs(e.Fragment,{children:[e.jsx(E,{title:"Jatah brainstorming",desc:"Berapa sesi brainstorming per project tiap bulan. 0 = tertutup.",children:e.jsx(fe,{label:"Jatah brainstorming",value:r.brainstormPerMonth,min:0,max:1e3,placeholder:"mis. 3",onToast:i,onCommit:s=>d({brainstormPerMonth:s},"Jatah brainstorming → "+s)})}),e.jsx(E,{title:"Jatah pertanyaan",desc:"Berapa sesi tanya-jawab per project tiap bulan. 0 = tertutup.",children:e.jsx(fe,{label:"Jatah pertanyaan",value:r.askPerMonth,min:0,max:1e4,placeholder:"mis. 30",onToast:i,onCommit:s=>d({askPerMonth:s},"Jatah pertanyaan → "+s)})}),e.jsx(E,{title:"Model",desc:"Mesin obrolan selalu Claude — gerbang tool yang menjaganya tak ada di runtime lain.",children:e.jsx(T,{size:"sm","aria-label":"Model obrolan portal",value:h.value,className:"hn-set-w-sm",options:qe(h.value),onChange:s=>h.set(s.target.value)})}),e.jsx(E,{title:"Effort",children:e.jsx(T,{size:"sm","aria-label":"Effort obrolan portal",value:l.value,className:"hn-set-w-xs",options:Ve(h.value),onChange:s=>l.set(s.target.value)})}),e.jsx(E,{title:"Batas waktu satu jawaban",last:!0,desc:"Detik. Lewat dari ini klien menerima kalimat 'coba lagi sebentar', bukan layar menggantung.",children:e.jsx(fe,{label:"Batas waktu obrolan portal",value:r.timeoutSec,min:10,max:900,placeholder:"mis. 120",onToast:i,onCommit:s=>d({timeoutSec:s},"Batas waktu obrolan → "+s)})})]})}const fn={name:"",url:"",events:["*"],enabled:!0,allowPrivate:!1},xn=Ge.map(a=>({entity:a.entity,label:a.label,types:ge.filter(t=>t.entity===a.entity).map(t=>t.type)})),ze=a=>a?new Date(a).toLocaleString("id-ID",{dateStyle:"short",timeStyle:"medium"}):"—";function vn({e:a}){return a.enabled?e.jsx(X,{tone:"ok",children:"aktif"}):a.disabledAt?e.jsx(X,{tone:"err",children:"dinonaktifkan otomatis"}):e.jsx(X,{children:"nonaktif"})}function yn({onToast:a,onOpenDocs:t}={}){const[i,r]=c.useState(null),[d,h]=c.useState(!1),[l,s]=c.useState(null),[m,j]=c.useState(null),[k,b]=c.useState(!1),[p,f]=c.useState({}),[g,S]=c.useState(null),[P,w]=c.useState([]),{confirm:D,dialog:O}=ie(),N=c.useCallback(async()=>{try{r((await y.listWebhooks()).endpoints),h(!1)}catch{h(!0)}},[]);c.useEffect(()=>{N()},[N]);const I=async o=>{S(o);try{w((await y.listWebhookDeliveries(o)).items)}catch{w([])}},z=async()=>{if(l){b(!0);try{if(l.id)await y.updateWebhook(l.id,{name:l.name,url:l.url,events:l.events,enabled:l.enabled,allowPrivate:l.allowPrivate}),a==null||a("Endpoint webhook diperbarui");else{const o=await y.createWebhook({name:l.name,url:l.url,events:l.events,enabled:l.enabled,allowPrivate:l.allowPrivate});j(o.secret??null),a==null||a("Endpoint webhook dibuat")}s(null),await N()}catch(o){a==null||a(K("menyimpan endpoint",o),"err")}finally{b(!1)}}},C=async o=>{b(!0);try{const A=await y.updateWebhook(o,{rotateSecret:!0});j(A.secret??null),await N()}catch(A){a==null||a(`Gagal merotasi secret: ${A.message}`,"err")}finally{b(!1)}},H=async(o,A)=>{try{await y.updateWebhook(o.id,{enabled:A}),await N()}catch(B){a==null||a(K("mengubah status endpoint",B),"err")}},L=async o=>{b(!0);try{const A=await y.testWebhook(o);f(B=>({...B,[o]:A})),await N()}catch(A){f(B=>({...B,[o]:{ok:!1,httpStatus:null,durationMs:0,error:A.message}}))}finally{b(!1)}},Q=async o=>{try{await D({title:`Hapus endpoint "${o.name}"?`,message:"Endpoint ini tak lagi menerima peristiwa dari hanoman.",impact:["Riwayat pengiriman endpoint ini ikut dihapus.","Tindakan ini tak bisa dibatalkan."],confirmLabel:"Hapus endpoint",tone:"danger",icon:"trash-2",run:()=>y.deleteWebhook(o.id)})&&(a==null||a("Endpoint webhook dihapus"),await N())}catch(A){a==null||a(K("menghapus endpoint",A),"err")}},V=async o=>{try{await y.retryWebhookDelivery(o),g&&await I(g),await N()}catch(A){a==null||a(`Gagal antre ulang: ${A.message}`,"err")}},Y=(o,A)=>s(B=>B&&{...B,events:A?[...new Set([...B.events.filter(u=>u!=="*"),...o])]:B.events.filter(u=>u!=="*"&&!o.includes(u))}),U=(l==null?void 0:l.events.includes("*"))??!1;return e.jsxs(e.Fragment,{children:[e.jsxs(M,{eyebrow:"integrasi",title:"Webhook keluar",children:[e.jsxs("div",{className:"hn-wh-muted hn-wh-mb12",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",{className:"hn-wh-actions",children:[e.jsxs(x,{size:"sm",onClick:()=>{s({...fn}),j(null)},children:[e.jsx(le,{name:"plus",size:14})," Tambah endpoint"]}),t&&e.jsxs(x,{size:"sm",variant:"ghost",onClick:t,children:[e.jsx(le,{name:"book-open",size:14})," Dokumentasi webhook"]})]}),d?e.jsx(W,{kind:"error",compact:!0,title:"Gagal memuat endpoint webhook",hint:"Coba muat ulang.",action:()=>void N(),actionLabel:"Coba lagi"}):i===null?e.jsx("div",{className:"hn-wh-muted",children:"Memuat…"}):i.length===0?e.jsx(W,{kind:"empty",compact:!0,title:"Belum ada endpoint webhook",hint:"Daftarkan URL penerima pertama untuk mulai berlangganan peristiwa hanoman."}):e.jsx("div",{className:"hn-wh-col-10",children:i.map(o=>e.jsxs("div",{"data-testid":`webhook-${o.id}`,className:"hn-wh-endpoint",children:[e.jsxs("div",{className:"hn-wh-endpoint-head",children:[e.jsx("div",{className:"hn-wh-name",children:o.name}),e.jsx(vn,{e:o}),o.pending>0?e.jsxs(X,{children:[o.pending," mengantre"]}):null,e.jsxs("div",{className:"hn-wh-endpoint-actions",children:[e.jsx(J,{checked:o.enabled,"aria-label":`Aktifkan ${o.name}`,onChange:A=>void H(o,A)}),e.jsx(x,{size:"sm",variant:"ghost",disabled:k,onClick:()=>void L(o.id),children:"Test"}),e.jsx(x,{size:"sm",variant:"ghost",onClick:()=>void I(o.id),children:"Riwayat"}),e.jsx(x,{size:"sm",variant:"ghost",onClick:()=>{j(null),s({id:o.id,name:o.name,url:o.url,events:o.events,enabled:o.enabled,allowPrivate:o.allowPrivate})},children:"Ubah"}),e.jsx(x,{size:"sm",variant:"ghost",onClick:()=>void Q(o),children:"Hapus"})]})]}),e.jsx("div",{className:"hn-wh-url",children:o.url}),e.jsxs("div",{className:"hn-wh-muted hn-wh-mt6",children:["Peristiwa: ",e.jsx("code",{children:o.events.join(", ")})," · secret …",o.secretHint," ·"," ","sukses terakhir ",ze(o.lastSuccessAt),o.failureStreak>0?` · ${o.failureStreak} gagal beruntun`:""]}),o.disabledReason?e.jsx("div",{className:"hn-wh-muted hn-wh-mt6 hn-wh-err",children:o.disabledReason}):null,p[o.id]?e.jsx("div",{className:"hn-wh-muted hn-wh-mt6",children:p[o.id].ok?`Test berhasil — HTTP ${p[o.id].httpStatus} · ${p[o.id].durationMs} ms`:`Test gagal — ${p[o.id].error}`}):null]},o.id))})]}),m?e.jsxs(M,{eyebrow:"secret",title:"Salin sekarang — secret tak ditampilkan lagi",children:[e.jsx(te,{tone:"brass",children:e.jsx("div",{className:"hn-wh-secret",children:m})}),e.jsxs("div",{className:"hn-wh-row hn-wh-mt10",children:[e.jsx(x,{size:"sm",onClick:()=>{var o;return void((o=navigator.clipboard)==null?void 0:o.writeText(m))},children:"Salin secret"}),e.jsx(x,{size:"sm",variant:"ghost",onClick:()=>j(null),children:"Tutup"})]})]}):null,l?e.jsx(M,{eyebrow:l.id?"ubah":"tambah",title:l.id?"Ubah endpoint":"Endpoint baru",children:e.jsxs("div",{className:"hn-wh-col-12",children:[e.jsx(Z,{label:"Nama",children:e.jsx(q,{value:l.name,placeholder:"mis. Dashboard internal",onChange:o=>s({...l,name:o.target.value})})}),e.jsx(Z,{label:"URL tujuan",children:e.jsx(q,{value:l.url,placeholder:"https://contoh.id/hanoman-webhook",onChange:o=>s({...l,url:o.target.value})})}),e.jsxs("div",{children:[e.jsx("div",{className:"hn-wh-label",children:"Peristiwa yang dikirim"}),e.jsx(oe,{checked:U,label:"Semua peristiwa",onChange:o=>s({...l,events:o?["*"]:[]})}),U?null:e.jsxs("div",{className:"hn-wh-tree",children:[xn.map(o=>e.jsxs("div",{children:[e.jsx(oe,{checked:o.types.every(A=>l.events.includes(A)),label:o.label,onChange:A=>Y(o.types,A)}),e.jsx("div",{className:"hn-wh-family-types",children:o.types.map(A=>e.jsx(oe,{checked:l.events.includes(A),label:A,onChange:B=>s({...l,events:B?[...new Set([...l.events.filter(u=>u!=="*"),A])]:l.events.filter(u=>u!==A&&u!=="*")})},A))})]},o.entity)),e.jsxs("div",{className:"hn-wh-muted",children:[e.jsx("code",{children:Ha})," selalu terkirim saat tombol Test ditekan, apa pun langganannya."]})]})]}),e.jsx(oe,{checked:l.allowPrivate,label:"Izinkan alamat internal / loopback",onChange:o=>s({...l,allowPrivate:o})}),e.jsx("div",{className:"hn-wh-muted",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(oe,{checked:l.enabled,label:"Aktif",onChange:o=>s({...l,enabled:o})}),e.jsxs("div",{className:"hn-wh-row",children:[e.jsx(x,{size:"sm",disabled:k||!l.name||!l.url||l.events.length===0,onClick:()=>void z(),children:"Simpan"}),e.jsx(x,{size:"sm",variant:"ghost",onClick:()=>s(null),children:"Batal"}),l.id?e.jsx(x,{size:"sm",variant:"ghost",disabled:k,onClick:()=>void C(l.id),children:"Rotasi secret"}):null]})]})}):null,g?e.jsxs(M,{eyebrow:"riwayat",title:"Riwayat pengiriman",children:[e.jsx("div",{className:"hn-wh-history-head",children:e.jsx(x,{size:"sm",variant:"ghost",onClick:()=>S(null),children:"Tutup riwayat"})}),P.length===0?e.jsx("div",{className:"hn-wh-muted",children:"Belum ada pengiriman."}):e.jsx("div",{className:"hn-local-overflow",children:e.jsxs("table",{className:"hn-wh-table hn-wh-table--full",children:[e.jsx("thead",{children:e.jsxs("tr",{className:"hn-wh-thead",children:[e.jsx("th",{className:"hn-wh-cell",children:"Waktu"}),e.jsx("th",{className:"hn-wh-cell",children:"Jenis"}),e.jsx("th",{className:"hn-wh-cell",children:"Status"}),e.jsx("th",{className:"hn-wh-cell",children:"HTTP"}),e.jsx("th",{className:"hn-wh-cell",children:"Percobaan"}),e.jsx("th",{className:"hn-wh-cell",children:"Galat"}),e.jsx("th",{})]})}),e.jsx("tbody",{children:P.map(o=>e.jsxs("tr",{className:"hn-wh-tr",children:[e.jsx("td",{className:"hn-wh-cell hn-wh-cell--nowrap",children:ze(o.createdAt)}),e.jsx("td",{className:"hn-wh-cell hn-wh-cell--mono12",children:o.eventType}),e.jsx("td",{className:"hn-wh-cell",children:o.status}),e.jsx("td",{className:"hn-wh-cell",children:o.httpStatus??"—"}),e.jsx("td",{className:"hn-wh-cell",children:`${o.attempt} / ${o.maxAttempts}`}),e.jsx("td",{className:"hn-wh-cell hn-wh-cell--muted",children:o.error??"—"}),e.jsx("td",{className:"hn-wh-cell",children:o.status==="failed"||o.status==="dropped"?e.jsx(x,{size:"sm",variant:"ghost",onClick:()=>void V(o.id),children:"Antre ulang"}):null})]},o.id))})]})})]}):null,O]})}function ve({text:a,label:t="Salin"}){const[i,r]=c.useState(!1);return e.jsxs("div",{className:"hn-wh-copy",children:[e.jsx("pre",{className:"hn-wh-code",children:a}),e.jsx("div",{className:"hn-wh-mt6",children:e.jsx(x,{size:"sm",variant:"ghost",onClick:()=>{var d;(d=navigator.clipboard)==null||d.writeText(a),r(!0),setTimeout(()=>r(!1),1500)},children:i?"Tersalin":t})})]})}const wn=`// Node.js (Express) — verifikasi tanda tangan webhook hanoman
1
+ import{br as ea,bs as aa,bt as na,bu as ta,bv as ia,bw as sa,bx as la,by as ra,bz as da,bA as ca,bB as oa,bC as ua,bD as ha,bE as ma,bF as ga,bG as pa,bH as ka,bI as ba,bJ as ja,bK as fa,bL as xa,bM as va,bN as ya,h as y,i as K,j as e,f as V,bO as wa,bP as Sa,a7 as Ce,bQ as Na,a6 as de,ae as Ca,bR as Ea,a8 as Ee,aI as M,b2 as J,g as T,B as x,bS as Pe,bg as Aa,aY as be,a9 as Oa,aa as Pa,ab as Ma,bT as Ia,bU as Da,bV as _a,bW as Me,bX as Ie,n as X,H as La,bY as ae,bZ as se,b_ as Ba,b$ as we,d as ie,I as le,m as W,C as te,e as Z,aJ as oe,c0 as Ha,c1 as Ge,c2 as ge,c3 as Ue,c4 as Ra,c5 as ue,c6 as Ae,c7 as za,c8 as De,c9 as Fa,ca as $a,cb as Ka,cc as ke,$ as Ga,b as Te,cd as Ua,ce as Ta,cf as Wa,ah as Se,q as Ja,D as Va,cg as qa,ch as Ya}from"./index-R2HBkTbR.js";import{R as c}from"./react-DfGhg7ZW.js";import"./markdown-CWEY34jk.js";import{S as Oe,s as We,u as $,c as Je,a as E,b as _e,P as G,d as je,e as ne,A as Qa,f as Ve,g as qe,C as Xa,h as Za,F as pe,E as Ne,N as fe,B as he,i as en,n as Le,j as an,r as nn,D as tn}from"./fields-Bhl01LnE.js";import{r as Ye,O as sn}from"./settings-rebase-DOqNAynI.js";import{D as ln}from"./DocPreviewModal-Cu0UFzPH.js";import"./xterm-3VOAfa_q.js";/* empty css */const rn=[...ea,...aa,...na,...ta,...ia,...sa,...la,...ra,...da,...ca,...oa,...ua,...ha,...ma,...ga,...pa,...ka,...ba,...ja,...fa,...xa,...va,...ya],dn=2,Be=()=>{};function cn(a){const[t,i]=c.useState(null),[r,d]=c.useState(!1),h=c.useRef(null),l=c.useRef(a);l.current=a;const s=c.useRef(Promise.resolve()),m=c.useCallback(g=>{h.current=g,i(g)},[]),j=c.useCallback(()=>{d(!1),m(null),y.getSettings().then(g=>m(g),()=>d(!0))},[m]);c.useEffect(()=>{j()},[j]);const k=c.useCallback((g,S,P,w)=>{const D=h.current;if(!D)return Promise.resolve(!1);const O=Object.keys(g).filter(I=>JSON.stringify(g[I])!==JSON.stringify(D[I]));m(g);const N=s.current.then(async()=>{const I=await y.getSettings().catch(()=>{});await y.putSettings(Ye(D,g,I))});return s.current=N.then(Be,Be),N.then(()=>{var I;return S&&((I=l.current)==null||I.call(l,S,P||"ok",w||"check-circle-2")),!0},I=>{var C;const z=Object.fromEntries(O.map(H=>[H,D[H]]));return m({...h.current??D,...z}),(C=l.current)==null||C.call(l,K("menyimpan pengaturan",I),"err","alert-triangle"),!1})},[m]),b=c.useCallback((g,S)=>{const P=h.current;return P?k({...P,...g},S):Promise.resolve(!1)},[k]),p=c.useCallback(g=>{const S=h.current;S&&m(g(S))},[m]),f=c.useCallback(()=>h.current,[]);return{s:t,failed:r,load:j,persist:k,save:b,update:p,current:f}}function on(a){const t=a.trim().toLowerCase();return t?Oe.flatMap(i=>i.sections).filter(i=>`${i.label} ${i.keywords}`.toLowerCase().includes(t)).map(i=>({key:i.key,path:We(i.key)})):[]}const xe=(a,t)=>(a??"").toLowerCase().includes(t);function un(a,t){if(!a)return 0;const i=t.trim().toLowerCase(),r=Array.from(a.querySelectorAll("[data-setting-row]")),d=Array.from(a.querySelectorAll("[data-setting-card]")),h=s=>{var m;return((m=s.parentElement)==null?void 0:m.closest("[data-setting-card]"))??null};let l=0;return i?(d.forEach(s=>{const m=xe(s.getAttribute("data-search"),i),j=Array.from(s.querySelectorAll("[data-setting-row]")).filter(b=>h(b)===s);if(j.length===0){s.hidden=!m,m&&l++;return}let k=!1;j.forEach(b=>{const p=m||xe(b.getAttribute("data-search"),i);b.hidden=!p,p&&(k=!0,l++)}),s.hidden=!(m||k)}),r.forEach(s=>{if(h(s))return;const m=xe(s.getAttribute("data-search"),i);s.hidden=!m,m&&l++}),l):(r.forEach(s=>{s.hidden=!1}),d.forEach(s=>{s.hidden=!1}),r.length+d.filter(s=>!s.querySelector("[data-setting-row]")).length)}function hn(a,t,i){const[r,d]=c.useState(0);return c.useLayoutEffect(()=>{const h=a.current,l=()=>d(un(h,t));if(l(),!h||typeof MutationObserver>"u")return;const s=new MutationObserver(l);return s.observe(h,{childList:!0,subtree:!0}),()=>s.disconnect()},[t,i,a]),r}function mn({value:a,onChange:t,hits:i,onPick:r}){return e.jsxs("div",{className:"hn-set-search",role:"search",children:[e.jsx(V,{type:"search","aria-label":"Cari pengaturan",placeholder:"mis. notifikasi, telegram, token",value:a,leftIcon:"search",className:"hn-set-full",onChange:d=>t(d.target.value),onKeyDown:d=>{d.key==="Escape"&&a&&t("")}}),a.trim()!==""&&i.length>0&&e.jsx("ul",{className:"hn-set-search-hits","aria-label":"Bagian yang cocok",children:i.map(d=>e.jsx("li",{children:e.jsx("button",{type:"button",className:"hn-set-search-hit",onClick:()=>r(d.key),children:d.path})},d.key))})]})}const gn={default:null,tampil:!0,sembunyi:!1};function re(a,t,i,r){return $({value:i,delayMs:0,save:d=>a.save({[t]:d},r+(d?" · aktif":" · nonaktif"))})}function pn({s:a,store:t,onToast:i,codexVer:r}){const d=wa(),[h,l]=c.useState(null),[s,m]=c.useState("");c.useEffect(()=>{y.getMethodStatus().then(l).catch(()=>l(null)),y.listProjects({limit:100}).then(n=>m(v=>{var F;return v||(((F=n.items.find(ce=>ce.binding??ce.repoDir))==null?void 0:F.id)??"")})).catch(n=>i==null?void 0:i(K("memuat daftar project",n),"err","alert-triangle"))},[]);const[j,k]=Sa(),b=j===null?"default":j?"tampil":"sembunyi",p=re(t,"notifyDone",a.notifyDone,"Notifikasi backlog selesai"),f=$({value:a.notifySound,delayMs:0,save:n=>t.save({notifySound:n},"Sound → "+n)}),g=re(t,"notifyDecision",a.notifyDecision,"Notifikasi keputusan"),S=re(t,"notifyDecisionTelegram",a.notifyDecisionTelegram,"Keputusan ke Telegram"),P=$({value:a.notifyDecisionSound,delayMs:0,save:n=>t.save({notifyDecisionSound:n},"Sound keputusan → "+n)}),w=re(t,"notifyFail",a.notifyFail,"Notifikasi gagal"),D=re(t,"autoDefault",a.autoDefault,"Full-auto default"),O=re(t,"autoScaffold",a.autoScaffold,"Auto-scaffold"),N=a.agent??"claude",I=a.codex??Ce,z=$({value:N,delayMs:0,save:n=>t.save({agent:n},"Agen → "+n)}),C=$({value:a.model,delayMs:0,save:n=>t.save({model:n,...Je(n,a.effort)},"Model claude → "+n)}),H=$({value:a.effort,delayMs:0,save:n=>t.save({effort:n},"Effort claude → "+n)}),L=$({value:I.model,delayMs:0,save:n=>t.save({codex:{model:n,effort:Na(n,I.effort)}},"Model codex → "+n)}),Q=$({value:I.effort,delayMs:0,save:n=>t.save({codex:{...I,effort:n}},"Effort codex → "+n)}),q=a.antigravity??de,Y=$({value:q.model,delayMs:0,save:n=>t.save({antigravity:{model:n,effort:Ca("antigravity",n,q.effort)}},"Model Antigravity → "+n)}),U=$({value:q.effort,delayMs:0,save:n=>t.save({antigravity:{...q,effort:n}},"Effort Antigravity → "+n)}),o=$({value:a.goal.enabled,delayMs:0,save:n=>t.save({goal:{...a.goal,enabled:n}},"Mode goal"+(n?" · aktif":" · nonaktif"))}),A=$({value:a.goal.condition,save:n=>t.save({goal:{...a.goal,condition:n}},"Kondisi mode goal disimpan")}),B=$({value:a.verifyScope??"changed",delayMs:0,save:n=>t.save({verifyScope:n},"Scope verifikasi → "+n)}),u=$({value:Ea(a.method).id,delayMs:0,save:n=>t.save({method:n},"Metode → "+n)}),_=async n=>{if(s)try{await y.createShell(s,{method:n.method,agent:n.agent}),i==null||i(`Pemasangan ${n.label} · ${ne[n.agent]} berjalan di Terminal`,"ok","terminal")}catch{i==null||i("Gagal membuka sesi terminal pemasangan","err","alert-triangle")}},R={agent:N,...Ee({claude:{model:a.model,effort:a.effort},codex:I,antigravity:a.antigravity??de},N)};return e.jsxs(e.Fragment,{children:[e.jsxs(M,{eyebrow:"sesi",title:"Sesi & notifikasi","data-setting-card":!0,"data-search":"sesi notifikasi suara telegram keputusan",children:[e.jsx(E,{title:"Notifikasi backlog selesai",desc:"Toast + sound saat sebuah backlog mencapai stage done. Daftar lonceng tetap terisi meski dimatikan.",save:p,children:e.jsx(J,{"aria-label":"Notifikasi backlog selesai",checked:p.value,onChange:n=>p.set(n)})}),e.jsx(E,{title:"Sound notifikasi",desc:"Durasi nada saat backlog selesai.",save:f,children:e.jsxs("div",{className:"hn-set-inline",children:[e.jsx(T,{size:"sm","aria-label":"Sound notifikasi",value:f.value,options:_e,className:"hn-set-w-sm",onChange:n=>f.set(n.target.value)}),e.jsx(x,{size:"sm",variant:"ghost",leftIcon:"volume-2",disabled:f.value==="off",onClick:()=>Pe(f.value),children:"Preview"})]})}),e.jsx(E,{title:"Notifikasi butuh keputusan",desc:"Toast + sound saat sesi Claude berhenti menunggu keputusanmu. Nada sengaja beda dari selesai.",save:g,children:e.jsx(J,{"aria-label":"Notifikasi keputusan",checked:g.value,onChange:n=>g.set(n)})}),e.jsx(E,{title:"Kirim keputusan ke Telegram",desc:"Pesan ke user id di allowlist Telegram saat sesi menunggu keputusanmu, supaya tak menunggu sampai dashboard dibuka. Perlu bot token + allowlist; gateway percakapan tak perlu berjalan.",save:S,children:e.jsx(J,{"aria-label":"Kirim keputusan ke Telegram",checked:S.value,onChange:n=>S.set(n)})}),e.jsx(E,{title:"Sound keputusan",desc:"Nada saat sebuah sesi menunggu keputusan.",save:P,children:e.jsxs("div",{className:"hn-set-inline",children:[e.jsx(T,{size:"sm","aria-label":"Sound keputusan",value:P.value,options:_e,className:"hn-set-w-sm",onChange:n=>P.set(n.target.value)}),e.jsx(x,{size:"sm",variant:"ghost",leftIcon:"volume-2",disabled:P.value==="off",onClick:()=>Pe(P.value),children:"Preview"})]})}),e.jsx(E,{title:"Notifikasi saat sesi gagal",last:!0,desc:"Kirim notifikasi ketika sesi Claude Code berakhir dengan error.",save:w,children:e.jsx(J,{"aria-label":"Notifikasi gagal",checked:w.value,onChange:n=>w.set(n)})})]}),e.jsxs(M,{eyebrow:"umum",title:"Perilaku sesi","data-setting-card":!0,"data-search":"perilaku sesi full-auto scaffold doc index",children:[e.jsx(E,{title:"Full-auto sebagai default",desc:"Run baru jalan sendiri sampai selesai. Manusia tetap bisa steer / interupsi kapan pun.",save:D,children:e.jsx(J,{"aria-label":"Full-auto sebagai default",checked:D.value,onChange:n=>D.set(n)})}),e.jsx(E,{title:"Auto-scaffold doc index",desc:"Project yang dibuat dari nol otomatis disiapkan indeks dokumennya setelah tujuannya terkunci.",save:O,children:e.jsx(J,{"aria-label":"Auto-scaffold",checked:O.value,onChange:n=>O.set(n)})}),e.jsx(E,{title:"Tampilkan Hanoman (pet)",last:!0,search:"pet hanoman maskot tampil sembunyi",desc:"Pendamping kecil di sudut layar. Pilihan ini hanya berlaku di browser ini. Default: aktif di desktop, mati di tablet & ponsel.",children:e.jsxs(Aa,{"aria-label":"Tampilkan Hanoman (pet)",direction:"row",value:b,onChange:n=>k(gn[n]??null),children:[e.jsx(be,{value:"default",label:"Default"}),e.jsx(be,{value:"tampil",label:"Tampilkan"}),e.jsx(be,{value:"sembunyi",label:"Sembunyikan"})]})})]}),e.jsxs(M,{eyebrow:"model",title:"Agen & model — default global","data-setting-card":!0,"data-search":"agen model default effort claude codex agen sesi baru",children:[e.jsxs(G,{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 bagian ",e.jsx("b",{children:"Orkestrasi"})," pada Agen & model. Yang benar-benar dipakai adalah blok milik agen terpilih di bawah — blok runtime lain tersimpan, menunggu giliran."]}),e.jsx(E,{title:"Agen default",desc:"Sesi backlog masih bisa memilih agen lain saat Start.",save:z,children:e.jsx(T,{size:"sm","aria-label":"Agen default",value:z.value,className:"hn-set-w-sm",options:Oa,onChange:n=>z.set(n.target.value)})}),e.jsxs("div",{"data-testid":"agent-group-antigravity",className:"hn-set-group",children:[e.jsx(je,{id:"antigravity",label:ne.antigravity,active:N==="antigravity"}),e.jsx(Qa,{}),e.jsx(E,{title:"Model",desc:"Model parent diteruskan ke agy --model.",save:Y,children:e.jsx(T,{size:"sm","aria-label":"Model antigravity",value:Y.value,options:Pa("antigravity",Y.value),onChange:n=>Y.set(n.target.value)})}),e.jsx(E,{title:"Effort",last:!0,desc:"Effort mengikuti kemampuan slug model parent.",save:U,children:e.jsx(T,{size:"sm","aria-label":"Effort antigravity",value:U.value,options:Ma("antigravity",Y.value).map(n=>({value:n,label:n})),onChange:n=>U.set(n.target.value)})})]}),e.jsxs("div",{"data-testid":"agent-group-claude",className:"hn-set-group",children:[e.jsxs("div",{className:"hn-set-catalog",children:["Katalog model diperiksa otomatis setiap 5 menit.",Object.entries(d.providers).map(([n,v])=>e.jsxs("div",{children:[n,": ",v.source==="bundled"?"daftar bawaan":v.source==="cache"?"cache terakhir":"dari CLI",v.checkedAt?" · diperiksa "+new Date(v.checkedAt).toLocaleTimeString():"",v.error?" · "+v.error:""]},n))]}),e.jsx(je,{id:"claude",label:ne.claude,active:N==="claude"}),e.jsx(E,{title:"Model",desc:"Diteruskan apa adanya ke `claude --model`.",save:C,children:e.jsx(T,{size:"sm","aria-label":"Model claude",value:C.value,options:Ve(C.value),className:"hn-set-w-sm",onChange:n=>C.set(n.target.value)})}),e.jsx(E,{title:"Effort",last:!0,desc:"Anggaran berpikir per giliran (`claude --effort`).",save:H,children:e.jsx(T,{size:"sm","aria-label":"Effort claude",value:H.value,options:qe(C.value),className:"hn-set-w-xs",onChange:n=>H.set(n.target.value)})})]}),e.jsxs("div",{"data-testid":"agent-group-codex",className:"hn-set-group hn-set-group--codex",children:[e.jsx(je,{id:"codex",label:ne.codex,active:N==="codex"}),e.jsx(Xa,{model:L.value,codexVer:r}),e.jsx(E,{title:"Model",desc:"Diteruskan apa adanya ke `codex -m`.",save:L,children:e.jsx(T,{size:"sm","aria-label":"Model codex",value:L.value,className:"hn-set-w-sm",options:Ia(Da,L.value),onChange:n=>L.set(n.target.value)})}),e.jsx(E,{title:"Effort",last:!0,desc:"Diteruskan ke `codex -c model_reasoning_effort`.",save:Q,children:e.jsx(T,{size:"sm","aria-label":"Effort codex",value:Q.value,className:"hn-set-w-xs",options:_a(L.value).map(n=>({value:n,label:n})),onChange:n=>Q.set(n.target.value)})})]}),e.jsxs("div",{className:"hn-set-inherited-note","data-testid":"session-default-summary",children:["Sesi baru memakai ",e.jsx("b",{children:ne[R.agent]})," · ",e.jsx("code",{children:R.model})," · ",e.jsx("code",{children:R.effort}),"."]})]}),e.jsxs(M,{eyebrow:"goal",title:"Mode goal — sesi backlog","data-setting-card":!0,"data-search":"mode goal kondisi template gate stop",children:[N==="antigravity"&&e.jsx(G,{children:"Mode goal native Antigravity belum didukung. Nilai global tetap disimpan; matikan mode goal secara eksplisit atau pilih runtime lain sebelum memulai sesi."}),e.jsxs(G,{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(E,{title:"Aktif sebagai default",desc:"Sesi backlog baru lahir dengan mode goal. Masih bisa dimatikan per sesi saat Start.",save:o,children:e.jsx(J,{"aria-label":"Mode goal default",disabled:N==="antigravity"&&!o.value,checked:o.value,onChange:n=>o.set(n)})}),e.jsx(E,{title:"Kondisi (template global)",last:!0,desc:"Kosong = kondisi bawaan hanoman: semua fase tercatat di phase file, plan tak menyisakan task, push sukses.",save:A,children:e.jsx("div",{className:"hn-set-w-field",children:e.jsx(kn,{value:A.value,onSet:A.set,onFlush:A.flush})})})]}),e.jsxs(M,{eyebrow:"verifikasi",title:"Scope verifikasi — sesi backlog","data-setting-card":!0,"data-search":"scope verifikasi hanya yang berubah seluruh project uji",children:[e.jsxs(G,{children:["Beberapa sesi berjalan bersamaan di perangkat ini. ",e.jsx("b",{children:"Hanya yang berubah"})," menyuruh sesi menguji hanya berkas yang ia ubah — misalnya dengan ",e.jsx("code",{children:"vitest --changed"})," (uji) dan typecheck per paket — alih-alih seluruh project. Dengan begitu RAM dan CPU tetap tersisa untuk sesi lain. Uji penuh, lint penuh, dan build penuh tetap dijalankan manusia sebelum merge."]}),e.jsx(E,{title:"Scope default",last:!0,desc:"Sesi backlog baru lahir dengan scope ini. Masih bisa diubah per sesi saat Start.",save:B,children:e.jsx(T,{size:"sm","aria-label":"Scope verifikasi default",value:B.value,className:"hn-set-w-md",options:[{value:"changed",label:"Hanya yang berubah"},{value:"full",label:"Seluruh project"}],onChange:n=>B.set(n.target.value)})})]}),e.jsxs(M,{eyebrow:"metode",title:"Metode workflow — sesi backlog","data-setting-card":!0,"data-search":"metode workflow skill direktori plan spec pemasangan",children:[e.jsxs(G,{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(E,{title:"Metode default",last:!0,desc:"Sesi backlog baru lahir dengan metode ini. Masih bisa diubah per sesi saat Start.",save:u,children:e.jsx(T,{size:"sm","aria-label":"Metode default",className:"hn-set-w-md",value:u.value,options:Me.map(n=>({value:n,label:Ie[n].label})),onChange:n=>u.set(n.target.value)})}),h&&e.jsxs("div",{"data-testid":"method-status",className:"hn-set-method-status",children:[Me.map(n=>e.jsxs("div",{className:"hn-set-method",children:[e.jsx("div",{className:"hn-set-method-name",children:Ie[n].label}),h.methods.filter(v=>v.method===n).map(v=>e.jsxs("div",{"data-testid":`method-status-${v.method}-${v.agent}`,className:"hn-set-method-row",children:[e.jsx(X,{tone:v.ready?"ok":"warn",children:v.ready?"siap":"belum siap"}),e.jsxs("div",{className:"hn-set-method-text",children:[e.jsx("b",{children:ne[v.agent]}),v.availabilityReason&&e.jsx("div",{children:v.availabilityReason}),!v.ready&&e.jsxs("div",{className:"hn-set-method-missing",children:[v.missingPackages.length>0&&e.jsxs("div",{children:["paket kurang: ",e.jsx("code",{children:v.missingPackages.join(" · ")})]}),v.missingSkills.length>0&&e.jsxs("div",{children:["skill kurang: ",e.jsx("code",{children:v.missingSkills.join(" · ")})]})]})]}),!v.ready&&e.jsx(x,{size:"sm",variant:"ghost",leftIcon:"download",disabled:!s||v.available===!1,onClick:()=>_(v),children:"Pasang"})]},v.agent))]},n)),e.jsx(Za,{children:s?e.jsxs(e.Fragment,{children:["Pemasangan berjalan di ",e.jsx("b",{children:"sesi terminal"})," project ",e.jsx("code",{children:s})," — 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."})})]})]})]})}function kn({value:a,onSet:t,onFlush:i}){return e.jsx("div",{onBlur:()=>i(),children:e.jsx(La,{value:a,rows:4,mono:!0,"aria-label":"Kondisi mode goal",placeholder:"mis. semua fase tercatat & plan tanpa - [ ]",onChange:r=>t(r.target.value)})})}async function He(a,t,i,r){var l;const d=((l=a.current())==null?void 0:l.lead)??se,h=s=>(s??se).engine??se.engine;a.update(s=>({...s,lead:{...s.lead??se,engine:{...h(s.lead),...i}}}));try{const s=await y.getLeadConfig(),m=await y.putLeadConfig({...s,engine:{...s.engine??se.engine,...i}});return a.update(j=>({...j,lead:m})),t==null||t(r,"ok","check-circle-2"),!0}catch{return a.update(s=>({...s,lead:d})),t==null||t("Gagal menyimpan setelan lead","err","alert-triangle"),!1}}async function Re(a,t,i,r){var l;const d=((l=a.current())==null?void 0:l.telegram)??ae,h=s=>s.engine??ae.engine;a.update(s=>({...s,telegram:{...s.telegram??ae,engine:{...h(s.telegram??ae),...i}}}));try{const s=await y.getSettings(),m=s.telegram??ae,j={...s,telegram:{...m,engine:{...m.engine??ae.engine,...i}}},k=await y.putSettings(j);return a.update(b=>({...b,telegram:(k??j).telegram??b.telegram})),t==null||t(r,"ok","check-circle-2"),!0}catch{return a.update(s=>({...s,telegram:d})),t==null||t("Gagal menyimpan setelan operator Telegram","err","alert-triangle"),!1}}function bn({s:a,store:t,onToast:i,codexVer:r,onNavigate:d}){const h=a.codex??Ce,l=a.agent??"claude",s={claude:{model:a.model,effort:a.effort},codex:{model:h.model,effort:h.effort},antigravity:a.antigravity??de},m={agent:l,...Ee({claude:{model:a.model,effort:a.effort},codex:h,antigravity:a.antigravity??de},l)},j=g=>e.jsxs(e.Fragment,{children:[g," ",e.jsx("b",{children:ne[m.agent]})," · ",e.jsx("code",{children:m.model})," · ",e.jsx("code",{children:m.effort}),"."]}),k=a.conflict??Ba,b=(g,S)=>t.save({conflict:{...k,...g}},S),f=(a.lead??se).engine??se.engine;return e.jsxs(e.Fragment,{children:[e.jsxs(M,{eyebrow:"override",title:"Override per fungsi","data-setting-card":!0,"data-search":"override per fungsi",children:[e.jsxs(G,{children:["Default global diatur di ",e.jsx("b",{children:"Sesi harian"}),". Tiap fungsi di bawah bisa memakai setelan sendiri; bila sakelarnya mati, ia ikut default global. Agen operator Telegram juga override, tetapi diatur di satu tempat bersama kredensial Telegram."]}),e.jsx("div",{className:"hn-set-inline",children:e.jsx(x,{size:"sm",variant:"ghost",leftIcon:"send",onClick:()=>d("telegram"),children:"Buka Telegram"})})]}),e.jsx(pe,{eyebrow:"konflik",title:"Konflik rebase & merge",desc:"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.",toggleDesc:"Mati = ikut default global. Hidup = sesi konflik memakai pilihan di bawah.",toggleAria:"Override agen konflik",enabled:k.enabled,onToggle:g=>b({enabled:g},"Setelan konflik"+(g?" · aktif":" · ikut default global")),inheritedTestId:"conflict-inherited",inherited:j("Sesi konflik memakai default global:"),children:e.jsx(Ne,{engine:{enabled:k.enabled,agent:k.agent,model:k.model,effort:k.effort},labels:{agentTitle:"Agen",agentDesc:"Mesin yang membereskan konflik. Bisa beda dari agen sesi kerja.",effortDesc:"Konflik biasanya mekanis — effort rendah sering cukup.",aria:{agent:"Agen konflik",model:"Model konflik",effort:"Effort konflik"},msg:{agent:"Agen konflik",model:"Model konflik",effort:"Effort konflik"}},defaults:s,codexVer:r,save:(g,S)=>b(g,S)})}),e.jsx(pe,{eyebrow:"lead",title:"Agen hanoman-lead",desc:e.jsxs(e.Fragment,{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"}),"."]}),toggleDesc:"Mati = ikut default global. Hidup = lead memakai pilihan di bawah.",toggleAria:"Override agen lead",enabled:f.enabled,onToggle:g=>He(t,i,{enabled:g},"Setelan lead"+(g?" · aktif":" · ikut default global")),inheritedTestId:"lead-engine-inherited",inherited:j("hanoman-lead memakai default global:"),children:e.jsx(Ne,{engine:f,labels:{agentTitle:"Runtime",agentDesc:"Mesin yang menjalankan lead. Bisa beda dari agen sesi kerja.",effortDesc:"Putusan lead menuntut membaca bukti — dokumen proyek, ADR, plan, kode, riwayat git. Effort rendah memangkas kedalamannya.",aria:{agent:"Runtime lead",model:"Model lead",effort:"Effort lead"},msg:{agent:"Runtime lead",model:"Model lead",effort:"Effort lead"}},defaults:s,codexVer:r,save:(g,S)=>He(t,i,g,S)})}),e.jsx(pe,{eyebrow:"portal",title:"Obrolan portal klien",desc:"Permukaan obrolan di portal klien: klien memilih Brainstorming (digali sampai jelas, keluarannya PRD draft untuk Anda review) atau Bertanya (dijawab langsung dari dokumen project). Jatah berlaku per project dan dihitung bulanan; brainstorming dan pertanyaan punya jatah masing-masing.",toggleTitle:"Aktifkan obrolan",toggleDesc:"Mati = klien hanya punya Pekerjaan & Help desk, persis seperti sebelumnya.",toggleAria:"Aktifkan obrolan portal",enabled:(a.portalChat??we).enabled,onToggle:g=>t.save({portalChat:{...a.portalChat??we,enabled:g}},"Obrolan portal"+(g?" · aktif":" · mati")),children:e.jsx(jn,{s:a,store:t,onToast:i})}),e.jsx(sn,{orchestration:a.orchestration,onChange:(g,S)=>{t.save({orchestration:g},S)}})]})}function jn({s:a,store:t,onToast:i}){const r=a.portalChat??we,d=(s,m)=>t.save({portalChat:{...r,...s}},m),h=$({value:r.model,delayMs:0,save:s=>d({model:s,...Je(s,r.effort)},"Model obrolan portal → "+s)}),l=$({value:r.effort,delayMs:0,save:s=>d({effort:s},"Effort obrolan portal → "+s)});return e.jsxs(e.Fragment,{children:[e.jsx(E,{title:"Jatah brainstorming",desc:"Berapa sesi brainstorming per project tiap bulan. 0 = tertutup.",children:e.jsx(fe,{label:"Jatah brainstorming",value:r.brainstormPerMonth,min:0,max:1e3,placeholder:"mis. 3",onToast:i,onCommit:s=>d({brainstormPerMonth:s},"Jatah brainstorming → "+s)})}),e.jsx(E,{title:"Jatah pertanyaan",desc:"Berapa sesi tanya-jawab per project tiap bulan. 0 = tertutup.",children:e.jsx(fe,{label:"Jatah pertanyaan",value:r.askPerMonth,min:0,max:1e4,placeholder:"mis. 30",onToast:i,onCommit:s=>d({askPerMonth:s},"Jatah pertanyaan → "+s)})}),e.jsx(E,{title:"Model",desc:"Mesin obrolan selalu Claude — gerbang tool yang menjaganya tak ada di runtime lain.",children:e.jsx(T,{size:"sm","aria-label":"Model obrolan portal",value:h.value,className:"hn-set-w-sm",options:Ve(h.value),onChange:s=>h.set(s.target.value)})}),e.jsx(E,{title:"Effort",children:e.jsx(T,{size:"sm","aria-label":"Effort obrolan portal",value:l.value,className:"hn-set-w-xs",options:qe(h.value),onChange:s=>l.set(s.target.value)})}),e.jsx(E,{title:"Batas waktu satu jawaban",last:!0,desc:"Detik. Lewat dari ini klien menerima kalimat 'coba lagi sebentar', bukan layar menggantung.",children:e.jsx(fe,{label:"Batas waktu obrolan portal",value:r.timeoutSec,min:10,max:900,placeholder:"mis. 120",onToast:i,onCommit:s=>d({timeoutSec:s},"Batas waktu obrolan → "+s)})})]})}const fn={name:"",url:"",events:["*"],enabled:!0,allowPrivate:!1},xn=Ge.map(a=>({entity:a.entity,label:a.label,types:ge.filter(t=>t.entity===a.entity).map(t=>t.type)})),ze=a=>a?new Date(a).toLocaleString("id-ID",{dateStyle:"short",timeStyle:"medium"}):"—";function vn({e:a}){return a.enabled?e.jsx(X,{tone:"ok",children:"aktif"}):a.disabledAt?e.jsx(X,{tone:"err",children:"dinonaktifkan otomatis"}):e.jsx(X,{children:"nonaktif"})}function yn({onToast:a,onOpenDocs:t}={}){const[i,r]=c.useState(null),[d,h]=c.useState(!1),[l,s]=c.useState(null),[m,j]=c.useState(null),[k,b]=c.useState(!1),[p,f]=c.useState({}),[g,S]=c.useState(null),[P,w]=c.useState([]),{confirm:D,dialog:O}=ie(),N=c.useCallback(async()=>{try{r((await y.listWebhooks()).endpoints),h(!1)}catch{h(!0)}},[]);c.useEffect(()=>{N()},[N]);const I=async o=>{S(o);try{w((await y.listWebhookDeliveries(o)).items)}catch{w([])}},z=async()=>{if(l){b(!0);try{if(l.id)await y.updateWebhook(l.id,{name:l.name,url:l.url,events:l.events,enabled:l.enabled,allowPrivate:l.allowPrivate}),a==null||a("Endpoint webhook diperbarui");else{const o=await y.createWebhook({name:l.name,url:l.url,events:l.events,enabled:l.enabled,allowPrivate:l.allowPrivate});j(o.secret??null),a==null||a("Endpoint webhook dibuat")}s(null),await N()}catch(o){a==null||a(K("menyimpan endpoint",o),"err")}finally{b(!1)}}},C=async o=>{b(!0);try{const A=await y.updateWebhook(o,{rotateSecret:!0});j(A.secret??null),await N()}catch(A){a==null||a(`Gagal merotasi secret: ${A.message}`,"err")}finally{b(!1)}},H=async(o,A)=>{try{await y.updateWebhook(o.id,{enabled:A}),await N()}catch(B){a==null||a(K("mengubah status endpoint",B),"err")}},L=async o=>{b(!0);try{const A=await y.testWebhook(o);f(B=>({...B,[o]:A})),await N()}catch(A){f(B=>({...B,[o]:{ok:!1,httpStatus:null,durationMs:0,error:A.message}}))}finally{b(!1)}},Q=async o=>{try{await D({title:`Hapus endpoint "${o.name}"?`,message:"Endpoint ini tak lagi menerima peristiwa dari hanoman.",impact:["Riwayat pengiriman endpoint ini ikut dihapus.","Tindakan ini tak bisa dibatalkan."],confirmLabel:"Hapus endpoint",tone:"danger",icon:"trash-2",run:()=>y.deleteWebhook(o.id)})&&(a==null||a("Endpoint webhook dihapus"),await N())}catch(A){a==null||a(K("menghapus endpoint",A),"err")}},q=async o=>{try{await y.retryWebhookDelivery(o),g&&await I(g),await N()}catch(A){a==null||a(`Gagal antre ulang: ${A.message}`,"err")}},Y=(o,A)=>s(B=>B&&{...B,events:A?[...new Set([...B.events.filter(u=>u!=="*"),...o])]:B.events.filter(u=>u!=="*"&&!o.includes(u))}),U=(l==null?void 0:l.events.includes("*"))??!1;return e.jsxs(e.Fragment,{children:[e.jsxs(M,{eyebrow:"integrasi",title:"Webhook keluar",children:[e.jsxs("div",{className:"hn-wh-muted hn-wh-mb12",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",{className:"hn-wh-actions",children:[e.jsxs(x,{size:"sm",onClick:()=>{s({...fn}),j(null)},children:[e.jsx(le,{name:"plus",size:14})," Tambah endpoint"]}),t&&e.jsxs(x,{size:"sm",variant:"ghost",onClick:t,children:[e.jsx(le,{name:"book-open",size:14})," Dokumentasi webhook"]})]}),d?e.jsx(W,{kind:"error",compact:!0,title:"Gagal memuat endpoint webhook",hint:"Coba muat ulang.",action:()=>void N(),actionLabel:"Coba lagi"}):i===null?e.jsx("div",{className:"hn-wh-muted",children:"Memuat…"}):i.length===0?e.jsx(W,{kind:"empty",compact:!0,title:"Belum ada endpoint webhook",hint:"Daftarkan URL penerima pertama untuk mulai berlangganan peristiwa hanoman."}):e.jsx("div",{className:"hn-wh-col-10",children:i.map(o=>e.jsxs("div",{"data-testid":`webhook-${o.id}`,className:"hn-wh-endpoint",children:[e.jsxs("div",{className:"hn-wh-endpoint-head",children:[e.jsx("div",{className:"hn-wh-name",children:o.name}),e.jsx(vn,{e:o}),o.pending>0?e.jsxs(X,{children:[o.pending," mengantre"]}):null,e.jsxs("div",{className:"hn-wh-endpoint-actions",children:[e.jsx(J,{checked:o.enabled,"aria-label":`Aktifkan ${o.name}`,onChange:A=>void H(o,A)}),e.jsx(x,{size:"sm",variant:"ghost",disabled:k,onClick:()=>void L(o.id),children:"Test"}),e.jsx(x,{size:"sm",variant:"ghost",onClick:()=>void I(o.id),children:"Riwayat"}),e.jsx(x,{size:"sm",variant:"ghost",onClick:()=>{j(null),s({id:o.id,name:o.name,url:o.url,events:o.events,enabled:o.enabled,allowPrivate:o.allowPrivate})},children:"Ubah"}),e.jsx(x,{size:"sm",variant:"ghost",onClick:()=>void Q(o),children:"Hapus"})]})]}),e.jsx("div",{className:"hn-wh-url",children:o.url}),e.jsxs("div",{className:"hn-wh-muted hn-wh-mt6",children:["Peristiwa: ",e.jsx("code",{children:o.events.join(", ")})," · secret …",o.secretHint," ·"," ","sukses terakhir ",ze(o.lastSuccessAt),o.failureStreak>0?` · ${o.failureStreak} gagal beruntun`:""]}),o.disabledReason?e.jsx("div",{className:"hn-wh-muted hn-wh-mt6 hn-wh-err",children:o.disabledReason}):null,p[o.id]?e.jsx("div",{className:"hn-wh-muted hn-wh-mt6",children:p[o.id].ok?`Test berhasil — HTTP ${p[o.id].httpStatus} · ${p[o.id].durationMs} ms`:`Test gagal — ${p[o.id].error}`}):null]},o.id))})]}),m?e.jsxs(M,{eyebrow:"secret",title:"Salin sekarang — secret tak ditampilkan lagi",children:[e.jsx(te,{tone:"brass",children:e.jsx("div",{className:"hn-wh-secret",children:m})}),e.jsxs("div",{className:"hn-wh-row hn-wh-mt10",children:[e.jsx(x,{size:"sm",onClick:()=>{var o;return void((o=navigator.clipboard)==null?void 0:o.writeText(m))},children:"Salin secret"}),e.jsx(x,{size:"sm",variant:"ghost",onClick:()=>j(null),children:"Tutup"})]})]}):null,l?e.jsx(M,{eyebrow:l.id?"ubah":"tambah",title:l.id?"Ubah endpoint":"Endpoint baru",children:e.jsxs("div",{className:"hn-wh-col-12",children:[e.jsx(Z,{label:"Nama",children:e.jsx(V,{value:l.name,placeholder:"mis. Dashboard internal",onChange:o=>s({...l,name:o.target.value})})}),e.jsx(Z,{label:"URL tujuan",children:e.jsx(V,{value:l.url,placeholder:"https://contoh.id/hanoman-webhook",onChange:o=>s({...l,url:o.target.value})})}),e.jsxs("div",{children:[e.jsx("div",{className:"hn-wh-label",children:"Peristiwa yang dikirim"}),e.jsx(oe,{checked:U,label:"Semua peristiwa",onChange:o=>s({...l,events:o?["*"]:[]})}),U?null:e.jsxs("div",{className:"hn-wh-tree",children:[xn.map(o=>e.jsxs("div",{children:[e.jsx(oe,{checked:o.types.every(A=>l.events.includes(A)),label:o.label,onChange:A=>Y(o.types,A)}),e.jsx("div",{className:"hn-wh-family-types",children:o.types.map(A=>e.jsx(oe,{checked:l.events.includes(A),label:A,onChange:B=>s({...l,events:B?[...new Set([...l.events.filter(u=>u!=="*"),A])]:l.events.filter(u=>u!==A&&u!=="*")})},A))})]},o.entity)),e.jsxs("div",{className:"hn-wh-muted",children:[e.jsx("code",{children:Ha})," selalu terkirim saat tombol Test ditekan, apa pun langganannya."]})]})]}),e.jsx(oe,{checked:l.allowPrivate,label:"Izinkan alamat internal / loopback",onChange:o=>s({...l,allowPrivate:o})}),e.jsx("div",{className:"hn-wh-muted",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(oe,{checked:l.enabled,label:"Aktif",onChange:o=>s({...l,enabled:o})}),e.jsxs("div",{className:"hn-wh-row",children:[e.jsx(x,{size:"sm",disabled:k||!l.name||!l.url||l.events.length===0,onClick:()=>void z(),children:"Simpan"}),e.jsx(x,{size:"sm",variant:"ghost",onClick:()=>s(null),children:"Batal"}),l.id?e.jsx(x,{size:"sm",variant:"ghost",disabled:k,onClick:()=>void C(l.id),children:"Rotasi secret"}):null]})]})}):null,g?e.jsxs(M,{eyebrow:"riwayat",title:"Riwayat pengiriman",children:[e.jsx("div",{className:"hn-wh-history-head",children:e.jsx(x,{size:"sm",variant:"ghost",onClick:()=>S(null),children:"Tutup riwayat"})}),P.length===0?e.jsx("div",{className:"hn-wh-muted",children:"Belum ada pengiriman."}):e.jsx("div",{className:"hn-local-overflow",children:e.jsxs("table",{className:"hn-wh-table hn-wh-table--full",children:[e.jsx("thead",{children:e.jsxs("tr",{className:"hn-wh-thead",children:[e.jsx("th",{className:"hn-wh-cell",children:"Waktu"}),e.jsx("th",{className:"hn-wh-cell",children:"Jenis"}),e.jsx("th",{className:"hn-wh-cell",children:"Status"}),e.jsx("th",{className:"hn-wh-cell",children:"HTTP"}),e.jsx("th",{className:"hn-wh-cell",children:"Percobaan"}),e.jsx("th",{className:"hn-wh-cell",children:"Galat"}),e.jsx("th",{})]})}),e.jsx("tbody",{children:P.map(o=>e.jsxs("tr",{className:"hn-wh-tr",children:[e.jsx("td",{className:"hn-wh-cell hn-wh-cell--nowrap",children:ze(o.createdAt)}),e.jsx("td",{className:"hn-wh-cell hn-wh-cell--mono12",children:o.eventType}),e.jsx("td",{className:"hn-wh-cell",children:o.status}),e.jsx("td",{className:"hn-wh-cell",children:o.httpStatus??"—"}),e.jsx("td",{className:"hn-wh-cell",children:`${o.attempt} / ${o.maxAttempts}`}),e.jsx("td",{className:"hn-wh-cell hn-wh-cell--muted",children:o.error??"—"}),e.jsx("td",{className:"hn-wh-cell",children:o.status==="failed"||o.status==="dropped"?e.jsx(x,{size:"sm",variant:"ghost",onClick:()=>void q(o.id),children:"Antre ulang"}):null})]},o.id))})]})})]}):null,O]})}function ve({text:a,label:t="Salin"}){const[i,r]=c.useState(!1);return e.jsxs("div",{className:"hn-wh-copy",children:[e.jsx("pre",{className:"hn-wh-code",children:a}),e.jsx("div",{className:"hn-wh-mt6",children:e.jsx(x,{size:"sm",variant:"ghost",onClick:()=>{var d;(d=navigator.clipboard)==null||d.writeText(a),r(!0),setTimeout(()=>r(!1),1500)},children:i?"Tersalin":t})})]})}const wn=`// Node.js (Express) — verifikasi tanda tangan webhook hanoman
2
2
  import express from "express";
3
3
  import { createHmac, timingSafeEqual } from "node:crypto";
4
4
 
@@ -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`,Nn=[["specVersion",`Versi skema amplop. Hari ini selalu "${Ue}". 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 ee({eyebrow:a,title:t,children:i}){return e.jsx(M,{children:e.jsxs("details",{children:[e.jsxs("summary",{className:"hn-wh-fold-summary",children:[e.jsx("span",{className:"hn-eyebrow",children:a}),e.jsx("span",{className:"hn-wh-fold-title",children:t})]}),e.jsx("div",{className:"hn-wh-fold-body",children:i})]})})}function Cn({onBack:a}={}){const t=Ge.map(i=>({label:i.label,events:ge.filter(r=>r.entity===i.entity)})).concat([{label:"Webhook",events:ge.filter(i=>i.entity==="webhook")}]);return e.jsxs(e.Fragment,{children:[e.jsxs(M,{eyebrow:"dokumentasi",title:"Dokumentasi webhook",children:[a&&e.jsx("div",{className:"hn-wh-back",children:e.jsxs(x,{size:"sm",variant:"ghost",onClick:a,children:[e.jsx(le,{name:"arrow-left",size:14})," Kembali ke daftar endpoint"]})}),e.jsxs("div",{className:"hn-wh-prose",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:Ue}),", jadi penerima yang ditulis hari ini tetap bekerja saat jenis peristiwa baru ditambahkan."]}),e.jsx("div",{className:"hn-wh-prose hn-wh-mt10",children:"Setiap bagian di bawah bisa dibuka sendiri dan semuanya tertutup dulu, supaya halaman ini tak memanjang."})]}),e.jsxs(ee,{eyebrow:"katalog",title:"Jenis peristiwa dan kapan terpicu",children:[e.jsx("div",{className:"hn-wh-col-16",children:t.map(i=>e.jsxs("div",{children:[e.jsx("div",{className:"hn-wh-group",children:i.label}),e.jsx("div",{className:"hn-wh-col-10",children:i.events.map(r=>e.jsxs("div",{className:"hn-wh-event",children:[e.jsxs("div",{className:"hn-wh-event-head",children:[e.jsx("code",{className:"hn-wh-type",children:r.type}),e.jsx(X,{tone:"brass",size:"sm",children:r.entityLabel}),e.jsx("span",{className:"hn-wh-event-label",children:r.label})]}),e.jsx("div",{className:"hn-wh-prose hn-wh-mt4",children:r.when})]},r.type))})]},i.label))}),e.jsx(te,{tone:"info",title:"Satu perubahan = satu peristiwa",className:"hn-wh-mt14",children:e.jsxs("div",{className:"hn-wh-prose",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(ee,{eyebrow:"amplop",title:"Anatomi amplop",children:e.jsx("div",{className:"hn-local-overflow",children:e.jsx("table",{className:"hn-wh-table hn-wh-table--full",children:e.jsx("tbody",{children:Nn.map(([i,r])=>e.jsxs("tr",{className:"hn-wh-tr",children:[e.jsx("td",{className:"hn-wh-cell hn-wh-cell--mono hn-wh-cell--nowrap hn-wh-cell--top",children:i}),e.jsx("td",{className:"hn-wh-cell hn-wh-cell--muted",children:r})]},i))})})})}),e.jsxs(ee,{eyebrow:"contoh",title:"Contoh payload per jenis",children:[e.jsx("div",{className:"hn-wh-prose hn-wh-mb10",children:"Contoh di bawah dibangun dari katalog yang sama dengan pengirimnya — bukan salinan yang bisa jadi data lama. Salin salah satunya sebagai fixture test penerima Anda."}),e.jsx("div",{className:"hn-wh-col-16",children:ge.map(i=>e.jsxs("div",{children:[e.jsx("div",{className:"hn-wh-type-head",children:e.jsx("code",{className:"hn-wh-type",children:i.type})}),e.jsx(ve,{text:JSON.stringify(Ra(i.type),null,2),label:`Salin contoh ${i.type}`})]},i.type))})]}),e.jsxs(ee,{eyebrow:"keamanan",title:"Header dan verifikasi tanda tangan",children:[e.jsx("div",{className:"hn-wh-prose",children:"Setiap permintaan membawa header berikut:"}),e.jsx("ul",{className:"hn-wh-prose hn-wh-list",children:Object.values(ue).map(i=>e.jsx("li",{children:e.jsx("code",{className:"hn-wh-mono",children:i})},i))}),e.jsxs("div",{className:"hn-wh-prose",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:[Ae," detik"]})," supaya kiriman lama tak bisa diputar ulang."]}),e.jsx("div",{className:"hn-wh-mt12",children:e.jsx(ve,{text:wn,label:"Salin contoh Node.js"})}),e.jsx("div",{className:"hn-wh-mt12",children:e.jsx(ve,{text:Sn,label:"Salin contoh Python"})})]}),e.jsxs(ee,{eyebrow:"pengiriman",title:"Kirim ulang, pengiriman ganda, dan idempotensi",children:[e.jsxs("div",{className:"hn-wh-prose",children:["Pengiriman berjalan lewat antrean, jadi penerima yang lambat tak pernah memperlambat hanoman. Pengiriman gagal diulang sampai ",e.jsxs("b",{children:[za," percobaan"]})," dengan jeda berikut:"]}),e.jsx("div",{className:"hn-local-overflow hn-wh-mt8",children:e.jsx("table",{className:"hn-wh-table",children:e.jsxs("tbody",{children:[e.jsxs("tr",{children:[e.jsx("td",{className:"hn-wh-cell--tight hn-wh-cell--muted",children:"Percobaan"}),De.map((i,r)=>e.jsx("td",{className:"hn-wh-cell--tight hn-wh-cell--mono",children:r+1},r))]}),e.jsxs("tr",{className:"hn-wh-tr",children:[e.jsx("td",{className:"hn-wh-cell--tight hn-wh-cell--muted",children:"Jeda sebelumnya"}),De.map((i,r)=>e.jsx("td",{className:"hn-wh-cell--tight hn-wh-cell--mono",children:i===0?"—":i<60?`${i} dtk`:i<3600?`${i/60} mnt`:`${i/3600} jam`},r))]})]})})}),e.jsx(te,{tone:"warn",title:"Penerima WAJIB idempoten",className:"hn-wh-mt12",children:e.jsxs("div",{className:"hn-wh-prose",children:["Kontraknya ",e.jsx("b",{children:"at-least-once"}),": satu peristiwa bisa tiba lebih dari sekali — kirim ulang setelah jaringan putus di tengah, atau setelah hanoman di-restart saat pengiriman sedang berjalan. Kirim ulang 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",{className:"hn-wh-prose hn-wh-mt10",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:[Fa," pengiriman gagal beruntun"]})," (yakni yang sudah kehabisan seluruh percobaannya) menonaktifkan endpoint otomatis, dan operator diberi notifikasi."]})]}),e.jsx(ee,{eyebrow:"batas",title:"Batas ukuran, laju, dan antrean",children:e.jsxs("div",{className:"hn-wh-prose",children:["Amplop dibatasi ",e.jsxs("b",{children:[Math.round($a/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:Ka})," kiriman menunggu — kelebihannya tercatat sebagai"," ",e.jsx("code",{children:"dropped"})," di riwayat, bukan hilang diam-diam."]})}),e.jsxs(ee,{eyebrow:"keamanan",title:"Yang kami jaga, dan yang tetap jadi tanggung jawab Anda",children:[e.jsxs("div",{className:"hn-wh-prose",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(te,{tone:"warn",title:"Batas yang jujur",className:"hn-wh-mt10",children:e.jsxs("div",{className:"hn-wh-prose",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(ee,{eyebrow:"panduan",title:"Membuat penerima webhook pertama Anda",children:e.jsxs("div",{className:"hn-wh-col-12",children:[e.jsxs("div",{children:[e.jsx("div",{className:"hn-wh-step",children:"Langkah 1 — jalankan penerima"}),e.jsxs("div",{className:"hn-wh-prose",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",{className:"hn-wh-step",children:"Langkah 2 — buat URL-nya bisa dijangkau"}),e.jsxs("div",{className:"hn-wh-prose",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",{className:"hn-wh-step",children:"Langkah 3 — daftarkan endpoint"}),e.jsxs("div",{className:"hn-wh-prose",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",{className:"hn-wh-step",children:"Langkah 4 — tekan Test, lalu buktikan"}),e.jsxs("div",{className:"hn-wh-prose",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."]})]})]})})]})}function En({s:a,store:t,onToast:i,codexVer:r}){var u,_,R;const{confirm:d,dialog:h}=ie(),l=a.telegram??ae,[s,m]=c.useState(null),[j,k]=c.useState(!1),b=c.useCallback(()=>{k(!1),m(null),y.getTelegramStatus().then(m).catch(()=>k(!0))},[]);c.useEffect(()=>{b()},[b]);const[p,f]=c.useState(null),[g,S]=c.useState({}),[P,w]=c.useState(null),[D,O]=c.useState(!1),N=c.useCallback(()=>{y.getTelegramCredentials().then(n=>{f(n),S({})}).catch(()=>f(null))},[]);c.useEffect(()=>{N()},[N]);const I=$({value:l.enabled,delayMs:0,save:n=>t.save({telegram:{...l,enabled:n}},`Gateway Telegram · ${n?"aktif":"nonaktif"}`)}),z=$({value:l.progress,delayMs:0,save:n=>t.save({telegram:{...l,progress:n}},`Progress Telegram · ${n?"aktif":"nonaktif"}`)}),C=l.engine??ae.engine,H=a.agent??"claude",L=a.codex??Ce,Q={claude:{model:a.model,effort:a.effort},codex:{model:L.model,effort:L.effort},antigravity:a.antigravity??de},V={agent:H,...Ee({claude:{model:a.model,effort:a.effort},codex:L,antigravity:a.antigravity??de},H)},Y=n=>n==="db"?e.jsx(X,{tone:"ok",children:"tersimpan"}):n==="env"?e.jsx(X,{tone:"warn",children:"dari .env · deprecated"}):e.jsx(X,{children:"belum diisi"}),U=Object.values(g).some(n=>n!==""),o=()=>{const n=Object.fromEntries(Object.entries(g).filter(([,v])=>v!==""));if(!Object.keys(n).length){i==null||i("Tak ada perubahan","info","info");return}O(!0),y.putTelegramCredentials(n).then(v=>{f(v),S({}),w(null),i==null||i("Kredensial Telegram disimpan","ok","check-circle-2"),b()}).catch(v=>i==null?void 0:i(K("menyimpan kredensial Telegram",v),"err","alert-triangle")).finally(()=>O(!1))},A=()=>{w("sending"),y.testTelegramConnection().then(w).catch(n=>w({ok:!1,error:n.message||"Gagal menghubungi server",inbound:{ok:!1,reason:"Status jalur masuk tak terbaca — server tak menjawab.",missingCapabilities:[],polling:!1}}))},B=async()=>{let n=null;try{if(!await d({title:"Hapus kredensial Telegram?",message:"Gateway berhenti kecuali nilai .env lama masih tersedia.",impact:["Keempat nilai dihapus dari database.","Gateway berhenti menerima pesan sampai kredensial diisi lagi."],confirmLabel:"Hapus kredensial",tone:"danger",icon:"trash-2",run:async()=>{n=await y.deleteTelegramCredentials()}}))return;const F=(n==null?void 0:n.envFallback.length)??0;i==null||i(F?`Kredensial dihapus — ${F} nilai masih datang dari .env`:"Kredensial Telegram dihapus","ok","check-circle-2"),w(null),N(),b()}catch(v){i==null||i(K("menghapus kredensial Telegram",v),"err","alert-triangle")}};return e.jsxs(e.Fragment,{children:[e.jsxs(M,{eyebrow:"telegram",title:"Kredensial Telegram","data-setting-card":!0,"data-search":"kredensial telegram bot token chat allowlist agenttoken",children:[e.jsxs(G,{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. Perubahan baru tersimpan saat tombol ",e.jsx("b",{children:"Simpan kredensial"})," ditekan."]}),p?e.jsxs("form",{onSubmit:n=>{n.preventDefault(),U&&!D&&o()},children:[p.fields.map((n,v)=>{var F,ce;return e.jsx(E,{title:n.label,desc:n.help,last:v===p.fields.length-1,children:e.jsxs("div",{className:"hn-set-inline",children:[Y(n.source),e.jsx(q,{"aria-label":n.label,mono:!0,type:n.kind==="secret"?"password":"text",placeholder:n.kind==="secret"?n.masked??((F=ke(n.key))==null?void 0:F.example)??"belum diisi":((ce=ke(n.key))==null?void 0:ce.example)??"belum diisi",value:g[n.key]??(n.kind==="secret"?"":n.value??""),onChange:Xe=>S(Ze=>({...Ze,[n.key]:Xe.target.value})),className:"hn-set-w-input"})]})},n.key)}),e.jsxs("div",{className:"hn-set-form-actions",children:[e.jsx("span",{className:"hn-set-dirty",role:"status",children:U?"Ada perubahan belum disimpan.":"Belum ada perubahan."}),e.jsx(x,{size:"sm",type:"submit",leftIcon:"save",disabled:!U||D,children:"Simpan kredensial"})]})]}):e.jsx(W,{kind:"loading",compact:!0,title:"Memuat kredensial…"})]}),e.jsxs(M,{eyebrow:"uji",title:"Uji koneksi & hapus","data-setting-card":!0,"data-search":"uji koneksi test connection hapus kredensial",actions:e.jsx(x,{size:"sm",variant:"ghost",leftIcon:"refresh-cw",onClick:N,children:"Refresh"}),children:[e.jsx(E,{title:"Test Connection",desc:"Mengirim satu pesan percobaan ke chat tujuan. Batas 10 detik — tak pernah menggantung.",children:e.jsx(x,{size:"sm",leftIcon:"send",disabled:P==="sending",onClick:A,children:"Test Connection"})}),P&&P!=="sending"&&e.jsxs("div",{className:"hn-set-result",children:[P.ok?e.jsxs(te,{tone:"ok",role:"status",children:["Berhasil — bot @",P.botUsername??"?"," mengirim ke chat ",P.chatId,"."]}):e.jsx(te,{tone:"err",role:"alert",children:P.error}),(u=P.inbound)!=null&&u.ok?e.jsx(te,{tone:"ok",role:"status",children:"Jalur masuk siap — gateway sedang long polling dan token agen (AgentToken)-nya sah."}):e.jsxs(te,{tone:"warn",role:"alert",children:["Jalur masuk BELUM siap: ",((_=P.inbound)==null?void 0:_.reason)??"status tak terbaca.",!!((R=P.inbound)!=null&&R.missingCapabilities.length)&&e.jsxs(e.Fragment,{children:[" Capability yang kurang: ",e.jsx("code",{children:P.inbound.missingCapabilities.join(", ")}),"."]})," ","Selama ini merah, pesan Telegram tidak akan pernah tertangkap."]})]}),e.jsx(E,{title:"Hapus kredensial",last:!0,desc:"Menghapus keempat nilai dari database. Bila .env lama masih terisi, nilainya kembali dipakai.",children:e.jsx(x,{size:"sm",variant:"danger",leftIcon:"trash-2",onClick:()=>void B(),children:"Hapus kredensial"})})]}),e.jsxs(M,{eyebrow:"gateway",title:"Gateway Telegram","data-setting-card":!0,"data-search":"gateway telegram aktif progress readiness allowlist onboarding",actions:e.jsx(x,{size:"sm",variant:"ghost",leftIcon:"refresh-cw",onClick:b,children:"Refresh"}),children:[e.jsx(G,{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(E,{title:"Gateway aktif",save:I,desc:"Menyalakan long polling in-process seketika. Mematikan tidak membunuh session tmux atau memory.",children:e.jsx(J,{"aria-label":"Gateway aktif",checked:I.value,onChange:n=>I.set(n)})}),e.jsx(E,{title:"Kirim progress ringkas",save:z,desc:"Hanya progress eksplisit dan fakta status Hanoman; layar PTY/reasoning tidak pernah diteruskan.",children:e.jsx(J,{"aria-label":"Kirim progress ringkas",checked:z.value,onChange:n=>z.set(n)})}),j?e.jsx(W,{kind:"error",compact:!0,title:"Gagal membaca status Telegram",action:b}):s?e.jsxs(e.Fragment,{children:[e.jsx(E,{title:`Readiness · ${s.readiness??"memuat"}`,desc:s.running?"Long poll aktif.":s.lastError??"Gateway belum polling.",children:e.jsx("span",{className:"hn-set-mono",children:s.botUsername?`@${s.botUsername}`:"bot belum terverifikasi"})}),e.jsx(E,{title:"Allowlist",last:!s.missingCapabilities.length,desc:`${s.allowlistCount} Telegram numeric user id diizinkan.`,children:e.jsx("span",{children:s.configured?"kredensial lengkap":"kredensial belum lengkap"})}),s.missingCapabilities.length>0&&e.jsx(E,{title:"Capability kurang",last:!0,desc:s.missingCapabilities.join(", ")})]}):e.jsx(W,{kind:"loading",compact:!0,title:"Memuat status Telegram…"}),e.jsxs("div",{className:"hn-set-onboarding",children:[e.jsx("b",{children:"Onboarding"}),e.jsxs("ol",{children:[e.jsx("li",{children:"Buat satu bot private-chat lewat BotFather, salin token-nya."}),e.jsxs("li",{children:["Di Akses AI Agent, aktifkan master switch dan buat ",e.jsx("b",{children:"token agen (AgentToken)"})," dengan capability yang ditampilkan status."]}),e.jsx("li",{children:"Isi keempat field di kartu Kredensial lalu tekan Simpan kredensial."}),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."]})]})]})]}),e.jsx(pe,{eyebrow:"telegram",title:"Agen operator Telegram",desc:e.jsxs("span",{"data-testid":"telegram-engine-desc",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 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."]}),toggleDesc:"Mati = ikut default global. Hidup = sesi operator Telegram memakai pilihan di bawah.",toggleAria:"Override agen Telegram",enabled:C.enabled,onToggle:n=>Re(t,i,{enabled:n},"Setelan operator Telegram"+(n?" · aktif":" · ikut default global")),inheritedTestId:"telegram-engine-inherited",inherited:e.jsxs(e.Fragment,{children:["Sesi operator Telegram memakai default global: ",e.jsx("b",{children:ne[V.agent]})," · ",e.jsx("code",{children:V.model})," · ",e.jsx("code",{children:V.effort}),"."]}),children:e.jsx(Ne,{engine:C,labels:{agentTitle:"Runtime",agentDesc:"Mesin yang menjalankan sesi operator. Bisa beda dari agen sesi kerja.",effortDesc:"Operator Telegram jarang menulis kode — effort rendah memangkas latensi balasan secara langsung.",aria:{agent:"Runtime Telegram",model:"Model Telegram",effort:"Effort Telegram"},msg:{agent:"Runtime operator Telegram",model:"Model operator Telegram",effort:"Effort operator Telegram"}},defaults:Q,codexVer:r,save:(n,v)=>Re(t,i,n,v)})}),h]})}function An({onToast:a}){return e.jsxs(e.Fragment,{children:[e.jsx(yn,{onToast:a}),e.jsxs("details",{className:"hn-set-fold","data-testid":"webhook-docs-fold",children:[e.jsxs("summary",{className:"hn-set-fold-summary",children:[e.jsx(le,{name:"book-open",size:14})," Dokumentasi webhook"]}),e.jsx(Cn,{})]})]})}const On="https://github.com/denameidina/hanoman/blob/main/docs/agent-integration.md";function Pn({onToast:a}){const[t,i]=c.useState(null),[r,d]=c.useState(!1),[h,l]=c.useState(!1),[s,m]=c.useState(!1),j=`${window.location.origin}${Ga.agentDoc}`;async function k(){if(!s){if(t){d(!0);return}m(!0),l(!1);try{i(await y.agentDoc()),d(!0)}catch{l(!0)}finally{m(!1)}}}async function b(){try{if(!navigator.clipboard)throw new Error("Clipboard tidak tersedia di browser ini");await navigator.clipboard.writeText(j),a==null||a("Tautan disalin","ok","copy")}catch(p){a==null||a(K("menyalin link",p),"err")}}return e.jsxs(e.Fragment,{children:[e.jsxs(M,{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:j}),e.jsxs("div",{style:{display:"flex",gap:8,flexWrap:"wrap",alignItems:"center"},children:[e.jsx(x,{size:"sm",leftIcon:"copy",onClick:()=>void b(),children:"Salin tautan"}),e.jsx(x,{size:"sm",variant:"ghost",leftIcon:"book-open",disabled:s,onClick:()=>void k(),children:s?"Memuat…":"Buka"}),e.jsx(x,{as:"a",size:"sm",variant:"ghost",leftIcon:"external-link",href:On,target:"_blank",rel:"noreferrer",children:"Lihat di GitHub"})]}),h&&e.jsx("div",{style:{marginTop:12},children:e.jsx(W,{kind:"error",compact:!0,title:"Gagal memuat dokumentasi",hint:j,action:()=>void k(),actionLabel:"Coba lagi"})})]}),r&&t!==null&&e.jsx(ln,{path:"agent-integration.md",text:t,eyebrow:"hanoman · panduan AI agent",onClose:()=>d(!1)})]})}const Fe=[{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:"antigravity",label:"Antigravity",hint:"~/.gemini/config/mcp_config.json — konfigurasi global MCP."},{id:"codex",label:"Codex",hint:"~/.codex/config.toml"},{id:"cursor",label:"Cursor / Copilot",hint:"~/.cursor/mcp.json atau .vscode/mcp.json di project."}],Mn={"read-only":"--read-only",default:"",danger:"--danger"};function In(a,t,i){const r=Mn[i],d=r?`["mcp", "${r}"]`:'["mcp"]',h=`{
45
+ return "", 200`,Nn=[["specVersion",`Versi skema amplop. Hari ini selalu "${Ue}". 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 ee({eyebrow:a,title:t,children:i}){return e.jsx(M,{children:e.jsxs("details",{children:[e.jsxs("summary",{className:"hn-wh-fold-summary",children:[e.jsx("span",{className:"hn-eyebrow",children:a}),e.jsx("span",{className:"hn-wh-fold-title",children:t})]}),e.jsx("div",{className:"hn-wh-fold-body",children:i})]})})}function Cn({onBack:a}={}){const t=Ge.map(i=>({label:i.label,events:ge.filter(r=>r.entity===i.entity)})).concat([{label:"Webhook",events:ge.filter(i=>i.entity==="webhook")}]);return e.jsxs(e.Fragment,{children:[e.jsxs(M,{eyebrow:"dokumentasi",title:"Dokumentasi webhook",children:[a&&e.jsx("div",{className:"hn-wh-back",children:e.jsxs(x,{size:"sm",variant:"ghost",onClick:a,children:[e.jsx(le,{name:"arrow-left",size:14})," Kembali ke daftar endpoint"]})}),e.jsxs("div",{className:"hn-wh-prose",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:Ue}),", jadi penerima yang ditulis hari ini tetap bekerja saat jenis peristiwa baru ditambahkan."]}),e.jsx("div",{className:"hn-wh-prose hn-wh-mt10",children:"Setiap bagian di bawah bisa dibuka sendiri dan semuanya tertutup dulu, supaya halaman ini tak memanjang."})]}),e.jsxs(ee,{eyebrow:"katalog",title:"Jenis peristiwa dan kapan terpicu",children:[e.jsx("div",{className:"hn-wh-col-16",children:t.map(i=>e.jsxs("div",{children:[e.jsx("div",{className:"hn-wh-group",children:i.label}),e.jsx("div",{className:"hn-wh-col-10",children:i.events.map(r=>e.jsxs("div",{className:"hn-wh-event",children:[e.jsxs("div",{className:"hn-wh-event-head",children:[e.jsx("code",{className:"hn-wh-type",children:r.type}),e.jsx(X,{tone:"brass",size:"sm",children:r.entityLabel}),e.jsx("span",{className:"hn-wh-event-label",children:r.label})]}),e.jsx("div",{className:"hn-wh-prose hn-wh-mt4",children:r.when})]},r.type))})]},i.label))}),e.jsx(te,{tone:"info",title:"Satu perubahan = satu peristiwa",className:"hn-wh-mt14",children:e.jsxs("div",{className:"hn-wh-prose",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(ee,{eyebrow:"amplop",title:"Anatomi amplop",children:e.jsx("div",{className:"hn-local-overflow",children:e.jsx("table",{className:"hn-wh-table hn-wh-table--full",children:e.jsx("tbody",{children:Nn.map(([i,r])=>e.jsxs("tr",{className:"hn-wh-tr",children:[e.jsx("td",{className:"hn-wh-cell hn-wh-cell--mono hn-wh-cell--nowrap hn-wh-cell--top",children:i}),e.jsx("td",{className:"hn-wh-cell hn-wh-cell--muted",children:r})]},i))})})})}),e.jsxs(ee,{eyebrow:"contoh",title:"Contoh payload per jenis",children:[e.jsx("div",{className:"hn-wh-prose hn-wh-mb10",children:"Contoh di bawah dibangun dari katalog yang sama dengan pengirimnya — bukan salinan yang bisa jadi data lama. Salin salah satunya sebagai fixture test penerima Anda."}),e.jsx("div",{className:"hn-wh-col-16",children:ge.map(i=>e.jsxs("div",{children:[e.jsx("div",{className:"hn-wh-type-head",children:e.jsx("code",{className:"hn-wh-type",children:i.type})}),e.jsx(ve,{text:JSON.stringify(Ra(i.type),null,2),label:`Salin contoh ${i.type}`})]},i.type))})]}),e.jsxs(ee,{eyebrow:"keamanan",title:"Header dan verifikasi tanda tangan",children:[e.jsx("div",{className:"hn-wh-prose",children:"Setiap permintaan membawa header berikut:"}),e.jsx("ul",{className:"hn-wh-prose hn-wh-list",children:Object.values(ue).map(i=>e.jsx("li",{children:e.jsx("code",{className:"hn-wh-mono",children:i})},i))}),e.jsxs("div",{className:"hn-wh-prose",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:[Ae," detik"]})," supaya kiriman lama tak bisa diputar ulang."]}),e.jsx("div",{className:"hn-wh-mt12",children:e.jsx(ve,{text:wn,label:"Salin contoh Node.js"})}),e.jsx("div",{className:"hn-wh-mt12",children:e.jsx(ve,{text:Sn,label:"Salin contoh Python"})})]}),e.jsxs(ee,{eyebrow:"pengiriman",title:"Kirim ulang, pengiriman ganda, dan idempotensi",children:[e.jsxs("div",{className:"hn-wh-prose",children:["Pengiriman berjalan lewat antrean, jadi penerima yang lambat tak pernah memperlambat hanoman. Pengiriman gagal diulang sampai ",e.jsxs("b",{children:[za," percobaan"]})," dengan jeda berikut:"]}),e.jsx("div",{className:"hn-local-overflow hn-wh-mt8",children:e.jsx("table",{className:"hn-wh-table",children:e.jsxs("tbody",{children:[e.jsxs("tr",{children:[e.jsx("td",{className:"hn-wh-cell--tight hn-wh-cell--muted",children:"Percobaan"}),De.map((i,r)=>e.jsx("td",{className:"hn-wh-cell--tight hn-wh-cell--mono",children:r+1},r))]}),e.jsxs("tr",{className:"hn-wh-tr",children:[e.jsx("td",{className:"hn-wh-cell--tight hn-wh-cell--muted",children:"Jeda sebelumnya"}),De.map((i,r)=>e.jsx("td",{className:"hn-wh-cell--tight hn-wh-cell--mono",children:i===0?"—":i<60?`${i} dtk`:i<3600?`${i/60} mnt`:`${i/3600} jam`},r))]})]})})}),e.jsx(te,{tone:"warn",title:"Penerima WAJIB idempoten",className:"hn-wh-mt12",children:e.jsxs("div",{className:"hn-wh-prose",children:["Kontraknya ",e.jsx("b",{children:"at-least-once"}),": satu peristiwa bisa tiba lebih dari sekali — kirim ulang setelah jaringan putus di tengah, atau setelah hanoman di-restart saat pengiriman sedang berjalan. Kirim ulang 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",{className:"hn-wh-prose hn-wh-mt10",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:[Fa," pengiriman gagal beruntun"]})," (yakni yang sudah kehabisan seluruh percobaannya) menonaktifkan endpoint otomatis, dan operator diberi notifikasi."]})]}),e.jsx(ee,{eyebrow:"batas",title:"Batas ukuran, laju, dan antrean",children:e.jsxs("div",{className:"hn-wh-prose",children:["Amplop dibatasi ",e.jsxs("b",{children:[Math.round($a/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:Ka})," kiriman menunggu — kelebihannya tercatat sebagai"," ",e.jsx("code",{children:"dropped"})," di riwayat, bukan hilang diam-diam."]})}),e.jsxs(ee,{eyebrow:"keamanan",title:"Yang kami jaga, dan yang tetap jadi tanggung jawab Anda",children:[e.jsxs("div",{className:"hn-wh-prose",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(te,{tone:"warn",title:"Batas yang jujur",className:"hn-wh-mt10",children:e.jsxs("div",{className:"hn-wh-prose",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(ee,{eyebrow:"panduan",title:"Membuat penerima webhook pertama Anda",children:e.jsxs("div",{className:"hn-wh-col-12",children:[e.jsxs("div",{children:[e.jsx("div",{className:"hn-wh-step",children:"Langkah 1 — jalankan penerima"}),e.jsxs("div",{className:"hn-wh-prose",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",{className:"hn-wh-step",children:"Langkah 2 — buat URL-nya bisa dijangkau"}),e.jsxs("div",{className:"hn-wh-prose",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",{className:"hn-wh-step",children:"Langkah 3 — daftarkan endpoint"}),e.jsxs("div",{className:"hn-wh-prose",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",{className:"hn-wh-step",children:"Langkah 4 — tekan Test, lalu buktikan"}),e.jsxs("div",{className:"hn-wh-prose",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."]})]})]})})]})}function En({s:a,store:t,onToast:i,codexVer:r}){var u,_,R;const{confirm:d,dialog:h}=ie(),l=a.telegram??ae,[s,m]=c.useState(null),[j,k]=c.useState(!1),b=c.useCallback(()=>{k(!1),m(null),y.getTelegramStatus().then(m).catch(()=>k(!0))},[]);c.useEffect(()=>{b()},[b]);const[p,f]=c.useState(null),[g,S]=c.useState({}),[P,w]=c.useState(null),[D,O]=c.useState(!1),N=c.useCallback(()=>{y.getTelegramCredentials().then(n=>{f(n),S({})}).catch(()=>f(null))},[]);c.useEffect(()=>{N()},[N]);const I=$({value:l.enabled,delayMs:0,save:n=>t.save({telegram:{...l,enabled:n}},`Gateway Telegram · ${n?"aktif":"nonaktif"}`)}),z=$({value:l.progress,delayMs:0,save:n=>t.save({telegram:{...l,progress:n}},`Progress Telegram · ${n?"aktif":"nonaktif"}`)}),C=l.engine??ae.engine,H=a.agent??"claude",L=a.codex??Ce,Q={claude:{model:a.model,effort:a.effort},codex:{model:L.model,effort:L.effort},antigravity:a.antigravity??de},q={agent:H,...Ee({claude:{model:a.model,effort:a.effort},codex:L,antigravity:a.antigravity??de},H)},Y=n=>n==="db"?e.jsx(X,{tone:"ok",children:"tersimpan"}):n==="env"?e.jsx(X,{tone:"warn",children:"dari .env · deprecated"}):e.jsx(X,{children:"belum diisi"}),U=Object.values(g).some(n=>n!==""),o=()=>{const n=Object.fromEntries(Object.entries(g).filter(([,v])=>v!==""));if(!Object.keys(n).length){i==null||i("Tak ada perubahan","info","info");return}O(!0),y.putTelegramCredentials(n).then(v=>{f(v),S({}),w(null),i==null||i("Kredensial Telegram disimpan","ok","check-circle-2"),b()}).catch(v=>i==null?void 0:i(K("menyimpan kredensial Telegram",v),"err","alert-triangle")).finally(()=>O(!1))},A=()=>{w("sending"),y.testTelegramConnection().then(w).catch(n=>w({ok:!1,error:n.message||"Gagal menghubungi server",inbound:{ok:!1,reason:"Status jalur masuk tak terbaca — server tak menjawab.",missingCapabilities:[],polling:!1}}))},B=async()=>{let n=null;try{if(!await d({title:"Hapus kredensial Telegram?",message:"Gateway berhenti kecuali nilai .env lama masih tersedia.",impact:["Keempat nilai dihapus dari database.","Gateway berhenti menerima pesan sampai kredensial diisi lagi."],confirmLabel:"Hapus kredensial",tone:"danger",icon:"trash-2",run:async()=>{n=await y.deleteTelegramCredentials()}}))return;const F=(n==null?void 0:n.envFallback.length)??0;i==null||i(F?`Kredensial dihapus — ${F} nilai masih datang dari .env`:"Kredensial Telegram dihapus","ok","check-circle-2"),w(null),N(),b()}catch(v){i==null||i(K("menghapus kredensial Telegram",v),"err","alert-triangle")}};return e.jsxs(e.Fragment,{children:[e.jsxs(M,{eyebrow:"telegram",title:"Kredensial Telegram","data-setting-card":!0,"data-search":"kredensial telegram bot token chat allowlist agenttoken",children:[e.jsxs(G,{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. Perubahan baru tersimpan saat tombol ",e.jsx("b",{children:"Simpan kredensial"})," ditekan."]}),p?e.jsxs("form",{onSubmit:n=>{n.preventDefault(),U&&!D&&o()},children:[p.fields.map((n,v)=>{var F,ce;return e.jsx(E,{title:n.label,desc:n.help,last:v===p.fields.length-1,children:e.jsxs("div",{className:"hn-set-inline",children:[Y(n.source),e.jsx(V,{"aria-label":n.label,mono:!0,type:n.kind==="secret"?"password":"text",placeholder:n.kind==="secret"?n.masked??((F=ke(n.key))==null?void 0:F.example)??"belum diisi":((ce=ke(n.key))==null?void 0:ce.example)??"belum diisi",value:g[n.key]??(n.kind==="secret"?"":n.value??""),onChange:Xe=>S(Ze=>({...Ze,[n.key]:Xe.target.value})),className:"hn-set-w-input"})]})},n.key)}),e.jsxs("div",{className:"hn-set-form-actions",children:[e.jsx("span",{className:"hn-set-dirty",role:"status",children:U?"Ada perubahan belum disimpan.":"Belum ada perubahan."}),e.jsx(x,{size:"sm",type:"submit",leftIcon:"save",disabled:!U||D,children:"Simpan kredensial"})]})]}):e.jsx(W,{kind:"loading",compact:!0,title:"Memuat kredensial…"})]}),e.jsxs(M,{eyebrow:"uji",title:"Uji koneksi & hapus","data-setting-card":!0,"data-search":"uji koneksi test connection hapus kredensial",actions:e.jsx(x,{size:"sm",variant:"ghost",leftIcon:"refresh-cw",onClick:N,children:"Refresh"}),children:[e.jsx(E,{title:"Test Connection",desc:"Mengirim satu pesan percobaan ke chat tujuan. Batas 10 detik — tak pernah menggantung.",children:e.jsx(x,{size:"sm",leftIcon:"send",disabled:P==="sending",onClick:A,children:"Test Connection"})}),P&&P!=="sending"&&e.jsxs("div",{className:"hn-set-result",children:[P.ok?e.jsxs(te,{tone:"ok",role:"status",children:["Berhasil — bot @",P.botUsername??"?"," mengirim ke chat ",P.chatId,"."]}):e.jsx(te,{tone:"err",role:"alert",children:P.error}),(u=P.inbound)!=null&&u.ok?e.jsx(te,{tone:"ok",role:"status",children:"Jalur masuk siap — gateway sedang long polling dan token agen (AgentToken)-nya sah."}):e.jsxs(te,{tone:"warn",role:"alert",children:["Jalur masuk BELUM siap: ",((_=P.inbound)==null?void 0:_.reason)??"status tak terbaca.",!!((R=P.inbound)!=null&&R.missingCapabilities.length)&&e.jsxs(e.Fragment,{children:[" Capability yang kurang: ",e.jsx("code",{children:P.inbound.missingCapabilities.join(", ")}),"."]})," ","Selama ini merah, pesan Telegram tidak akan pernah tertangkap."]})]}),e.jsx(E,{title:"Hapus kredensial",last:!0,desc:"Menghapus keempat nilai dari database. Bila .env lama masih terisi, nilainya kembali dipakai.",children:e.jsx(x,{size:"sm",variant:"danger",leftIcon:"trash-2",onClick:()=>void B(),children:"Hapus kredensial"})})]}),e.jsxs(M,{eyebrow:"gateway",title:"Gateway Telegram","data-setting-card":!0,"data-search":"gateway telegram aktif progress readiness allowlist onboarding",actions:e.jsx(x,{size:"sm",variant:"ghost",leftIcon:"refresh-cw",onClick:b,children:"Refresh"}),children:[e.jsx(G,{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(E,{title:"Gateway aktif",save:I,desc:"Menyalakan long polling in-process seketika. Mematikan tidak membunuh session tmux atau memory.",children:e.jsx(J,{"aria-label":"Gateway aktif",checked:I.value,onChange:n=>I.set(n)})}),e.jsx(E,{title:"Kirim progress ringkas",save:z,desc:"Hanya progress eksplisit dan fakta status Hanoman; layar PTY/reasoning tidak pernah diteruskan.",children:e.jsx(J,{"aria-label":"Kirim progress ringkas",checked:z.value,onChange:n=>z.set(n)})}),j?e.jsx(W,{kind:"error",compact:!0,title:"Gagal membaca status Telegram",action:b}):s?e.jsxs(e.Fragment,{children:[e.jsx(E,{title:`Readiness · ${s.readiness??"memuat"}`,desc:s.running?"Long poll aktif.":s.lastError??"Gateway belum polling.",children:e.jsx("span",{className:"hn-set-mono",children:s.botUsername?`@${s.botUsername}`:"bot belum terverifikasi"})}),e.jsx(E,{title:"Allowlist",last:!s.missingCapabilities.length,desc:`${s.allowlistCount} Telegram numeric user id diizinkan.`,children:e.jsx("span",{children:s.configured?"kredensial lengkap":"kredensial belum lengkap"})}),s.missingCapabilities.length>0&&e.jsx(E,{title:"Capability kurang",last:!0,desc:s.missingCapabilities.join(", ")})]}):e.jsx(W,{kind:"loading",compact:!0,title:"Memuat status Telegram…"}),e.jsxs("div",{className:"hn-set-onboarding",children:[e.jsx("b",{children:"Onboarding"}),e.jsxs("ol",{children:[e.jsx("li",{children:"Buat satu bot private-chat lewat BotFather, salin token-nya."}),e.jsxs("li",{children:["Di Akses AI Agent, aktifkan master switch dan buat ",e.jsx("b",{children:"token agen (AgentToken)"})," dengan capability yang ditampilkan status."]}),e.jsx("li",{children:"Isi keempat field di kartu Kredensial lalu tekan Simpan kredensial."}),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."]})]})]})]}),e.jsx(pe,{eyebrow:"telegram",title:"Agen operator Telegram",desc:e.jsxs("span",{"data-testid":"telegram-engine-desc",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 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."]}),toggleDesc:"Mati = ikut default global. Hidup = sesi operator Telegram memakai pilihan di bawah.",toggleAria:"Override agen Telegram",enabled:C.enabled,onToggle:n=>Re(t,i,{enabled:n},"Setelan operator Telegram"+(n?" · aktif":" · ikut default global")),inheritedTestId:"telegram-engine-inherited",inherited:e.jsxs(e.Fragment,{children:["Sesi operator Telegram memakai default global: ",e.jsx("b",{children:ne[q.agent]})," · ",e.jsx("code",{children:q.model})," · ",e.jsx("code",{children:q.effort}),"."]}),children:e.jsx(Ne,{engine:C,labels:{agentTitle:"Runtime",agentDesc:"Mesin yang menjalankan sesi operator. Bisa beda dari agen sesi kerja.",effortDesc:"Operator Telegram jarang menulis kode — effort rendah memangkas latensi balasan secara langsung.",aria:{agent:"Runtime Telegram",model:"Model Telegram",effort:"Effort Telegram"},msg:{agent:"Runtime operator Telegram",model:"Model operator Telegram",effort:"Effort operator Telegram"}},defaults:Q,codexVer:r,save:(n,v)=>Re(t,i,n,v)})}),h]})}function An({onToast:a}){return e.jsxs(e.Fragment,{children:[e.jsx(yn,{onToast:a}),e.jsxs("details",{className:"hn-set-fold","data-testid":"webhook-docs-fold",children:[e.jsxs("summary",{className:"hn-set-fold-summary",children:[e.jsx(le,{name:"book-open",size:14})," Dokumentasi webhook"]}),e.jsx(Cn,{})]})]})}const On="https://github.com/denameidina/hanoman/blob/main/docs/agent-integration.md";function Pn({onToast:a}){const[t,i]=c.useState(null),[r,d]=c.useState(!1),[h,l]=c.useState(!1),[s,m]=c.useState(!1),j=`${window.location.origin}${Ga.agentDoc}`;async function k(){if(!s){if(t){d(!0);return}m(!0),l(!1);try{i(await y.agentDoc()),d(!0)}catch{l(!0)}finally{m(!1)}}}async function b(){try{if(!navigator.clipboard)throw new Error("Clipboard tidak tersedia di browser ini");await navigator.clipboard.writeText(j),a==null||a("Tautan disalin","ok","copy")}catch(p){a==null||a(K("menyalin link",p),"err")}}return e.jsxs(e.Fragment,{children:[e.jsxs(M,{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:j}),e.jsxs("div",{style:{display:"flex",gap:8,flexWrap:"wrap",alignItems:"center"},children:[e.jsx(x,{size:"sm",leftIcon:"copy",onClick:()=>void b(),children:"Salin tautan"}),e.jsx(x,{size:"sm",variant:"ghost",leftIcon:"book-open",disabled:s,onClick:()=>void k(),children:s?"Memuat…":"Buka"}),e.jsx(x,{as:"a",size:"sm",variant:"ghost",leftIcon:"external-link",href:On,target:"_blank",rel:"noreferrer",children:"Lihat di GitHub"})]}),h&&e.jsx("div",{style:{marginTop:12},children:e.jsx(W,{kind:"error",compact:!0,title:"Gagal memuat dokumentasi",hint:j,action:()=>void k(),actionLabel:"Coba lagi"})})]}),r&&t!==null&&e.jsx(ln,{path:"agent-integration.md",text:t,eyebrow:"hanoman · panduan AI agent",onClose:()=>d(!1)})]})}const Fe=[{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:"antigravity",label:"Antigravity",hint:"~/.gemini/config/mcp_config.json — konfigurasi global MCP."},{id:"codex",label:"Codex",hint:"~/.codex/config.toml"},{id:"cursor",label:"Cursor / Copilot",hint:"~/.cursor/mcp.json atau .vscode/mcp.json di project."}],Mn={"read-only":"--read-only",default:"",danger:"--danger"};function In(a,t,i){const r=Mn[i],d=r?`["mcp", "${r}"]`:'["mcp"]',h=`{
46
46
  "mcpServers": {
47
47
  "hanoman": {
48
48
  "command": "hanoman",
@@ -59,4 +59,4 @@ args = ${d}
59
59
  env = { HANOMAN_HOST = "${t}", HANOMAN_AGENT_TOKEN = "hnm_agt_…" }`:a==="claude-code"?`${h}
60
60
 
61
61
  # atau, sekali jalan:
62
- claude mcp add hanoman --env HANOMAN_HOST=${t} --env HANOMAN_AGENT_TOKEN=hnm_agt_… -- hanoman mcp${r?" "+r:""}`:a==="cursor"?h.replace('"mcpServers"','"servers"'):h}function Dn(){const[a,t]=c.useState("claude-code"),[i,r]=c.useState("default"),[d,h]=c.useState(!1),l=typeof window>"u"?"http://localhost:8787":window.location.origin,s=In(a,l,i),m=Fe.find(k=>k.id===a),j=()=>{var k;(k=navigator.clipboard)==null||k.writeText(s).then(()=>{h(!0),setTimeout(()=>h(!1),1500)},()=>{})};return e.jsxs(e.Fragment,{children:[e.jsxs(M,{eyebrow:"mcp",title:"MCP server",children:[e.jsxs("div",{className:"hn-mcp-muted",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 ",dn,"."]})]}),e.jsxs("div",{className:"hn-mcp-muted",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",{className:"hn-mcp-muted",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",className:"hn-mcp-clients",children:[Fe.map(k=>e.jsx(x,{size:"sm",variant:k.id===a?"primary":"ghost",onClick:()=>t(k.id),children:k.label},k.id)),e.jsx(x,{size:"sm",variant:i==="read-only"?"primary":"ghost",leftIcon:"eye",onClick:()=>r(k=>k==="read-only"?"default":"read-only"),children:"Mode baca-saja"}),e.jsx(x,{size:"sm",variant:i==="danger"?"primary":"ghost",leftIcon:"alert-triangle",onClick:()=>r(k=>k==="danger"?"default":"danger"),children:"Tampilkan tool berbahaya"})]}),e.jsx("div",{className:"hn-mcp-muted hn-mcp-hint",children:m.hint}),a==="antigravity"&&e.jsxs("div",{"data-testid":"antigravity-mcp-guide",className:"hn-mcp-muted",children:["Gabungkan hanya entri ",e.jsx("code",{children:"mcpServers.hanoman"})," dari contoh ini; pertahankan server lain dan field yang tidak dikenal di root. Bila JSON rusak atau entri hanoman berbeda sudah ada, lakukan rekonsiliasi manual sebelum menyimpan. Gunakan izin berkas",e.jsx("code",{children:"0600"})," setelah mengganti placeholder dengan token yang kamu pilih di kartu akses agent; pilihan tingkat di sini tidak menambah capability token. Konfigurasi workspace ",e.jsx("code",{children:".agents/mcp_config.json"})," tetap utuh. Buka sesi Antigravity baru, periksa ",e.jsx("code",{children:"/mcp"}),", lalu panggil ",e.jsx("code",{children:"hanoman_about"})," untuk memverifikasi koneksi."]}),e.jsx("pre",{"data-testid":"mcp-snippet",className:"hn-mcp-pre",children:s}),e.jsx("div",{className:"hn-mcp-copy",children:e.jsx(x,{size:"sm",leftIcon:"copy",onClick:j,children:d?"Tersalin":"Salin"})})]}),e.jsxs(M,{eyebrow:"tool",title:"Tool yang tersedia",children:[e.jsxs("div",{className:"hn-mcp-muted",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",children:e.jsxs("table",{"data-testid":"mcp-tools",className:"hn-mcp-table",children:[e.jsx("thead",{children:e.jsxs("tr",{children:[e.jsx("th",{className:"hn-mcp-th",children:"Tool"}),e.jsx("th",{className:"hn-mcp-th",children:"Mode"}),e.jsx("th",{className:"hn-mcp-th",children:"Capability"})]})}),e.jsx("tbody",{children:rn.map(k=>e.jsxs("tr",{children:[e.jsxs("td",{className:"hn-mcp-td",children:[e.jsx("code",{children:k.name}),e.jsx("div",{className:"hn-mcp-sub",children:k.title})]}),e.jsx("td",{className:"hn-mcp-td",children:k.mode==="read"?"baca":"tulis"}),e.jsx("td",{className:"hn-mcp-td",children:k.capability?e.jsx("code",{children:k.capability}):e.jsx("span",{className:"hn-mcp-dash",children:"—"})})]},k.name))})]})})]})]})}const _n=[{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"},{had:"qa:write",needs:"qa:delete",sentence:"dulu bisa menghapus laporan, test case, temuan & lampiran QA"}];function $e(a){return _n.filter(t=>a.includes(t.had)&&!a.includes(t.needs)).map(t=>t.sentence)}function me({ids:a,picked:t,onToggle:i,ariaLabel:r,label:d}){const h=c.useRef(null),l=a.filter(m=>t.includes(m)).length,s=a.length>0&&l===a.length;return c.useEffect(()=>{h.current&&(h.current.indeterminate=l>0&&!s)},[l,s]),a.length===0?null:e.jsxs("label",{className:"hn-set-checkall",children:[d&&e.jsx("span",{children:d}),e.jsx("input",{ref:h,type:"checkbox","aria-label":r,checked:s,onChange:()=>i(a,!s)})]})}function Ln({onToast:a}={}){const{confirm:t,dialog:i}=ie(),r=Te()==="mobile",[d,h]=c.useState([]),[l,s]=c.useState(null),[m,j]=c.useState(null),[k,b]=c.useState(""),[p,f]=c.useState([]),[g,S]=c.useState(null),[P,w]=c.useState(!1),D=c.useId(),O=k.trim().length<1?"Isi nama token dulu.":null,N=c.useCallback(()=>{y.listAgentTokens().then(u=>s(u.items)).catch(()=>s([]))},[]);c.useEffect(()=>{y.getSettings().then(j).catch(u=>a==null?void 0:a(K("memuat pengaturan akses agen",u),"err","alert-triangle")),y.getAgentCapabilities().then(u=>h(u.capabilities)).catch(u=>a==null?void 0:a(K("memuat daftar capability agen",u),"err","alert-triangle")),N()},[N]);async function I(u){if(!m)return!1;const _={...m,agentAccessEnabled:u};j(_);try{const R=await y.getSettings().catch(()=>{});return await y.putSettings(Ye(m,_,R)),a==null||a("Akses AI agent "+(u?"aktif":"nonaktif"),u?"ok":"warn","bot"),!0}catch{return a==null||a("Gagal menyimpan","err","x-circle"),j(m),!1}}const z=$({value:!!(m!=null&&m.agentAccessEnabled),delayMs:0,save:I}),C=u=>f(_=>_.includes(u)?_.filter(R=>R!==u):[..._,u]),H=(u,_)=>f(R=>_?Array.from(new Set([...R,...u])):R.filter(n=>!u.includes(n)));async function L(){if(!(O||P)){w(!0);try{const u=await y.createAgentToken({name:k.trim(),capabilities:p});S({name:u.name,token:u.token}),b(""),f([]),N(),a==null||a("Agent token dibuat — salin sekarang","ok","key-round")}catch{a==null||a("Gagal membuat token","err","x-circle")}finally{w(!1)}}}async function Q(u){try{if(!await t({title:`Cabut agent token "${u.name}"?`,message:"Agen yang memakainya langsung kehilangan akses.",impact:[`Token "${u.name}" tak bisa dipakai lagi untuk masuk lewat API.`,"Tindakan ini tak bisa dibatalkan."],confirmLabel:"Cabut token",icon:"key-round",run:()=>y.revokeAgentToken(u.id)}))return;N(),a==null||a("Token dicabut","warn","trash-2")}catch{a==null||a("Gagal mencabut token","err","x-circle")}}async function V(u,_){try{await y.patchAgentToken(u.id,{enabled:_}),N()}catch{a==null||a("Gagal mengubah token","err","x-circle")}}const Y=Array.from(new Set(d.map(u=>u.domain))),U=u=>d.filter(_=>_.access===u).map(_=>_.id),o=d.map(u=>u.id),A=(l??[]).filter(u=>!u.revokedAt),B=(u,_)=>e.jsxs("div",{className:"hn-set-capcell",children:[u&&r&&e.jsx("span",{className:"hn-set-capword",children:_}),u&&e.jsx("input",{type:"checkbox","aria-label":u.label,checked:p.includes(u.id),onChange:()=>C(u.id)})]});return e.jsxs(e.Fragment,{children:[e.jsx(Pn,{onToast:a}),e.jsxs(M,{eyebrow:"ai agent",title:"Akses AI Agent","data-setting-card":!0,"data-search":"akses ai agent master switch capability",children:[e.jsxs(G,{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(E,{title:"Aktifkan akses AI agent",last:!0,save:z,desc:"Master switch. Nonaktif → semua agent token 401, apa pun capability-nya.",children:e.jsx(J,{checked:z.value,onChange:u=>z.set(u),"aria-label":"Aktifkan akses AI agent"})})]}),e.jsxs(M,{eyebrow:"token",title:"Agent tokens","data-setting-card":!0,"data-search":"agent token capability buat cabut",children:[g&&e.jsxs("div",{className:"hn-set-token-box",children:[e.jsxs("div",{className:"hn-set-token-title",children:["Token “",g.name,"” — salin sekarang, tak akan ditampilkan lagi:"]}),e.jsx("code",{className:"hn-set-token-code",children:g.token}),e.jsxs("div",{className:"hn-set-inline",children:[e.jsx(x,{size:"sm",leftIcon:"copy",onClick:()=>{var u;(u=navigator.clipboard)==null||u.writeText(g.token),a==null||a("Disalin","ok","copy")},children:"Salin"}),e.jsx(x,{size:"sm",variant:"ghost",onClick:()=>S(null),children:"Tutup"})]})]}),l===null?e.jsx(W,{kind:"loading",compact:!0,title:"Memuat token…"}):A.length===0?e.jsx("div",{className:"hn-set-empty",children:"Belum ada agent token."}):A.map((u,_)=>e.jsx(E,{title:u.name,last:_===A.length-1,desc:`${u.tokenPrefix}… · ${u.capabilities.length} capability · `+(u.lastUsedAt?"terpakai "+new Date(u.lastUsedAt).toLocaleString("id-ID"):"belum dipakai")+($e(u.capabilities).length?` — token ini ${$e(u.capabilities).join(", ")}; sekarang tidak, sampai capability berbahayanya dicentang. Cabut lalu buat ulang bila memang perlu.`:""),children:e.jsxs("div",{className:"hn-set-inline",children:[e.jsx(J,{size:"sm",checked:u.enabled,onChange:R=>void V(u,R),"aria-label":`Aktif: ${u.name}`}),e.jsx(x,{size:"sm",variant:"ghost",leftIcon:"trash-2",onClick:()=>Q(u),children:"Cabut"})]})},u.id)),e.jsxs("form",{className:"hn-set-form-block",onSubmit:u=>{u.preventDefault(),L()},children:[e.jsx(Z,{label:"Nama token",children:e.jsx(q,{value:k,placeholder:"mis. agent-ci",onChange:u=>b(u.target.value),className:"hn-set-full"})}),e.jsx("div",{className:"hn-set-subhead",children:"Capability"}),e.jsxs("div",{className:"hn-grid-mobile hn-set-capgrid",children:[e.jsx("div",{className:"hn-set-capcol-right",children:e.jsx(me,{label:"Pilih semua capability",ariaLabel:"pilih semua capability",ids:o,picked:p,onToggle:H})}),e.jsxs("div",{className:"hn-set-capcol",children:["baca",e.jsx(me,{ariaLabel:"pilih semua baca",ids:U("read"),picked:p,onToggle:H})]}),e.jsxs("div",{className:"hn-set-capcol",children:["tulis",e.jsx(me,{ariaLabel:"pilih semua tulis",ids:U("write"),picked:p,onToggle:H})]}),e.jsxs("div",{className:"hn-set-capcol hn-set-capcol--danger",children:["berbahaya",e.jsx(me,{ariaLabel:"pilih semua berbahaya",ids:U("danger"),picked:p,onToggle:H})]}),Y.map(u=>{const _=d.find(F=>F.domain===u&&F.access==="read"),R=d.find(F=>F.domain===u&&F.access==="write"),n=d.find(F=>F.domain===u&&F.access==="danger"),v=Ua.find(F=>F.domain===u);return e.jsxs(c.Fragment,{children:[e.jsxs("div",{className:"hn-set-capdomain",children:[e.jsxs("div",{className:"hn-set-capdomain-name",children:[(v==null?void 0:v.label)??u,R!=null&&R.risk||n!=null&&n.risk?" ⚠":""]}),(v==null?void 0:v.desc)&&e.jsx("div",{className:"hn-set-capdomain-desc",children:v.desc}),n&&e.jsxs("div",{className:"hn-set-capdomain-danger",children:[n.label.replace(/^[^—]+—\s*/,"berbahaya: ")," — ",n.desc]})]}),B(_,"baca"),B(R,"tulis"),B(n,"berbahaya")]},u)})]}),e.jsx("div",{className:"hn-set-form-actions",children:e.jsx(x,{size:"sm",type:"submit",leftIcon:"plus",disabled:!!O||P,"aria-describedby":O?D:void 0,children:"Buat token"})}),e.jsx(he,{id:D,text:O})]}),i]}),e.jsx(Dn,{})]})}function Bn({onToast:a}){const{confirm:t,dialog:i}=ie(),[r,d]=c.useState(null),[h,l]=c.useState(""),[s,m]=c.useState(null),[j,k]=c.useState(!1),b=c.useId(),p=c.useCallback(()=>{y.listDeviceTokens().then(d).catch(()=>d([]))},[]);c.useEffect(()=>{p()},[p]);const f=h.trim().length<1?"Isi nama perangkat dulu.":null;async function g(){if(!(f||j)){k(!0);try{const w=await y.createDeviceToken({name:h.trim()});m({name:w.name,token:w.token}),l(""),p(),a==null||a("Token perangkat dibuat — salin sekarang","ok","key-round")}catch(w){a==null||a(K("membuat token perangkat",w),"err","x-circle")}finally{k(!1)}}}async function S(w){try{if(!await t({title:`Cabut token perangkat "${w.name}"?`,message:"Perangkat itu tak bisa sync lagi sampai token baru dibuat.",impact:["Perangkat berhenti menyinkronkan data ke server pusat.","Tindakan ini tak bisa dibatalkan."],confirmLabel:"Cabut token",icon:"key-round",run:()=>y.revokeDeviceToken(w.id)}))return;p(),a==null||a("Token dicabut","warn","trash-2")}catch(D){a==null||a(K("mencabut token",D),"err","x-circle")}}const P=(r??[]).filter(w=>!w.revokedAt);return e.jsxs(M,{eyebrow:"perangkat",title:"Token perangkat","data-setting-card":!0,"data-search":"token perangkat sinkron server pusat cabut",children:[e.jsxs(G,{children:["Token per perangkat untuk menyinkronkan hanoman di perangkat itu ke server pusat. Tempelkan di kolom",e.jsx("code",{className:"hn-set-codeinline",children:"SYNC_DEVICE_TOKEN"})," pada instance perangkat. Teks token hanya tampil sekali."]}),s&&e.jsxs("div",{className:"hn-set-token-box",children:[e.jsxs("div",{className:"hn-set-token-title",children:["Token untuk “",s.name,"” — salin sekarang, tak akan ditampilkan lagi:"]}),e.jsx("code",{className:"hn-set-token-code",children:s.token}),e.jsxs("div",{className:"hn-set-inline",children:[e.jsx(x,{size:"sm",leftIcon:"copy",onClick:()=>{var w;(w=navigator.clipboard)==null||w.writeText(s.token),a==null||a("Disalin","ok","copy")},children:"Salin"}),e.jsx(x,{size:"sm",variant:"ghost",onClick:()=>m(null),children:"Tutup"})]})]}),r===null?e.jsx(W,{kind:"loading",compact:!0,title:"Memuat token…"}):P.length===0?e.jsx("div",{className:"hn-set-empty",children:"Belum ada token perangkat."}):P.map((w,D)=>e.jsx(E,{title:w.name,last:D===P.length-1,desc:"dibuat "+new Date(w.createdAt).toLocaleDateString("id-ID")+(w.lastSeenAt?" · terlihat "+new Date(w.lastSeenAt).toLocaleString("id-ID"):" · belum dipakai"),children:e.jsx(x,{size:"sm",variant:"ghost",leftIcon:"trash-2",onClick:()=>S(w),children:"Cabut"})},w.id)),e.jsxs("form",{className:"hn-set-form-block",onSubmit:w=>{w.preventDefault(),g()},children:[e.jsxs("div",{className:"hn-grid-mobile hn-set-form2",children:[e.jsx(Z,{label:"Nama perangkat",children:e.jsx(q,{value:h,placeholder:"laptop-dena",onChange:w=>l(w.target.value),className:"hn-set-full"})}),e.jsx(x,{size:"sm",type:"submit",leftIcon:"plus",disabled:!!f||j,"aria-describedby":f?b:void 0,className:"hn-set-btn-align",children:"Buat token"})]}),e.jsx(he,{id:b,text:f})]}),i]})}function Hn({me:a,onLoggedOut:t,onToast:i}){const[r,d]=c.useState(""),[h,l]=c.useState(""),[s,m]=c.useState(!1),j=c.useId(),k=r?h.length<8?"Password baru minimal 8 karakter.":null:"Isi password lama dulu.",b=!k&&!s;async function p(){if(b){m(!0);try{await y.changePassword({currentPassword:r,newPassword:h}),d(""),l(""),i==null||i("Password diganti · perangkat lain ter-logout","ok","key-round")}catch(g){i==null||i(g instanceof Se&&g.status===400?"Password lama salah":K("mengganti password",g),"err","x-circle")}finally{m(!1)}}}async function f(){try{await y.logout()}finally{t()}}return e.jsxs(M,{eyebrow:"akun",title:"Akun","data-setting-card":!0,"data-search":"akun password logout email",actions:e.jsx(x,{size:"sm",variant:"ghost",leftIcon:"log-out",onClick:f,children:"Logout"}),children:[e.jsx(E,{title:"Masuk sebagai",desc:a.email,children:e.jsx("span",{className:"hn-set-mono hn-set-muted",children:a.id})}),e.jsxs("div",{className:"hn-set-block",children:[e.jsx("div",{className:"hn-set-subhead",children:"Ganti password"}),e.jsxs("form",{onSubmit:g=>{g.preventDefault(),p()},children:[e.jsxs("div",{className:"hn-grid-mobile hn-set-form3",children:[e.jsx(Z,{label:"Password lama",children:e.jsx(q,{type:"password",autoComplete:"current-password",placeholder:"••••••••",value:r,onChange:g=>d(g.target.value),className:"hn-set-full"})}),e.jsx(Z,{label:e.jsxs(e.Fragment,{children:["Password baru ",e.jsx("span",{className:"hn-set-hintlabel",children:"· min 8"})]}),children:e.jsx(q,{type:"password",autoComplete:"new-password",placeholder:"minimal 8 karakter",value:h,"aria-invalid":h.length>0&&h.length<8?!0:void 0,invalid:h.length>0&&h.length<8,onChange:g=>l(g.target.value),className:"hn-set-full"})}),e.jsx(x,{size:"sm",type:"submit",leftIcon:"key-round",disabled:!b,"aria-describedby":k?j:void 0,className:"hn-set-btn-align",children:"Ganti"})]}),e.jsx(he,{id:j,text:k})]})]})]})}function Rn({me:a,onToast:t}){const{confirm:i,dialog:r}=ie(),[d,h]=c.useState(null),[l,s]=c.useState(""),[m,j]=c.useState(""),[k,b]=c.useState(!1),p=c.useId(),f=c.useCallback(()=>{y.listUsers().then(h).catch(()=>h([]))},[]);c.useEffect(()=>{f()},[f]);const g=l.length>0&&!/\S+@\S+\.\S+/.test(l),S=l?g?"Format email belum benar.":m.length<8?"Password minimal 8 karakter.":null:"Isi email pengguna dulu.",P=!S&&!k;async function w(){if(P){b(!0);try{await y.inviteUser({email:l,password:m}),s(""),j(""),f(),t==null||t("Pengguna "+l+" diundang","ok","user-plus")}catch(O){t==null||t(O instanceof Se&&O.status===409?"Email sudah dipakai":K("mengundang pengguna",O),"err","x-circle")}finally{b(!1)}}}async function D(O){try{if(!await i({title:`Hapus pengguna "${O.email}"?`,message:"Pengguna ini kehilangan akses ke dashboard seketika.",impact:["Semua sesi login miliknya ikut dicabut.","Tindakan ini tak bisa dibatalkan."],confirmLabel:"Hapus pengguna",run:()=>y.deleteUser(O.id)}))return;f(),t==null||t("Pengguna "+O.email+" dihapus","warn","trash-2")}catch(N){t==null||t(N instanceof Se&&N.status===400?"Tak bisa hapus pengguna terakhir":K("menghapus pengguna",N),"err","x-circle")}}return e.jsxs(M,{eyebrow:"users",title:"Pengguna","data-setting-card":!0,"data-search":"pengguna undang hapus email password",children:[e.jsx(G,{children:"Undang pengguna lain dengan menetapkan password-nya langsung — tanpa email undangan."}),d===null?e.jsx(W,{kind:"loading",compact:!0,title:"Memuat pengguna…"}):d.map((O,N)=>e.jsx(E,{title:O.email,last:N===d.length-1,desc:"dibuat "+new Date(O.createdAt).toLocaleDateString("id-ID")+(O.id===a.id?" · kamu":""),children:e.jsx(x,{size:"sm",variant:"ghost",leftIcon:"trash-2",disabled:d.length<=1,onClick:()=>D(O),children:"Hapus"})},O.id)),e.jsxs("div",{className:"hn-set-block",children:[e.jsx("div",{className:"hn-set-subhead",children:"Undang pengguna"}),e.jsxs("form",{onSubmit:O=>{O.preventDefault(),w()},children:[e.jsxs("div",{className:"hn-grid-mobile hn-set-form3",children:[e.jsx(Z,{label:"Email",children:e.jsx(q,{type:"email",value:l,placeholder:"user@nafanesia.id","aria-invalid":g?!0:void 0,invalid:g,onChange:O=>s(O.target.value),className:"hn-set-full"})}),e.jsx(Z,{label:e.jsxs(e.Fragment,{children:["Password ",e.jsx("span",{className:"hn-set-hintlabel",children:"· min 8"})]}),children:e.jsx(q,{type:"password",autoComplete:"new-password",placeholder:"minimal 8 karakter",value:m,onChange:O=>j(O.target.value),className:"hn-set-full"})}),e.jsx(x,{size:"sm",type:"submit",leftIcon:"user-plus",disabled:!P,"aria-describedby":S?p:void 0,className:"hn-set-btn-align",children:"Undang"})]}),e.jsx(he,{id:p,text:S})]})]}),r]})}function zn(){const[a,t]=c.useState(null),[i,r]=c.useState(!1),d=c.useCallback(()=>{y.setupStatus().then(t).catch(()=>t(null))},[]);return c.useEffect(d,[d]),a?i?e.jsx(Wa,{status:a,onDone:()=>{r(!1),d()},onCancel:()=>r(!1)}):e.jsx("div",{"data-testid":"setup-card","data-setting-card":!0,children:e.jsxs(M,{eyebrow:"setup",title:"Setup awal","data-search":"setup awal peruntukan hardening ulang",children:[e.jsxs("div",{className:"hn-set-setupstatus",children:["Peruntukan: ",a.deployment==="local"?"Perangkat ini":"Diakses orang lain",e.jsx("br",{}),a.hardening?"Hardening menyala":"Hardening mati",a.hardeningLocked?" — dipasang lewat env, tak bisa diubah dari sini":""]}),e.jsx(x,{onClick:()=>r(!0),children:"Jalankan ulang setup"})]})}):e.jsx(W,{kind:"loading",title:"Memuat setup…"})}const Ke={sync:"Sync",claude:"Claude",vps:"VPS",runtime:"Runtime",bootstrap:"Bootstrap (read-only)"},Fn={db:"tersimpan di database",env:"dari .env",default:"bawaan"},$n={live:"langsung berlaku","new-session":"untuk sesi baru",restart:"perlu restart"},Qe={live:"Berlaku langsung.","new-session":"Berlaku untuk sesi baru.",restart:"Berlaku setelah server di-restart."};function Kn({onToast:a}){const{confirm:t,dialog:i}=ie(),[r,d]=c.useState(null),[h,l]=c.useState({}),s=c.useCallback(()=>{y.getConfig().then(d).catch(()=>d(null))},[]);if(c.useEffect(()=>{s()},[s]),!r)return e.jsx(W,{kind:"loading",title:"Memuat konfigurasi…"});const m=p=>l(f=>{const g={...f};return delete g[p],g}),j=async p=>{const f=h[p.key]??"";try{await y.putConfig(p.key,f),m(p.key),s(),a==null||a(`${p.label} disimpan`,"ok","check-circle-2")}catch(g){a==null||a(K(`menyimpan ${p.label}`,g),"err","x-circle")}},k=async p=>{const f=p.kind==="secret";try{if(!await t({title:f?`Hapus nilai "${p.label}"?`:`Reset "${p.label}" ke bawaan?`,message:f?"Nilai rahasia ini hilang dari database.":"Nilai yang Anda atur untuk setelan ini dihapus.",impact:["Yang berlaku kembali nilai dari .env atau nilai bawaan aplikasi.",Qe[p.apply],"Tindakan ini tak bisa dibatalkan — nilai lama tidak disimpan sebagai cadangan."],confirmLabel:f?"Hapus nilai":"Reset",tone:"danger",icon:f?"trash-2":"rotate-ccw",run:()=>y.deleteConfig(p.key)}))return;m(p.key),s(),a==null||a(`${p.label} direset`,"warn","rotate-ccw")}catch(g){a==null||a(K(`mereset ${p.label}`,g),"err","x-circle")}},b=[...new Set(r.entries.map(p=>p.group))];return e.jsxs(e.Fragment,{children:[e.jsx(G,{children:"Setiap baris disimpan lewat tombolnya sendiri. Nilai bawaan tak berubah sampai Anda mereset setelan itu."}),b.map(p=>e.jsxs(M,{eyebrow:p,title:Ke[p]??p,"data-setting-card":!0,"data-search":`${p} ${Ke[p]??p} konfigurasi env`,children:[p==="sync"&&e.jsx("div",{className:"hn-set-muted hn-set-syncline",children:r.sync.running?r.sync.connected?"● Tersambung ke server pusat":"◐ Sinkron aktif, menyambung…":"○ Tidak sinkron (instance ini adalah server pusat)"}),r.entries.filter(f=>f.group===p).map(f=>e.jsx(E,{title:f.label,desc:f.help,children:e.jsx(Gn,{entry:f,draft:h[f.key],onDraft:g=>l(S=>({...S,[f.key]:g})),onSave:()=>j(f),onReset:()=>k(f)})},f.key))]},p)),i]})}function Gn({entry:a,draft:t,onDraft:i,onSave:r,onReset:d}){var s,m;const h=e.jsxs("span",{title:Qe[a.apply],className:"hn-set-badgeword",children:[Fn[a.source]," · ",$n[a.apply]]});if(!a.editable)return e.jsxs("div",{className:"hn-set-inline",children:[h,e.jsx("code",{className:"hn-set-codeinline",children:a.masked??a.value??"—"})]});const l=j=>{j.preventDefault(),r()};if(a.kind==="secret")return e.jsxs("div",{className:"hn-set-inline hn-set-wrap",children:[h,a.hasValue&&t===void 0?e.jsxs(e.Fragment,{children:[e.jsx("code",{className:"hn-set-codeinline",children:a.masked}),e.jsx(x,{size:"sm",variant:"ghost",leftIcon:"pencil",onClick:()=>i(""),children:"Ganti"}),e.jsx(x,{size:"sm",variant:"danger-outline",leftIcon:"trash-2",onClick:d,children:"Hapus"})]}):e.jsxs("form",{onSubmit:l,className:"hn-set-inline hn-set-wrap",children:[e.jsx(q,{"aria-label":a.label,type:"password",placeholder:a.hasValue?"biarkan kosong = pertahankan":((s=ke(a.key))==null?void 0:s.example)??"tempel token…",value:t??"",onChange:j=>i(j.target.value),className:"hn-set-w-input"}),e.jsx(x,{size:"sm",type:"submit",leftIcon:"save",children:"Simpan"})]})]});if(a.kind==="bool"){const j=(t??a.value)==="1";return e.jsxs("div",{className:"hn-set-inline",children:[h,e.jsx(J,{checked:j,"aria-label":a.label,onChange:k=>{i(k?"1":"0")}}),t!==void 0&&e.jsx(x,{size:"sm",leftIcon:"save",onClick:r,children:"Simpan"})]})}return e.jsxs("form",{onSubmit:l,className:"hn-set-inline hn-set-wrap",children:[h,e.jsx(q,{"aria-label":a.label,type:a.kind==="int"?"number":"text",placeholder:(m=ke(a.key))==null?void 0:m.example,value:t??a.value??"",onChange:j=>i(j.target.value),className:"hn-set-w-input"}),e.jsx(x,{size:"sm",type:"submit",leftIcon:"save",children:"Simpan"}),a.source==="db"&&e.jsx(x,{size:"sm",variant:"danger-outline",leftIcon:"rotate-ccw",onClick:d,children:"Reset"})]})}function Un({onToast:a}){const{confirm:t,dialog:i}=ie(),[r,d]=c.useState(""),[h,l]=c.useState(null),s=c.useId(),m=c.useCallback(()=>{y.listSessionResults(r||void 0).then(l).catch(()=>l([]))},[r]);c.useEffect(()=>{m()},[m]);const j=r?null:"Isi nama project dulu untuk menghapus log satu project.";async function k(){if(!j)try{if(!await t({title:`Hapus log aktivitas project "${r}"?`,message:"Seluruh entri hasil sesi project ini dihapus dari perangkat ini.",impact:["Log aktivitas hanya bisa ditambah, jadi entri yang dihapus tak bisa dipulihkan."],confirmLabel:"Hapus log",run:async()=>{const b=await y.purgeSessionResults(r);a==null||a(`${b.purged} entri dihapus`,"warn","trash-2")}}))return;m()}catch(b){a==null||a(K("menghapus log aktivitas",b),"err","x-circle")}}return e.jsxs(M,{eyebrow:"aktivitas",title:"Log aktivitas","data-setting-card":!0,"data-search":"log aktivitas hasil sesi project hapus",children:[e.jsx(G,{children:"Ringkasan hasil sesi dari semua perangkat (perpindahan tahap, commit, PR). Hanya bisa ditambah. Saring per project."}),e.jsxs("div",{className:"hn-grid-mobile hn-set-form2 hn-set-spaced",children:[e.jsx(Z,{label:"Project (opsional)",children:e.jsx(q,{value:r,placeholder:"mis. hanoman — kosong = semua project",onChange:b=>d(b.target.value),className:"hn-set-full"})}),e.jsx(x,{size:"sm",variant:"danger-outline",leftIcon:"trash-2",disabled:!!j,"aria-describedby":j?s:void 0,onClick:k,className:"hn-set-btn-align",children:"Hapus log"})]}),e.jsx(he,{id:s,text:j}),h===null?e.jsx(W,{kind:"loading",compact:!0,title:"Memuat aktivitas…"}):h.length===0?e.jsx("div",{className:"hn-set-empty",children:"Belum ada aktivitas."}):h.map((b,p)=>e.jsx(E,{last:p===h.length-1,title:`${b.specId??b.projectId}${b.oldStage&&b.newStage?` · ${b.oldStage} → ${b.newStage}`:""}`,desc:[b.status,b.commitSha?b.commitSha.slice(0,8):null,b.branch,b.author,new Date(b.createdAt).toLocaleString("id-ID")].filter(Boolean).join(" · "),children:b.prUrl&&e.jsx("a",{href:b.prUrl,target:"_blank",rel:"noreferrer",children:e.jsx(x,{size:"sm",variant:"ghost",leftIcon:"external-link",children:"PR"})})},b.id)),i]})}function Tn({runtime:a,onToast:t}){return e.jsxs(M,{eyebrow:"agen",title:"Custom agent — global","data-setting-card":!0,"data-search":"custom agent global persona mention katalog",children:[e.jsxs(G,{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(Ta,{projectId:null,runtime:a,onToast:t})]})}function Wn({s:a,store:t,onToast:i}){const{confirm:r,dialog:d}=ie(),h=async()=>{await r({title:"Kembalikan semua setelan ke default?",message:"Setelan di seluruh halaman ini kembali ke nilai default hanoman.",impact:["Sesi, model, notifikasi, mode goal, scope verifikasi, metode, konflik, lead, Telegram, dan obrolan portal kembali ke default.","Orkestrasi (model dan effort per fase) dan penanda suntingan agen bawaan ikut kembali ke default.","Kendali jarak jauh, pengiriman log, dan retensi log ikut kembali ke default (mati).","Nilai yang sudah Anda atur tidak disimpan sebagai cadangan."],confirmLabel:"Kembalikan ke default",tone:"danger",icon:"rotate-ccw"})&&t.persist(JSON.parse(JSON.stringify(en)),"Pengaturan dikembalikan ke default","warn","rotate-ccw")};return e.jsxs(M,{eyebrow:"bahaya",title:"Zona bahaya","data-setting-card":!0,"data-search":"zona bahaya reset ke default kembalikan semua setelan",children:[e.jsx(G,{children:"Tindakan di sini menyentuh seluruh pengaturan. Setiap tindakan meminta konfirmasi dan menyebut dampaknya."}),e.jsx(E,{title:"Reset ke default",last:!0,desc:"Mengembalikan semua setelan Pengaturan ke nilai bawaan hanoman. Nilai yang sudah diatur tidak disimpan sebagai cadangan.",children:e.jsx(x,{size:"sm",variant:"danger",leftIcon:"rotate-ccw",disabled:!a,onClick:()=>void h(),children:"Reset ke default"})}),d]})}const Jn=new Set(["sesi","model","telegram","zona-bahaya"]),qn=Oe.flatMap(a=>a.sections.map(t=>({value:t.key,label:We(t.key)})));function it({onToast:a,me:t,onLoggedOut:i}){const r=cn(a),d=Te(),h=d==="mobile",[l,s]=Ja("settings","tab",tn,qa),m=Le(l),j=an(m);c.useEffect(()=>{const C=nn(window.location.hash);C&&s(C)},[]);const[k,b]=c.useState(null);c.useEffect(()=>{y.getCodexVersion().then(b).catch(C=>a==null?void 0:a(K("membaca versi Codex CLI",C),"err","alert-triangle"))},[]);const[p,f]=c.useState(j.key==="lanjutan");c.useEffect(()=>{j.key==="lanjutan"&&f(!0)},[j.key]);const g=C=>s(C),[S,P]=c.useState(""),w=c.useRef(null),D=hn(w,S,m),O=c.useMemo(()=>on(S),[S]),N=r.s,I=!Jn.has(m)||N!==null;let z;return I?z=Vn(m,N,{store:r,onToast:a,codexVer:k,onLoggedOut:i,me:t,onNavigate:g}):z=r.failed?e.jsx(W,{kind:"error",title:"Gagal memuat pengaturan",hint:"Pengaturan tidak ditampilkan agar tidak menimpa nilai di server.",action:r.load}):e.jsx(W,{kind:"loading",title:"Memuat pengaturan…"}),e.jsxs("div",{className:"hn-settings","data-layout":d,children:[e.jsx(mn,{value:S,onChange:P,hits:O,onPick:C=>g(C)}),e.jsxs("div",{className:"hn-settings-layout","data-layout":d,children:[h?e.jsx(T,{"aria-label":"Bagian pengaturan",value:m,className:"hn-set-full",options:qn,onChange:C=>g(Le(C.target.value))}):e.jsx("nav",{"aria-label":"Navigasi pengaturan",className:"hn-set-nav",children:Oe.map(C=>{const H=C.key===j.key;if(C.collapsible)return e.jsxs("div",{className:"hn-set-navgroup",children:[e.jsxs("button",{type:"button",className:"hn-set-nav-item","aria-expanded":p,"aria-controls":p?"hn-set-lanjutan":void 0,onClick:()=>f(L=>!L),children:[e.jsx(le,{name:C.icon,size:16,color:"var(--text-subtle)"}),e.jsx("span",{className:"hn-set-nav-label",children:C.label}),e.jsx("span",{className:"hn-set-chev","aria-hidden":"true",children:p?"▾":"▸"})]}),p&&e.jsx("div",{id:"hn-set-lanjutan",className:"hn-set-sub",children:C.sections.map(L=>e.jsx(ye,{sec:L,active:m,onPick:g},L.key))})]},C.key);if(C.sections.length===1){const L=C.sections[0];return e.jsx(ye,{sec:L,active:m,onPick:g},C.key)}return e.jsxs("div",{className:"hn-set-navgroup",children:[e.jsxs("button",{type:"button",className:`hn-set-nav-item${H?" hn-set-nav-item--parent":""}`,onClick:()=>{H||g(C.sections[0].key)},children:[e.jsx(le,{name:C.icon,size:16,color:"var(--text-subtle)"}),e.jsx("span",{className:"hn-set-nav-label",children:C.label})]}),H&&e.jsx("div",{className:"hn-set-sub",children:C.sections.map(L=>e.jsx(ye,{sec:L,active:m,onPick:g},L.key))})]},C.key)})}),e.jsxs("div",{className:"hn-set-main",children:[S.trim()!==""&&I&&D===0&&e.jsxs("div",{className:"hn-set-nomatch",role:"status",children:["Tak ada pengaturan di bagian ini yang cocok dengan “",S.trim(),"”.",O.length>0&&" Lihat bagian yang cocok di atas."]}),e.jsx("div",{className:"hn-set-body",ref:w,"data-section":m,children:z})]})]})]})}function ye({sec:a,active:t,onPick:i}){const r=a.key===t;return e.jsxs("button",{type:"button",className:`hn-set-nav-item${r?" hn-set-nav-item--on":""}`,"aria-current":r?"page":void 0,onClick:()=>i(a.key),children:[e.jsx(le,{name:a.icon,size:14,color:r?"var(--brass-700)":"var(--text-subtle)"}),e.jsx("span",{className:"hn-set-nav-label",children:a.label})]})}function Vn(a,t,i){const{store:r,onToast:d,codexVer:h,onLoggedOut:l,me:s}=i;switch(a){case"sesi":return t?e.jsx(pn,{s:t,store:r,onToast:d,codexVer:h}):null;case"model":return t?e.jsx(bn,{s:t,store:r,onToast:d,codexVer:h,onNavigate:i.onNavigate}):null;case"telegram":return t?e.jsx(En,{s:t,store:r,onToast:d,codexVer:h}):null;case"zona-bahaya":return e.jsx(Wn,{s:t,store:r,onToast:d});case"agent":return e.jsx(Ln,{onToast:d});case"webhook":return e.jsx(An,{onToast:d});case"akses-klien":return e.jsx(Ya,{onToast:d});case"perangkat":return e.jsx(Bn,{onToast:d});case"kendali-jarak-jauh":return e.jsx(Va,{onToast:d});case"akun":return e.jsx(Hn,{me:s,onLoggedOut:l,onToast:d});case"users":return e.jsx(Rn,{me:s,onToast:d});case"setup":return e.jsx(zn,{});case"konfigurasi":return e.jsx(Kn,{onToast:d});case"aktivitas":return e.jsx(Un,{onToast:d});case"custom-agent":return e.jsx(Tn,{runtime:t==null?void 0:t.agent,onToast:d})}}export{Ln as AgentAccessPanel,Bn as DeviceTokensPanel,it as SettingsScreen};
62
+ claude mcp add hanoman --env HANOMAN_HOST=${t} --env HANOMAN_AGENT_TOKEN=hnm_agt_… -- hanoman mcp${r?" "+r:""}`:a==="cursor"?h.replace('"mcpServers"','"servers"'):h}function Dn(){const[a,t]=c.useState("claude-code"),[i,r]=c.useState("default"),[d,h]=c.useState(!1),l=typeof window>"u"?"http://localhost:8787":window.location.origin,s=In(a,l,i),m=Fe.find(k=>k.id===a),j=()=>{var k;(k=navigator.clipboard)==null||k.writeText(s).then(()=>{h(!0),setTimeout(()=>h(!1),1500)},()=>{})};return e.jsxs(e.Fragment,{children:[e.jsxs(M,{eyebrow:"mcp",title:"MCP server",children:[e.jsxs("div",{className:"hn-mcp-muted",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 ",dn,"."]})]}),e.jsxs("div",{className:"hn-mcp-muted",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",{className:"hn-mcp-muted",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",className:"hn-mcp-clients",children:[Fe.map(k=>e.jsx(x,{size:"sm",variant:k.id===a?"primary":"ghost",onClick:()=>t(k.id),children:k.label},k.id)),e.jsx(x,{size:"sm",variant:i==="read-only"?"primary":"ghost",leftIcon:"eye",onClick:()=>r(k=>k==="read-only"?"default":"read-only"),children:"Mode baca-saja"}),e.jsx(x,{size:"sm",variant:i==="danger"?"primary":"ghost",leftIcon:"alert-triangle",onClick:()=>r(k=>k==="danger"?"default":"danger"),children:"Tampilkan tool berbahaya"})]}),e.jsx("div",{className:"hn-mcp-muted hn-mcp-hint",children:m.hint}),a==="antigravity"&&e.jsxs("div",{"data-testid":"antigravity-mcp-guide",className:"hn-mcp-muted",children:["Gabungkan hanya entri ",e.jsx("code",{children:"mcpServers.hanoman"})," dari contoh ini; pertahankan server lain dan field yang tidak dikenal di root. Bila JSON rusak atau entri hanoman berbeda sudah ada, lakukan rekonsiliasi manual sebelum menyimpan. Gunakan izin berkas",e.jsx("code",{children:"0600"})," setelah mengganti placeholder dengan token yang kamu pilih di kartu akses agent; pilihan tingkat di sini tidak menambah capability token. Konfigurasi workspace ",e.jsx("code",{children:".agents/mcp_config.json"})," tetap utuh. Buka sesi Antigravity baru, periksa ",e.jsx("code",{children:"/mcp"}),", lalu panggil ",e.jsx("code",{children:"hanoman_about"})," untuk memverifikasi koneksi."]}),e.jsx("pre",{"data-testid":"mcp-snippet",className:"hn-mcp-pre",children:s}),e.jsx("div",{className:"hn-mcp-copy",children:e.jsx(x,{size:"sm",leftIcon:"copy",onClick:j,children:d?"Tersalin":"Salin"})})]}),e.jsxs(M,{eyebrow:"tool",title:"Tool yang tersedia",children:[e.jsxs("div",{className:"hn-mcp-muted",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",children:e.jsxs("table",{"data-testid":"mcp-tools",className:"hn-mcp-table",children:[e.jsx("thead",{children:e.jsxs("tr",{children:[e.jsx("th",{className:"hn-mcp-th",children:"Tool"}),e.jsx("th",{className:"hn-mcp-th",children:"Mode"}),e.jsx("th",{className:"hn-mcp-th",children:"Capability"})]})}),e.jsx("tbody",{children:rn.map(k=>e.jsxs("tr",{children:[e.jsxs("td",{className:"hn-mcp-td",children:[e.jsx("code",{children:k.name}),e.jsx("div",{className:"hn-mcp-sub",children:k.title})]}),e.jsx("td",{className:"hn-mcp-td",children:k.mode==="read"?"baca":"tulis"}),e.jsx("td",{className:"hn-mcp-td",children:k.capability?e.jsx("code",{children:k.capability}):e.jsx("span",{className:"hn-mcp-dash",children:"—"})})]},k.name))})]})})]})]})}const _n=[{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"},{had:"qa:write",needs:"qa:delete",sentence:"dulu bisa menghapus laporan, test case, temuan & lampiran QA"}];function $e(a){return _n.filter(t=>a.includes(t.had)&&!a.includes(t.needs)).map(t=>t.sentence)}function me({ids:a,picked:t,onToggle:i,ariaLabel:r,label:d}){const h=c.useRef(null),l=a.filter(m=>t.includes(m)).length,s=a.length>0&&l===a.length;return c.useEffect(()=>{h.current&&(h.current.indeterminate=l>0&&!s)},[l,s]),a.length===0?null:e.jsxs("label",{className:"hn-set-checkall",children:[d&&e.jsx("span",{children:d}),e.jsx("input",{ref:h,type:"checkbox","aria-label":r,checked:s,onChange:()=>i(a,!s)})]})}function Ln({onToast:a}={}){const{confirm:t,dialog:i}=ie(),r=Te()==="mobile",[d,h]=c.useState([]),[l,s]=c.useState(null),[m,j]=c.useState(null),[k,b]=c.useState(""),[p,f]=c.useState([]),[g,S]=c.useState(null),[P,w]=c.useState(!1),D=c.useId(),O=k.trim().length<1?"Isi nama token dulu.":null,N=c.useCallback(()=>{y.listAgentTokens().then(u=>s(u.items)).catch(()=>s([]))},[]);c.useEffect(()=>{y.getSettings().then(j).catch(u=>a==null?void 0:a(K("memuat pengaturan akses agen",u),"err","alert-triangle")),y.getAgentCapabilities().then(u=>h(u.capabilities)).catch(u=>a==null?void 0:a(K("memuat daftar capability agen",u),"err","alert-triangle")),N()},[N]);async function I(u){if(!m)return!1;const _={...m,agentAccessEnabled:u};j(_);try{const R=await y.getSettings().catch(()=>{});return await y.putSettings(Ye(m,_,R)),a==null||a("Akses AI agent "+(u?"aktif":"nonaktif"),u?"ok":"warn","bot"),!0}catch{return a==null||a("Gagal menyimpan","err","x-circle"),j(m),!1}}const z=$({value:!!(m!=null&&m.agentAccessEnabled),delayMs:0,save:I}),C=u=>f(_=>_.includes(u)?_.filter(R=>R!==u):[..._,u]),H=(u,_)=>f(R=>_?Array.from(new Set([...R,...u])):R.filter(n=>!u.includes(n)));async function L(){if(!(O||P)){w(!0);try{const u=await y.createAgentToken({name:k.trim(),capabilities:p});S({name:u.name,token:u.token}),b(""),f([]),N(),a==null||a("Agent token dibuat — salin sekarang","ok","key-round")}catch{a==null||a("Gagal membuat token","err","x-circle")}finally{w(!1)}}}async function Q(u){try{if(!await t({title:`Cabut agent token "${u.name}"?`,message:"Agen yang memakainya langsung kehilangan akses.",impact:[`Token "${u.name}" tak bisa dipakai lagi untuk masuk lewat API.`,"Tindakan ini tak bisa dibatalkan."],confirmLabel:"Cabut token",icon:"key-round",run:()=>y.revokeAgentToken(u.id)}))return;N(),a==null||a("Token dicabut","warn","trash-2")}catch{a==null||a("Gagal mencabut token","err","x-circle")}}async function q(u,_){try{await y.patchAgentToken(u.id,{enabled:_}),N()}catch{a==null||a("Gagal mengubah token","err","x-circle")}}const Y=Array.from(new Set(d.map(u=>u.domain))),U=u=>d.filter(_=>_.access===u).map(_=>_.id),o=d.map(u=>u.id),A=(l??[]).filter(u=>!u.revokedAt),B=(u,_)=>e.jsxs("div",{className:"hn-set-capcell",children:[u&&r&&e.jsx("span",{className:"hn-set-capword",children:_}),u&&e.jsx("input",{type:"checkbox","aria-label":u.label,checked:p.includes(u.id),onChange:()=>C(u.id)})]});return e.jsxs(e.Fragment,{children:[e.jsx(Pn,{onToast:a}),e.jsxs(M,{eyebrow:"ai agent",title:"Akses AI Agent","data-setting-card":!0,"data-search":"akses ai agent master switch capability",children:[e.jsxs(G,{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(E,{title:"Aktifkan akses AI agent",last:!0,save:z,desc:"Master switch. Nonaktif → semua agent token 401, apa pun capability-nya.",children:e.jsx(J,{checked:z.value,onChange:u=>z.set(u),"aria-label":"Aktifkan akses AI agent"})})]}),e.jsxs(M,{eyebrow:"token",title:"Agent tokens","data-setting-card":!0,"data-search":"agent token capability buat cabut",children:[g&&e.jsxs("div",{className:"hn-set-token-box",children:[e.jsxs("div",{className:"hn-set-token-title",children:["Token “",g.name,"” — salin sekarang, tak akan ditampilkan lagi:"]}),e.jsx("code",{className:"hn-set-token-code",children:g.token}),e.jsxs("div",{className:"hn-set-inline",children:[e.jsx(x,{size:"sm",leftIcon:"copy",onClick:()=>{var u;(u=navigator.clipboard)==null||u.writeText(g.token),a==null||a("Disalin","ok","copy")},children:"Salin"}),e.jsx(x,{size:"sm",variant:"ghost",onClick:()=>S(null),children:"Tutup"})]})]}),l===null?e.jsx(W,{kind:"loading",compact:!0,title:"Memuat token…"}):A.length===0?e.jsx("div",{className:"hn-set-empty",children:"Belum ada agent token."}):A.map((u,_)=>e.jsx(E,{title:u.name,last:_===A.length-1,desc:`${u.tokenPrefix}… · ${u.capabilities.length} capability · `+(u.lastUsedAt?"terpakai "+new Date(u.lastUsedAt).toLocaleString("id-ID"):"belum dipakai")+($e(u.capabilities).length?` — token ini ${$e(u.capabilities).join(", ")}; sekarang tidak, sampai capability berbahayanya dicentang. Cabut lalu buat ulang bila memang perlu.`:""),children:e.jsxs("div",{className:"hn-set-inline",children:[e.jsx(J,{size:"sm",checked:u.enabled,onChange:R=>void q(u,R),"aria-label":`Aktif: ${u.name}`}),e.jsx(x,{size:"sm",variant:"ghost",leftIcon:"trash-2",onClick:()=>Q(u),children:"Cabut"})]})},u.id)),e.jsxs("form",{className:"hn-set-form-block",onSubmit:u=>{u.preventDefault(),L()},children:[e.jsx(Z,{label:"Nama token",children:e.jsx(V,{value:k,placeholder:"mis. agent-ci",onChange:u=>b(u.target.value),className:"hn-set-full"})}),e.jsx("div",{className:"hn-set-subhead",children:"Capability"}),e.jsxs("div",{className:"hn-grid-mobile hn-set-capgrid",children:[e.jsx("div",{className:"hn-set-capcol-right",children:e.jsx(me,{label:"Pilih semua capability",ariaLabel:"pilih semua capability",ids:o,picked:p,onToggle:H})}),e.jsxs("div",{className:"hn-set-capcol",children:["baca",e.jsx(me,{ariaLabel:"pilih semua baca",ids:U("read"),picked:p,onToggle:H})]}),e.jsxs("div",{className:"hn-set-capcol",children:["tulis",e.jsx(me,{ariaLabel:"pilih semua tulis",ids:U("write"),picked:p,onToggle:H})]}),e.jsxs("div",{className:"hn-set-capcol hn-set-capcol--danger",children:["berbahaya",e.jsx(me,{ariaLabel:"pilih semua berbahaya",ids:U("danger"),picked:p,onToggle:H})]}),Y.map(u=>{const _=d.find(F=>F.domain===u&&F.access==="read"),R=d.find(F=>F.domain===u&&F.access==="write"),n=d.find(F=>F.domain===u&&F.access==="danger"),v=Ua.find(F=>F.domain===u);return e.jsxs(c.Fragment,{children:[e.jsxs("div",{className:"hn-set-capdomain",children:[e.jsxs("div",{className:"hn-set-capdomain-name",children:[(v==null?void 0:v.label)??u,R!=null&&R.risk||n!=null&&n.risk?" ⚠":""]}),(v==null?void 0:v.desc)&&e.jsx("div",{className:"hn-set-capdomain-desc",children:v.desc}),n&&e.jsxs("div",{className:"hn-set-capdomain-danger",children:[n.label.replace(/^[^—]+—\s*/,"berbahaya: ")," — ",n.desc]})]}),B(_,"baca"),B(R,"tulis"),B(n,"berbahaya")]},u)})]}),e.jsx("div",{className:"hn-set-form-actions",children:e.jsx(x,{size:"sm",type:"submit",leftIcon:"plus",disabled:!!O||P,"aria-describedby":O?D:void 0,children:"Buat token"})}),e.jsx(he,{id:D,text:O})]}),i]}),e.jsx(Dn,{})]})}function Bn({onToast:a}){const{confirm:t,dialog:i}=ie(),[r,d]=c.useState(null),[h,l]=c.useState(""),[s,m]=c.useState(null),[j,k]=c.useState(!1),b=c.useId(),p=c.useCallback(()=>{y.listDeviceTokens().then(d).catch(()=>d([]))},[]);c.useEffect(()=>{p()},[p]);const f=h.trim().length<1?"Isi nama perangkat dulu.":null;async function g(){if(!(f||j)){k(!0);try{const w=await y.createDeviceToken({name:h.trim()});m({name:w.name,token:w.token}),l(""),p(),a==null||a("Token perangkat dibuat — salin sekarang","ok","key-round")}catch(w){a==null||a(K("membuat token perangkat",w),"err","x-circle")}finally{k(!1)}}}async function S(w){try{if(!await t({title:`Cabut token perangkat "${w.name}"?`,message:"Perangkat itu tak bisa sync lagi sampai token baru dibuat.",impact:["Perangkat berhenti menyinkronkan data ke server pusat.","Tindakan ini tak bisa dibatalkan."],confirmLabel:"Cabut token",icon:"key-round",run:()=>y.revokeDeviceToken(w.id)}))return;p(),a==null||a("Token dicabut","warn","trash-2")}catch(D){a==null||a(K("mencabut token",D),"err","x-circle")}}const P=(r??[]).filter(w=>!w.revokedAt);return e.jsxs(M,{eyebrow:"perangkat",title:"Token perangkat","data-setting-card":!0,"data-search":"token perangkat sinkron server pusat cabut",children:[e.jsxs(G,{children:["Token per perangkat untuk menyinkronkan hanoman di perangkat itu ke server pusat. Tempelkan di kolom",e.jsx("code",{className:"hn-set-codeinline",children:"SYNC_DEVICE_TOKEN"})," pada instance perangkat. Teks token hanya tampil sekali."]}),s&&e.jsxs("div",{className:"hn-set-token-box",children:[e.jsxs("div",{className:"hn-set-token-title",children:["Token untuk “",s.name,"” — salin sekarang, tak akan ditampilkan lagi:"]}),e.jsx("code",{className:"hn-set-token-code",children:s.token}),e.jsxs("div",{className:"hn-set-inline",children:[e.jsx(x,{size:"sm",leftIcon:"copy",onClick:()=>{var w;(w=navigator.clipboard)==null||w.writeText(s.token),a==null||a("Disalin","ok","copy")},children:"Salin"}),e.jsx(x,{size:"sm",variant:"ghost",onClick:()=>m(null),children:"Tutup"})]})]}),r===null?e.jsx(W,{kind:"loading",compact:!0,title:"Memuat token…"}):P.length===0?e.jsx("div",{className:"hn-set-empty",children:"Belum ada token perangkat."}):P.map((w,D)=>e.jsx(E,{title:w.name,last:D===P.length-1,desc:"dibuat "+new Date(w.createdAt).toLocaleDateString("id-ID")+(w.lastSeenAt?" · terlihat "+new Date(w.lastSeenAt).toLocaleString("id-ID"):" · belum dipakai"),children:e.jsx(x,{size:"sm",variant:"ghost",leftIcon:"trash-2",onClick:()=>S(w),children:"Cabut"})},w.id)),e.jsxs("form",{className:"hn-set-form-block",onSubmit:w=>{w.preventDefault(),g()},children:[e.jsxs("div",{className:"hn-grid-mobile hn-set-form2",children:[e.jsx(Z,{label:"Nama perangkat",children:e.jsx(V,{value:h,placeholder:"laptop-dena",onChange:w=>l(w.target.value),className:"hn-set-full"})}),e.jsx(x,{size:"sm",type:"submit",leftIcon:"plus",disabled:!!f||j,"aria-describedby":f?b:void 0,className:"hn-set-btn-align",children:"Buat token"})]}),e.jsx(he,{id:b,text:f})]}),i]})}function Hn({me:a,onLoggedOut:t,onToast:i}){const[r,d]=c.useState(""),[h,l]=c.useState(""),[s,m]=c.useState(!1),j=c.useId(),k=r?h.length<8?"Password baru minimal 8 karakter.":null:"Isi password lama dulu.",b=!k&&!s;async function p(){if(b){m(!0);try{await y.changePassword({currentPassword:r,newPassword:h}),d(""),l(""),i==null||i("Password diganti · perangkat lain ter-logout","ok","key-round")}catch(g){i==null||i(g instanceof Se&&g.status===400?"Password lama salah":K("mengganti password",g),"err","x-circle")}finally{m(!1)}}}async function f(){try{await y.logout()}finally{t()}}return e.jsxs(M,{eyebrow:"akun",title:"Akun","data-setting-card":!0,"data-search":"akun password logout email",actions:e.jsx(x,{size:"sm",variant:"ghost",leftIcon:"log-out",onClick:f,children:"Logout"}),children:[e.jsx(E,{title:"Masuk sebagai",desc:a.email,children:e.jsx("span",{className:"hn-set-mono hn-set-muted",children:a.id})}),e.jsxs("div",{className:"hn-set-block",children:[e.jsx("div",{className:"hn-set-subhead",children:"Ganti password"}),e.jsxs("form",{onSubmit:g=>{g.preventDefault(),p()},children:[e.jsxs("div",{className:"hn-grid-mobile hn-set-form3",children:[e.jsx(Z,{label:"Password lama",children:e.jsx(V,{type:"password",autoComplete:"current-password",placeholder:"••••••••",value:r,onChange:g=>d(g.target.value),className:"hn-set-full"})}),e.jsx(Z,{label:e.jsxs(e.Fragment,{children:["Password baru ",e.jsx("span",{className:"hn-set-hintlabel",children:"· min 8"})]}),children:e.jsx(V,{type:"password",autoComplete:"new-password",placeholder:"minimal 8 karakter",value:h,"aria-invalid":h.length>0&&h.length<8?!0:void 0,invalid:h.length>0&&h.length<8,onChange:g=>l(g.target.value),className:"hn-set-full"})}),e.jsx(x,{size:"sm",type:"submit",leftIcon:"key-round",disabled:!b,"aria-describedby":k?j:void 0,className:"hn-set-btn-align",children:"Ganti"})]}),e.jsx(he,{id:j,text:k})]})]})]})}function Rn({me:a,onToast:t}){const{confirm:i,dialog:r}=ie(),[d,h]=c.useState(null),[l,s]=c.useState(""),[m,j]=c.useState(""),[k,b]=c.useState(!1),p=c.useId(),f=c.useCallback(()=>{y.listUsers().then(h).catch(()=>h([]))},[]);c.useEffect(()=>{f()},[f]);const g=l.length>0&&!/\S+@\S+\.\S+/.test(l),S=l?g?"Format email belum benar.":m.length<8?"Password minimal 8 karakter.":null:"Isi email pengguna dulu.",P=!S&&!k;async function w(){if(P){b(!0);try{await y.inviteUser({email:l,password:m}),s(""),j(""),f(),t==null||t("Pengguna "+l+" diundang","ok","user-plus")}catch(O){t==null||t(O instanceof Se&&O.status===409?"Email sudah dipakai":K("mengundang pengguna",O),"err","x-circle")}finally{b(!1)}}}async function D(O){try{if(!await i({title:`Hapus pengguna "${O.email}"?`,message:"Pengguna ini kehilangan akses ke dashboard seketika.",impact:["Semua sesi login miliknya ikut dicabut.","Tindakan ini tak bisa dibatalkan."],confirmLabel:"Hapus pengguna",run:()=>y.deleteUser(O.id)}))return;f(),t==null||t("Pengguna "+O.email+" dihapus","warn","trash-2")}catch(N){t==null||t(N instanceof Se&&N.status===400?"Tak bisa hapus pengguna terakhir":K("menghapus pengguna",N),"err","x-circle")}}return e.jsxs(M,{eyebrow:"users",title:"Pengguna","data-setting-card":!0,"data-search":"pengguna undang hapus email password",children:[e.jsx(G,{children:"Undang pengguna lain dengan menetapkan password-nya langsung — tanpa email undangan."}),d===null?e.jsx(W,{kind:"loading",compact:!0,title:"Memuat pengguna…"}):d.map((O,N)=>e.jsx(E,{title:O.email,last:N===d.length-1,desc:"dibuat "+new Date(O.createdAt).toLocaleDateString("id-ID")+(O.id===a.id?" · kamu":""),children:e.jsx(x,{size:"sm",variant:"ghost",leftIcon:"trash-2",disabled:d.length<=1,onClick:()=>D(O),children:"Hapus"})},O.id)),e.jsxs("div",{className:"hn-set-block",children:[e.jsx("div",{className:"hn-set-subhead",children:"Undang pengguna"}),e.jsxs("form",{onSubmit:O=>{O.preventDefault(),w()},children:[e.jsxs("div",{className:"hn-grid-mobile hn-set-form3",children:[e.jsx(Z,{label:"Email",children:e.jsx(V,{type:"email",value:l,placeholder:"user@nafanesia.id","aria-invalid":g?!0:void 0,invalid:g,onChange:O=>s(O.target.value),className:"hn-set-full"})}),e.jsx(Z,{label:e.jsxs(e.Fragment,{children:["Password ",e.jsx("span",{className:"hn-set-hintlabel",children:"· min 8"})]}),children:e.jsx(V,{type:"password",autoComplete:"new-password",placeholder:"minimal 8 karakter",value:m,onChange:O=>j(O.target.value),className:"hn-set-full"})}),e.jsx(x,{size:"sm",type:"submit",leftIcon:"user-plus",disabled:!P,"aria-describedby":S?p:void 0,className:"hn-set-btn-align",children:"Undang"})]}),e.jsx(he,{id:p,text:S})]})]}),r]})}function zn(){const[a,t]=c.useState(null),[i,r]=c.useState(!1),d=c.useCallback(()=>{y.setupStatus().then(t).catch(()=>t(null))},[]);return c.useEffect(d,[d]),a?i?e.jsx(Wa,{status:a,onDone:()=>{r(!1),d()},onCancel:()=>r(!1)}):e.jsx("div",{"data-testid":"setup-card","data-setting-card":!0,children:e.jsxs(M,{eyebrow:"setup",title:"Setup awal","data-search":"setup awal peruntukan hardening ulang",children:[e.jsxs("div",{className:"hn-set-setupstatus",children:["Peruntukan: ",a.deployment==="local"?"Perangkat ini":"Diakses orang lain",e.jsx("br",{}),a.hardening?"Hardening menyala":"Hardening mati",a.hardeningLocked?" — dipasang lewat env, tak bisa diubah dari sini":""]}),e.jsx(x,{onClick:()=>r(!0),children:"Jalankan ulang setup"})]})}):e.jsx(W,{kind:"loading",title:"Memuat setup…"})}const Ke={sync:"Sync",claude:"Claude",vps:"VPS",runtime:"Runtime",bootstrap:"Bootstrap (read-only)"},Fn={db:"tersimpan di database",env:"dari .env",default:"bawaan"},$n={live:"langsung berlaku","new-session":"untuk sesi baru",restart:"perlu restart"},Qe={live:"Berlaku langsung.","new-session":"Berlaku untuk sesi baru.",restart:"Berlaku setelah server di-restart."};function Kn({onToast:a}){const{confirm:t,dialog:i}=ie(),[r,d]=c.useState(null),[h,l]=c.useState({}),s=c.useCallback(()=>{y.getConfig().then(d).catch(()=>d(null))},[]);if(c.useEffect(()=>{s()},[s]),!r)return e.jsx(W,{kind:"loading",title:"Memuat konfigurasi…"});const m=p=>l(f=>{const g={...f};return delete g[p],g}),j=async p=>{const f=h[p.key]??"";try{await y.putConfig(p.key,f),m(p.key),s(),a==null||a(`${p.label} disimpan`,"ok","check-circle-2")}catch(g){a==null||a(K(`menyimpan ${p.label}`,g),"err","x-circle")}},k=async p=>{const f=p.kind==="secret";try{if(!await t({title:f?`Hapus nilai "${p.label}"?`:`Reset "${p.label}" ke bawaan?`,message:f?"Nilai rahasia ini hilang dari database.":"Nilai yang Anda atur untuk setelan ini dihapus.",impact:["Yang berlaku kembali nilai dari .env atau nilai bawaan aplikasi.",Qe[p.apply],"Tindakan ini tak bisa dibatalkan — nilai lama tidak disimpan sebagai cadangan."],confirmLabel:f?"Hapus nilai":"Reset",tone:"danger",icon:f?"trash-2":"rotate-ccw",run:()=>y.deleteConfig(p.key)}))return;m(p.key),s(),a==null||a(`${p.label} direset`,"warn","rotate-ccw")}catch(g){a==null||a(K(`mereset ${p.label}`,g),"err","x-circle")}},b=[...new Set(r.entries.map(p=>p.group))];return e.jsxs(e.Fragment,{children:[e.jsx(G,{children:"Setiap baris disimpan lewat tombolnya sendiri. Nilai bawaan tak berubah sampai Anda mereset setelan itu."}),b.map(p=>e.jsxs(M,{eyebrow:p,title:Ke[p]??p,"data-setting-card":!0,"data-search":`${p} ${Ke[p]??p} konfigurasi env`,children:[p==="sync"&&e.jsx("div",{className:"hn-set-muted hn-set-syncline",children:r.sync.running?r.sync.connected?"● Tersambung ke server pusat":"◐ Sinkron aktif, menyambung…":"○ Tidak sinkron (instance ini adalah server pusat)"}),r.entries.filter(f=>f.group===p).map(f=>e.jsx(E,{title:f.label,desc:f.help,children:e.jsx(Gn,{entry:f,draft:h[f.key],onDraft:g=>l(S=>({...S,[f.key]:g})),onSave:()=>j(f),onReset:()=>k(f)})},f.key))]},p)),i]})}function Gn({entry:a,draft:t,onDraft:i,onSave:r,onReset:d}){var s,m;const h=e.jsxs("span",{title:Qe[a.apply],className:"hn-set-badgeword",children:[Fn[a.source]," · ",$n[a.apply]]});if(!a.editable)return e.jsxs("div",{className:"hn-set-inline",children:[h,e.jsx("code",{className:"hn-set-codeinline",children:a.masked??a.value??"—"})]});const l=j=>{j.preventDefault(),r()};if(a.kind==="secret")return e.jsxs("div",{className:"hn-set-inline hn-set-wrap",children:[h,a.hasValue&&t===void 0?e.jsxs(e.Fragment,{children:[e.jsx("code",{className:"hn-set-codeinline",children:a.masked}),e.jsx(x,{size:"sm",variant:"ghost",leftIcon:"pencil",onClick:()=>i(""),children:"Ganti"}),e.jsx(x,{size:"sm",variant:"danger-outline",leftIcon:"trash-2",onClick:d,children:"Hapus"})]}):e.jsxs("form",{onSubmit:l,className:"hn-set-inline hn-set-wrap",children:[e.jsx(V,{"aria-label":a.label,type:"password",placeholder:a.hasValue?"biarkan kosong = pertahankan":((s=ke(a.key))==null?void 0:s.example)??"tempel token…",value:t??"",onChange:j=>i(j.target.value),className:"hn-set-w-input"}),e.jsx(x,{size:"sm",type:"submit",leftIcon:"save",children:"Simpan"})]})]});if(a.kind==="bool"){const j=(t??a.value)==="1";return e.jsxs("div",{className:"hn-set-inline",children:[h,e.jsx(J,{checked:j,"aria-label":a.label,onChange:k=>{i(k?"1":"0")}}),t!==void 0&&e.jsx(x,{size:"sm",leftIcon:"save",onClick:r,children:"Simpan"})]})}return e.jsxs("form",{onSubmit:l,className:"hn-set-inline hn-set-wrap",children:[h,e.jsx(V,{"aria-label":a.label,type:a.kind==="int"?"number":"text",placeholder:(m=ke(a.key))==null?void 0:m.example,value:t??a.value??"",onChange:j=>i(j.target.value),className:"hn-set-w-input"}),e.jsx(x,{size:"sm",type:"submit",leftIcon:"save",children:"Simpan"}),a.source==="db"&&e.jsx(x,{size:"sm",variant:"danger-outline",leftIcon:"rotate-ccw",onClick:d,children:"Reset"})]})}function Un({onToast:a}){const{confirm:t,dialog:i}=ie(),[r,d]=c.useState(""),[h,l]=c.useState(null),s=c.useId(),m=c.useCallback(()=>{y.listSessionResults(r||void 0).then(l).catch(()=>l([]))},[r]);c.useEffect(()=>{m()},[m]);const j=r?null:"Isi nama project dulu untuk menghapus log satu project.";async function k(){if(!j)try{if(!await t({title:`Hapus log aktivitas project "${r}"?`,message:"Seluruh entri hasil sesi project ini dihapus dari perangkat ini.",impact:["Log aktivitas hanya bisa ditambah, jadi entri yang dihapus tak bisa dipulihkan."],confirmLabel:"Hapus log",run:async()=>{const b=await y.purgeSessionResults(r);a==null||a(`${b.purged} entri dihapus`,"warn","trash-2")}}))return;m()}catch(b){a==null||a(K("menghapus log aktivitas",b),"err","x-circle")}}return e.jsxs(M,{eyebrow:"aktivitas",title:"Log aktivitas","data-setting-card":!0,"data-search":"log aktivitas hasil sesi project hapus",children:[e.jsx(G,{children:"Ringkasan hasil sesi dari semua perangkat (perpindahan tahap, commit, PR). Hanya bisa ditambah. Saring per project."}),e.jsxs("div",{className:"hn-grid-mobile hn-set-form2 hn-set-spaced",children:[e.jsx(Z,{label:"Project (opsional)",children:e.jsx(V,{value:r,placeholder:"mis. hanoman — kosong = semua project",onChange:b=>d(b.target.value),className:"hn-set-full"})}),e.jsx(x,{size:"sm",variant:"danger-outline",leftIcon:"trash-2",disabled:!!j,"aria-describedby":j?s:void 0,onClick:k,className:"hn-set-btn-align",children:"Hapus log"})]}),e.jsx(he,{id:s,text:j}),h===null?e.jsx(W,{kind:"loading",compact:!0,title:"Memuat aktivitas…"}):h.length===0?e.jsx("div",{className:"hn-set-empty",children:"Belum ada aktivitas."}):h.map((b,p)=>e.jsx(E,{last:p===h.length-1,title:`${b.specId??b.projectId}${b.oldStage&&b.newStage?` · ${b.oldStage} → ${b.newStage}`:""}`,desc:[b.status,b.commitSha?b.commitSha.slice(0,8):null,b.branch,b.author,new Date(b.createdAt).toLocaleString("id-ID")].filter(Boolean).join(" · "),children:b.prUrl&&e.jsx("a",{href:b.prUrl,target:"_blank",rel:"noreferrer",children:e.jsx(x,{size:"sm",variant:"ghost",leftIcon:"external-link",children:"PR"})})},b.id)),i]})}function Tn({runtime:a,onToast:t}){return e.jsxs(M,{eyebrow:"agen",title:"Custom agent — global","data-setting-card":!0,"data-search":"custom agent global persona mention katalog",children:[e.jsxs(G,{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(Ta,{projectId:null,runtime:a,onToast:t})]})}function Wn({s:a,store:t,onToast:i}){const{confirm:r,dialog:d}=ie(),h=async()=>{await r({title:"Kembalikan semua setelan ke default?",message:"Setelan di seluruh halaman ini kembali ke nilai default hanoman.",impact:["Sesi, model, notifikasi, mode goal, scope verifikasi, metode, konflik, lead, Telegram, dan obrolan portal kembali ke default.","Orkestrasi (model dan effort per fase) dan penanda suntingan agen bawaan ikut kembali ke default.","Kendali jarak jauh, pengiriman log, dan retensi log ikut kembali ke default (mati).","Nilai yang sudah Anda atur tidak disimpan sebagai cadangan."],confirmLabel:"Kembalikan ke default",tone:"danger",icon:"rotate-ccw"})&&t.persist(JSON.parse(JSON.stringify(en)),"Pengaturan dikembalikan ke default","warn","rotate-ccw")};return e.jsxs(M,{eyebrow:"bahaya",title:"Zona bahaya","data-setting-card":!0,"data-search":"zona bahaya reset ke default kembalikan semua setelan",children:[e.jsx(G,{children:"Tindakan di sini menyentuh seluruh pengaturan. Setiap tindakan meminta konfirmasi dan menyebut dampaknya."}),e.jsx(E,{title:"Reset ke default",last:!0,desc:"Mengembalikan semua setelan Pengaturan ke nilai bawaan hanoman. Nilai yang sudah diatur tidak disimpan sebagai cadangan.",children:e.jsx(x,{size:"sm",variant:"danger",leftIcon:"rotate-ccw",disabled:!a,onClick:()=>void h(),children:"Reset ke default"})}),d]})}const Jn=new Set(["sesi","model","telegram","zona-bahaya"]),Vn=Oe.flatMap(a=>a.sections.map(t=>({value:t.key,label:We(t.key)})));function it({onToast:a,me:t,onLoggedOut:i}){const r=cn(a),d=Te(),h=d==="mobile",[l,s]=Ja("settings","tab",tn,Va),m=Le(l),j=an(m);c.useEffect(()=>{const C=nn(window.location.hash);C&&s(C)},[]);const[k,b]=c.useState(null);c.useEffect(()=>{y.getCodexVersion().then(b).catch(C=>a==null?void 0:a(K("membaca versi Codex CLI",C),"err","alert-triangle"))},[]);const[p,f]=c.useState(j.key==="lanjutan");c.useEffect(()=>{j.key==="lanjutan"&&f(!0)},[j.key]);const g=C=>s(C),[S,P]=c.useState(""),w=c.useRef(null),D=hn(w,S,m),O=c.useMemo(()=>on(S),[S]),N=r.s,I=!Jn.has(m)||N!==null;let z;return I?z=qn(m,N,{store:r,onToast:a,codexVer:k,onLoggedOut:i,me:t,onNavigate:g}):z=r.failed?e.jsx(W,{kind:"error",title:"Gagal memuat pengaturan",hint:"Pengaturan tidak ditampilkan agar tidak menimpa nilai di server.",action:r.load}):e.jsx(W,{kind:"loading",title:"Memuat pengaturan…"}),e.jsxs("div",{className:"hn-settings","data-layout":d,children:[e.jsx(mn,{value:S,onChange:P,hits:O,onPick:C=>g(C)}),e.jsxs("div",{className:"hn-settings-layout","data-layout":d,children:[h?e.jsx(T,{"aria-label":"Bagian pengaturan",value:m,className:"hn-set-full",options:Vn,onChange:C=>g(Le(C.target.value))}):e.jsx("nav",{"aria-label":"Navigasi pengaturan",className:"hn-set-nav",children:Oe.map(C=>{const H=C.key===j.key;if(C.collapsible)return e.jsxs("div",{className:"hn-set-navgroup",children:[e.jsxs("button",{type:"button",className:"hn-set-nav-item","aria-expanded":p,"aria-controls":p?"hn-set-lanjutan":void 0,onClick:()=>f(L=>!L),children:[e.jsx(le,{name:C.icon,size:16,color:"var(--text-subtle)"}),e.jsx("span",{className:"hn-set-nav-label",children:C.label}),e.jsx("span",{className:"hn-set-chev","aria-hidden":"true",children:p?"▾":"▸"})]}),p&&e.jsx("div",{id:"hn-set-lanjutan",className:"hn-set-sub",children:C.sections.map(L=>e.jsx(ye,{sec:L,active:m,onPick:g},L.key))})]},C.key);if(C.sections.length===1){const L=C.sections[0];return e.jsx(ye,{sec:L,active:m,onPick:g},C.key)}return e.jsxs("div",{className:"hn-set-navgroup",children:[e.jsxs("button",{type:"button",className:`hn-set-nav-item${H?" hn-set-nav-item--parent":""}`,onClick:()=>{H||g(C.sections[0].key)},children:[e.jsx(le,{name:C.icon,size:16,color:"var(--text-subtle)"}),e.jsx("span",{className:"hn-set-nav-label",children:C.label})]}),H&&e.jsx("div",{className:"hn-set-sub",children:C.sections.map(L=>e.jsx(ye,{sec:L,active:m,onPick:g},L.key))})]},C.key)})}),e.jsxs("div",{className:"hn-set-main",children:[S.trim()!==""&&I&&D===0&&e.jsxs("div",{className:"hn-set-nomatch",role:"status",children:["Tak ada pengaturan di bagian ini yang cocok dengan “",S.trim(),"”.",O.length>0&&" Lihat bagian yang cocok di atas."]}),e.jsx("div",{className:"hn-set-body",ref:w,"data-section":m,children:z})]})]})]})}function ye({sec:a,active:t,onPick:i}){const r=a.key===t;return e.jsxs("button",{type:"button",className:`hn-set-nav-item${r?" hn-set-nav-item--on":""}`,"aria-current":r?"page":void 0,onClick:()=>i(a.key),children:[e.jsx(le,{name:a.icon,size:14,color:r?"var(--brass-700)":"var(--text-subtle)"}),e.jsx("span",{className:"hn-set-nav-label",children:a.label})]})}function qn(a,t,i){const{store:r,onToast:d,codexVer:h,onLoggedOut:l,me:s}=i;switch(a){case"sesi":return t?e.jsx(pn,{s:t,store:r,onToast:d,codexVer:h}):null;case"model":return t?e.jsx(bn,{s:t,store:r,onToast:d,codexVer:h,onNavigate:i.onNavigate}):null;case"telegram":return t?e.jsx(En,{s:t,store:r,onToast:d,codexVer:h}):null;case"zona-bahaya":return e.jsx(Wn,{s:t,store:r,onToast:d});case"agent":return e.jsx(Ln,{onToast:d});case"webhook":return e.jsx(An,{onToast:d});case"akses-klien":return e.jsx(Ya,{onToast:d});case"perangkat":return e.jsx(Bn,{onToast:d});case"kendali-jarak-jauh":return e.jsx(qa,{onToast:d});case"akun":return e.jsx(Hn,{me:s,onLoggedOut:l,onToast:d});case"users":return e.jsx(Rn,{me:s,onToast:d});case"setup":return e.jsx(zn,{});case"konfigurasi":return e.jsx(Kn,{onToast:d});case"aktivitas":return e.jsx(Un,{onToast:d});case"custom-agent":return e.jsx(Tn,{runtime:t==null?void 0:t.agent,onToast:d})}}export{Ln as AgentAccessPanel,Bn as DeviceTokensPanel,it as SettingsScreen};