hanoman 0.9.19 → 0.9.21

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 (64) hide show
  1. package/dist/build-info.json +3 -3
  2. package/dist/cli.js +2966 -775
  3. package/dist/server.js +10106 -8155
  4. package/docs/agent-integration.md +208 -97
  5. package/package.json +1 -1
  6. package/prisma/migrations/20261010070000_remove_memory_changelog/migration.sql +40 -0
  7. package/prisma/migrations/20261012100000_spec_reviewed_at/migration.sql +13 -0
  8. package/prisma/schema.prisma +7 -80
  9. package/web/assets/DalangHanomanScreen-Us_5qG25.js +13 -0
  10. package/web/assets/DalangHanomanScreen-gk7fa9dl.css +1 -0
  11. package/web/assets/{DocPreviewModal-CHrsP0pL.js → DocPreviewModal-CJP6d0Hx.js} +1 -1
  12. package/web/assets/DocsWorkspace-CSak7xFX.css +1 -0
  13. package/web/assets/DocsWorkspace-zGpPd7u3.js +3 -0
  14. package/web/assets/IdeScreen-CppUucs0.css +10 -0
  15. package/web/assets/IdeScreen-DTvfuR2n.js +2 -0
  16. package/web/assets/LeadScreen-CN53yR_e.css +1 -0
  17. package/web/assets/LeadScreen-DWbunLkQ.js +1 -0
  18. package/web/assets/ReviewScreen-Ch9UiKlx.js +1 -0
  19. package/web/assets/SchedulerScreen-zIUh-Vbe.js +1 -0
  20. package/web/assets/SettingsScreen-CLMuCJ6R.js +62 -0
  21. package/web/assets/SettingsScreen-DQZ3K43g.css +1 -0
  22. package/web/assets/TeamScreen-CV1R5Ekk.js +1 -0
  23. package/web/assets/TeamScreen-CtkpFpCF.css +1 -0
  24. package/web/assets/TerminalScreen-DrgZKlw2.css +1 -0
  25. package/web/assets/TerminalScreen-QVjhjrxx.js +2 -0
  26. package/web/assets/TriageScreen-B6_KrS6l.css +1 -0
  27. package/web/assets/TriageScreen-Dpj4YrBQ.js +1 -0
  28. package/web/assets/VpsScreen-BzXpc3le.css +1 -0
  29. package/web/assets/VpsScreen-DX_HEquM.js +2 -0
  30. package/web/assets/index-CL0_zz87.css +32 -0
  31. package/web/assets/index-DmnwLncG.js +1012 -0
  32. package/web/assets/live-B193WtZm.js +1 -0
  33. package/web/assets/sched-lead-shared-BjMO8Q_k.js +1 -0
  34. package/web/assets/scheduler-CQt-VP9-.css +1 -0
  35. package/web/assets/settings-rebase-BI7-Bk98.js +1 -0
  36. package/web/index.html +10 -3
  37. package/web/assets/ChangelogScreen-DL5v_FZf.js +0 -1
  38. package/web/assets/DalangHanomanScreen-aLBtUGE4.js +0 -1
  39. package/web/assets/DocsWorkspace-D262CVxH.js +0 -3
  40. package/web/assets/IdeScreen-BfC0goYb.css +0 -10
  41. package/web/assets/IdeScreen-D2GI3KYh.js +0 -2
  42. package/web/assets/LeadScreen-eb_DK1j6.js +0 -1
  43. package/web/assets/ReviewScreen-DarOFH_a.js +0 -1
  44. package/web/assets/SchedulerScreen-DKoMf2LT.js +0 -1
  45. package/web/assets/SettingsScreen-BAUhwd5K.js +0 -62
  46. package/web/assets/TeamScreen-C2fSsQ8C.js +0 -1
  47. package/web/assets/TerminalScreen-GsfOMtu5.js +0 -2
  48. package/web/assets/TriageScreen-DdKMAroz.js +0 -1
  49. package/web/assets/VpsScreen-CDAjvyT1.js +0 -2
  50. package/web/assets/hnm-blencong-v01-CBBfchr0.webp +0 -0
  51. package/web/assets/hnm-dalang-six-arms-v01-DeBacuq8.webp +0 -0
  52. package/web/assets/hnm-hero-rig-arm-ml-v01-Kts3pjTf.webp +0 -0
  53. package/web/assets/hnm-hero-rig-arm-mr-v01-DQgnE6bD.webp +0 -0
  54. package/web/assets/hnm-hero-rig-arm-ul-v01-CIAWAxFe.webp +0 -0
  55. package/web/assets/hnm-hero-rig-arm-ur-v01-C0R0CZnp.webp +0 -0
  56. package/web/assets/hnm-hero-rig-body-v01-DmbKJJHB.webp +0 -0
  57. package/web/assets/hnm-wayang-panakawan-v01-1LGv-V8N.webp +0 -0
  58. package/web/assets/hnm-wayang-project-v01-BG9EZqzn.webp +0 -0
  59. package/web/assets/hnm-wayang-putri-v01-F6p69kHq.webp +0 -0
  60. package/web/assets/hnm-wayang-satria-alus-v01-CpfoXKTy.webp +0 -0
  61. package/web/assets/hnm-wayang-satria-gagah-v01-bLfIYAsv.webp +0 -0
  62. package/web/assets/index-DI8I0VA5.css +0 -32
  63. package/web/assets/index-gNO91_qw.js +0 -1063
  64. package/web/assets/live-Cz6dWAau.js +0 -1
@@ -91,62 +91,57 @@ host-mu benar dan masalahnya ada pada token atau master switch.
91
91
  ## 3. Capability
92
92
 
93
93
  Capability berformat `"<domain>:<access>"`, `access ∈ {read, write}`, dan **write meng-implikasikan
94
- read** pada domain yang sama. Ada **13 domain**; empat di antaranya punya akses KETIGA `danger`
95
- (ADR-0155) yang **tak diimplikasikan `:write`**, sehingga totalnya **30 capability**. Katalog resmi (dengan label &
94
+ read** pada domain yang sama. Ada **17 domain**; empat di antaranya punya akses KETIGA `danger`
95
+ (ADR-0155) yang **tak diimplikasikan `:write`**, sehingga totalnya **38 capability**. Katalog resmi (dengan label &
96
+ read** pada domain yang sama. Ada **18 domain**; lima di antaranya punya akses KETIGA `danger`
97
+ (ADR-0155, ADR-0193) yang **tak diimplikasikan `:write`**, sehingga totalnya **40 capability**
98
+ (`portal` hanya punya `read` — lihat tabel). Katalog resmi (dengan label &
96
99
  deskripsi) tampil di panel **Settings → Akses AI Agent** saat manusia membuat token; endpoint
97
100
  katalognya (`GET /api/agent-tokens/capabilities`) bersifat **cookie-only** (lihat §5) — kamu tak
98
101
  perlu mengambilnya, cukup rujuk tabel di bawah:
102
+ read** pada domain yang sama. Ada **23 domain** dan **48 capability**; domain yang punya akses KETIGA
103
+ `danger` (`sessions:spawn`, `ide:git`, `backlog:lifecycle`, `vps:exec` — ADR-0155 — dan, sejak
104
+ ADR-0196..0198, `access:admin`, `remote:control`, `relay:exec`) **tak diimplikasikan `:write`**.
105
+ Katalog resmi (dengan label & deskripsi) tampil di panel **Settings → Akses AI Agent** saat manusia
106
+ membuat token; endpoint katalognya (`GET /api/agent-tokens/capabilities`) kini terbaca dengan
107
+ `tokens:read` — selain itu cukup rujuk tabel di bawah:
99
108
 
100
109
  | Domain | Cakupan endpoint | Catatan |
101
110
  |---|---|---|
102
111
  | `projects` | `/api/projects*` | project, branch, binding, Help Center |
103
112
  | `backlog` | `/api/specs*` | spec/backlog, dokumen, review diff, integrate |
104
- | `sessions` | `/api/terminal*` (+ WS terminal) | jalankan sesi agen/shell, kirim input, baca & jawab dialog sesi (SPEC-899) — **high-risk (RCE)** |
113
+ | `sessions` | `/api/terminal*` (+ WS terminal) | jalankan sesi agen/shell, kirim input — **high-risk (RCE)**. Sub-pohon `/dialog` **bukan** domain ini sejak ADR-0190: lihat `decisions`. `steer` ke sesi yang sedang menampilkan dialog juga digerbangi `sessions:decide` |
105
114
  | `docs` | `/api/prds*`, `/api/projects/:id/{docs,prds}*` | dokumen SoT project & PRD |
106
115
  | `ide` | `/api/projects/:id/{tree,file,file-diff,working-status,graph,commit,git,status,stashes,remotes,compare,archive,pr-url}*` | tree/file working tree, operasi git |
107
116
  | `vps` | `/api/vps*` | kelola VPS, audit, harden, konsol — **high-risk (remote exec)** |
108
- | `settings` | `/api/settings*`, `/api/config*`, `/api/scheduler*` | setelan instance & config runtime |
117
+ | `settings` | `/api/settings*`, `/api/config*`, `/api/scheduler*` kecuali `/api/scheduler/crons*` | setelan instance & config runtime |
109
118
  | `support` | `/api/tickets*`, `/api/github-issues*`, `/api/projects/:id/github*` | tiket Help Center & issue GitHub (triase) |
110
119
  | `notifications` | `/api/notifications*` | notifikasi |
111
120
  | `lead` | `/api/lead*` | minta putusan ke hanoman-lead & baca jejaknya — **`lead:write` bisa menggerakkan sesi** (ADR-0091) |
112
121
  | `agents` | `/api/custom-agents*` | katalog custom agent global & per project — **`agents:write` mengubah apa yang dilihat SETIAP sesi baru** (ADR-0094) |
113
122
  | `skills` | `/api/skills*` | skill global hanoman, user, plugin (baca-saja) & per project, termasuk isi berkasnya — **`skills:write` pada skill global hanoman mengubah instruksi SETIAP sesi baru di semua project** |
114
123
  | `telegram` | `/api/telegram*` kecuali sub-path kredensial | context/memory/reply/audit kanal operator Telegram (ADR-0096) |
115
- | `memory` | `/api/memories*` kecuali `…/activate` dan `…/reject` (cookie-only) | memori project bersama. Token **wajib** punya allowlist project (`projectIds`); project ditentukan dari header `x-hanoman-repo` yang diisi CLI MCP, bukan dari parameter (ADR-0178) |
124
+ | `qa` | `/api/projects/:id/qa*`, `/api/qa*` | laporan QA per project: test case, temuan, lampiran |
125
+ | `activity` | `/api/session-results*` | log aktivitas lintas device (stage, commit, PR); **`activity:write` = purge yang menyeberang ke SEMUA mesin lewat sync** (ADR-0188) |
126
+ | `portal-chat` | `/api/portal-chat*` | chat portal klien sisi OPERATOR: sesi, PRD draft, ekspor transkrip — berisi **email & percakapan klien**; `portal-chat:write` menulis `docs/prd/<slug>.md` di checkout (ADR-0189). Beda dari `/api/portal*` yang tetap cookie-only |
127
+ | `telegram` | `/api/telegram*` kecuali sub-path kredensial | context/memory/reply/audit kanal operator Telegram (ADR-0096). Sub-path **kredensial** (`/api/telegram/settings`, `/api/telegram/test`, `/api/telegram/credentials`) menuntut capability `danger` **`telegram:admin`** (ADR-0193) — tak diimplikasikan `telegram:write` maupun `settings:write`, sehingga token gateway Telegram sendiri tetap tak menjangkaunya. **Anti-eskalasi (403):** token gateway efektif tak boleh memegang capability yang tak dimiliki token pemanggil |
128
+ | `webhooks` | `/api/webhooks*` | webhook keluar: endpoint, putar secret, ping, riwayat & ulang pengiriman (ADR-0194). **`webhooks:write` menentukan ke mana data workspace mengalir keluar**; secret penandatanganan tampil **sekali** saat dibuat/diputar. `allowPrivate` (SSRF ke jaringan internal) tetap keputusan manusia — agent token ditolak 403 bila menyalakannya |
129
+ | `clients` | `/api/client-accounts*` | akun login portal klien: daftar, buat, ubah akses project/nonaktifkan, reset password, hapus (ADR-0194). `clients:write` **membuka login baru ke data project** |
130
+ | `portal` | `/api/portal*` (**baca** saja) | lihat portal **atas nama satu akun klien** yang disebut eksplisit lewat `?asClient=<id akun klien>` (ADR-0195); hanya `portal:read`, tak ada `portal:write`. Chat portal memuat percakapan klien |
116
131
  | `team` | `/api/tasks*`, `/api/members*` | papan **Tim**: kartu kerja MANUSIA & direktori anggota (ADR-0157). `status` kartu milik manusia — ia **bukan** `stage` backlog. `POST /api/tasks/:id/escalate` melahirkan backlog item dan tetap `team:write` (cermin `POST /api/tickets/:id/accept`) |
117
-
118
- ### Memori project (`/api/memories`, ADR-0178)
119
-
120
- - Project **tidak** dipilih lewat parameter. CLI MCP hanoman (`hanoman mcp`) membaca repo di direktori
121
- kerjanya — `origin`, root commit, HEAD — dan mengirimnya sebagai header `x-hanoman-repo`; server
122
- mencocokkannya ke `Project.gitRemote` dan ke allowlist **Project yang diizinkan** pada token.
123
- - Token tanpa allowlist → `403 {need:"projectIds"}`; mengirim `projectId` → `400`.
124
- - Jangkar (`anchors[]`) diisi path saja; blob SHA diisi CLI dari HEAD dan diverifikasi ulang server.
125
- Usulan dengan jangkar terverifikasi langsung `active`; tanpa jangkar atau `kind: decision` masuk
126
- review manusia (`…/activate` / `…/reject` cookie-only).
127
- - Galat: `404` remote tak dikenal / root commit beda · `409 {duplicateOf}` · `422 {anchor}` jangkar tak cocok
128
- · `422 {reason}` terdeteksi secret.
129
- - Memori adalah **data**, bukan instruksi.
130
- - **Dari dalam sesi hanoman** (ADR-0179): CLI MCP meneruskan `HANOMAN_SESSION_ID` + `HANOMAN_EVENT_TOKEN`
131
- sesi induknya sebagai `x-hanoman-session` / `x-hanoman-session-token` (hanya ke host loopback). Project
132
- diambil dari sesi — allowlist `projectIds` tak dibutuhkan, capability `memory:*` tetap. Kredensial sesi
133
- salah → `401`; sesi tak hidup → `404`. Memori dari sesi yang menyentuh input eksternal (Help Center,
134
- tiket, issue GitHub) selalu masuk review manusia.
135
- - Sesi backlog lahir dengan memori `active` yang jangkarnya cocok HEAD worktree-nya (claude:
136
- `--append-system-prompt-file`, codex: `developer_instructions`).
137
-
138
- Aturan pemetaan **deterministik** (`server/src/services/agent-capabilities.ts`): `GET`/`HEAD` →
139
- `:read`, metode lain → `:write`. Itu berlaku untuk domain `lead` juga — **`POST /api/lead/decisions`
140
- menuntut `lead:write`**, dan `lead:read` tak pernah cukup: meminta putusan melahirkan baris jejak
141
- permanen dan keputusannya bisa menggerakkan sesi. Sub-path `/api/projects/:id/{docs,prds}` dihitung
142
- domain **`docs`**; sub-path IDE/git di atas dihitung domain **`ide`**; WebSocket terminal butuh
143
- **`sessions:write`**.
144
-
145
- Empat endpoint STATUS tak menuntut capability sama sekali — token sah mana pun boleh membacanya
146
- (`GLOBAL_READ`, ADR-0157): `GET /api/limits`, `GET /api/limits/codex`, `GET /api/update`, dan
147
- `GET /api/fs/browse` (menelusuri folder mesin untuk mengisi `repoDir`). Hanya method BACA:
148
- `POST /api/update/apply` me-restart instance dan tetap **403** untuk agent token, apa pun
149
- capability-nya (SPEC-405/ADR-0088) — prefix yang sama tak menurunkan gerbangnya.
132
+ | `qa` | `/api/projects/:id/qa/**`, `/api/qa/**` | laporan QA, test case, temuan, lampiran, ekspor/impor. Method `DELETE` menuntut `qa:delete` (akses `danger`, ADR-0185), bukan `qa:write` |
133
+ | `decisions` | `/api/terminal/sessions/:id/dialog*` (capability `sessions:decide`, akses `danger`) | **gerbang manusia-yang-memutuskan** (ADR-0190, ADR-0192): baca, jawab, dan rebut dialog `AskUserQuestion` sebuah sesi. **Tak diimplikasikan `sessions:write`.** Beri HANYA ke token yang tidak dipakai agen di dalam sesi hanoman — lihat §13 |
134
+ | `scheduler` | `/api/scheduler/crons*` | cronjob per project — **cron = sesi agen berizin penuh yang ditunda** (ADR-0191). Membuat, mengubah prompt/agen/model/effort/jadwal, mengaktifkan, dan menjalankan cron menuntut **`scheduler:write` DAN `sessions:spawn`**; server menjawab 403 `need: sessions:spawn` bila yang kedua tak dicentang |
135
+ | `tokens` | `/api/agent-tokens*` | cetak/ubah/cabut **agent token**. Dulu cookie-only (ADR-0065); ADR-0196 membukanya dengan **pagar tak-eskalasi-diri** (§5b). `tokens:write` **bukan** izin menaikkan hak sendiri |
136
+ | `devices` | `/api/device-tokens*` | **device token** (identitas mesin untuk sync & relay) atas nama akun admin yang menerbitkan agent token ini (ADR-0197). Plaintext sekali-tampil |
137
+ | `access` | `/api/auth/users*`, `POST /api/auth/change-password` | daftar akun (`access:read`); **`access:admin`** (danger) membuat akun admin, menghapus akun, dan menyetel ulang password akun lain — **setara akses penuh instance** |
138
+ | `remote` | `/api/remote-control` | grant kendali jarak jauh mesin ini. `remote:read` membaca; **`remote:control`** (danger) mengubahnya — grant yang menyala membuat hub menjalankan agen di mesin ini (ADR-0165, ADR-0197) |
139
+ | `relay` | `/api/devices/:id/relay/*` | **`relay:exec`** (danger): meneruskan aksi sesi ke mesin LAIN lewat relay hub, atas nama operator — eksekusi lintas mesin (ADR-0165 §4, ADR-0197) |
140
+ | `logs` | `/api/logs*` | log terpusat lintas device: cari, transkrip, retensi (`logs:read`); `logs:write` mengubah retensi & lajur pengiriman log (memendekkan retensi menghapus jejak audit) |
141
+ | `presence` | `/api/presence` | peta sesi hidup di seluruh device operator (`presence:read`, ADR-0147/0197) |
142
+ | `metrics` | `/api/custom-agents/{metrics,invocations}*` | metrik custom agent (`metrics:read`) dan disposisi per invokasi (`metrics:write` — vonis MANUSIA atas mutu agen; ADR-0198) |
143
+ | `sync` | `GET /api/sync/{conflicts,pending}`, `POST /api/sync/now`, `POST /api/sync/conflicts/:entity/:recordId/resolve` | **hanya tiga permukaan UI sync** (ADR-0199): antrean konflik & hapus-tertunda (`sync:read`), pemicu sync manual (`sync:write`), memutuskan konflik (`sync:resolve`, akses `danger` — **membuang data satu sisi tanpa jalan pulang**, tak diimplikasikan `sync:write`). Kanal mesin-ke-mesin (`/api/sync/{pull,bootstrap,push,logs,attachments,qa-attachments,ws,relay}`) tetap bukan milik agen — lihat §5 |
144
+ | `instance` | `POST /api/update/apply`, `POST /api/restart` | satu capability `instance:admin` (akses `danger`, ADR-0200): memasang versi hanoman terbaru dari registry npm dan me-**restart** proses server. Hanya sah bila instance punya supervisor (`canApply` di `GET /api/update`); wajib `{ "confirm": true }` dan balasan 202 dikirim **sebelum** proses keluar |
150
145
 
151
146
  ## 4. Aturan gate & kode status
152
147
 
@@ -171,6 +166,26 @@ token selalu **403**, apa pun capability-nya, dan tak ada capability yang bisa m
171
166
  - `/api/auth/*` — kelola user & password
172
167
  - `/api/agent-tokens*` — agen tak boleh mencetak/menaikkan token sendiri
173
168
  - `/api/device-tokens*`, `/api/sync*` — identitas mesin & sync hub
169
+ - **Tulis** ke `/api/portal*` (`POST …/tickets`, `POST …/chat/sessions`, `POST …/messages`) — itu
170
+ bertindak **sebagai klien** (tiket multipart; chat menghabiskan kuota klien dan tertulis sebagai
171
+ ucapannya): peniruan identitas, bukan capability. Membaca portal sebagai klien tersedia lewat
172
+ capability `portal:read` (ADR-0195)
173
+
174
+ > Dulu cookie-only, kini capability **opt-in** (manusia yang mencentangnya di Settings → Akses AI
175
+ > Agent): `/api/webhooks*` → `webhooks:*` (ADR-0194), `/api/client-accounts*` → `clients:*`
176
+ > (ADR-0194), `/api/telegram/{settings,test,credentials}` → `telegram:admin` (ADR-0193), dan
177
+ > **baca** `/api/portal*` → `portal:read` (ADR-0195). Lihat tabel §3.
178
+ - `/api/auth/{status,setup,login,logout}` — cookie sesi & bootstrap akun pertama (setup token di
179
+ `HANOMAN_HOME`). Agen tak punya sesi cookie, jadi tak ada yang bisa dibungkus bermakna. (Pengelolaan
180
+ akun — `/api/auth/users*` dan `change-password` — **dibuka** lewat `access:*`; lihat §5b.)
181
+ - `/api/sync*` — sync mesin-ke-mesin; kredensialnya Bearer **device token**, bukan agent token
182
+ - `/api/device-tokens*`, `/api/sync*` — identitas mesin & sync hub. **Pengecualian sempit (ADR-0199):**
183
+ tiga permukaan UI sync (`GET /api/sync/conflicts`, `GET /api/sync/pending`, `POST /api/sync/now`) dan
184
+ `POST /api/sync/conflicts/:entity/:recordId/resolve` terbuka lewat domain `sync` (§3). SEMUA sisanya
185
+ — `/api/sync/{pull,bootstrap,push,logs,attachments,qa-attachments,ws,relay/ws}` — adalah kanal
186
+ mesin-ke-mesin ber-**Bearer device-token** yang ditegakkan route-nya sendiri; agent token bukan
187
+ device token, dan membungkusnya berarti menyerahkan kredensial mesin ke agen. `POST /api/ws-tickets`
188
+ (tiket WebSocket sekali-pakai untuk sesi cookie) juga cookie-only
174
189
  - `/api/webhooks*` — memegang secret penandatanganan **dan** menentukan ke mana data workspace
175
190
  mengalir keluar (ADR-0100)
176
191
  - `/api/telegram/settings`, `/api/telegram/test`, `/api/telegram/credentials` — permukaan
@@ -184,6 +199,15 @@ token selalu **403**, apa pun capability-nya, dan tak ada capability yang bisa m
184
199
  bisa memalsukan "sesi X bertanya Y" bisa menggerakkan lead atas nama sesi mana pun — itu peniruan
185
200
  identitas, bukan capability. Agent token di sini menerima **401** (token sesi tak cocok), bukan
186
201
  403: gate cookie mem-bypass path-nya dan route menegakkan tokennya sendiri
202
+ - `/api/terminal/workspace` — tata letak tab/pane **browser** satu akun manusia (revisi optimistik per
203
+ `req.user.id`); agent token tak punya akun, dan menulisnya hanya merusak layout operator
204
+ - Frame siar `presence` di `/api/events/ws` — grup itu **tidak** dikirim ke principal non-cookie,
205
+ walau kanalnya sendiri `GLOBAL_READ`. Agen membaca peta yang sama lewat `GET /api/presence`
206
+ (`presence:read`, §5b). `PUT /api/settings` tetap tak bisa mengubah kunci `remoteControl`/
207
+ `logShipping`/`logRetention`; jalurnya hanya `PUT /api/remote-control` dan `PUT /api/logs/retention`
208
+ - `POST /api/update/apply` dan tulis lain di bawah prefix status (`/api/limits`, `/api/update`,
209
+ `/api/events`, `/api/fs`, `/api/health`, serta — sejak ADR-0187 — `/api/models`, `/api/methods`,
210
+ `/api/codex`) — **baca**-nya terbuka untuk token mana pun, **tulis**-nya cookie-only
187
211
  - `/api/presence` — peta pekerjaan yang sedang berjalan di **seluruh mesin** operator: sesi apa
188
212
  hidup di device mana (SPEC-919/ADR-0147). Tak ada capability yang bisa berarti sesuatu untuk
189
213
  agregat lintas-mesin, jadi ia cookie-only apa pun method-nya. Frame siar `presence` di
@@ -198,13 +222,58 @@ token selalu **403**, apa pun capability-nya, dan tak ada capability yang bisa m
198
222
  mesin mana", dan agen yang bisa membacanya atau memendekkan retensinya bisa menyembunyikan atau
199
223
  memalsukan seluruh jejak sesi. Cookie-only apa pun method-nya — beda dari `POST /api/sync/logs`
200
224
  (pengiriman batch **dari** klien), yang tetap device-token seperti jalur sync lain
201
- - `POST /api/update/apply` dan tulis lain di bawah prefix status (`/api/limits`, `/api/update`,
202
- `/api/events`, `/api/fs`, `/api/health`) — **baca**-nya terbuka untuk token mana pun, **tulis**-nya
203
- cookie-only
225
+ - Tulis lain di bawah prefix status (`/api/limits`, `/api/update`, `/api/events`, `/api/fs`,
226
+ `/api/health`) — **baca**-nya terbuka untuk token mana pun, **tulis**-nya cookie-only. Satu-satunya
227
+ pengecualian (ADR-0200): `POST /api/update/apply` dan `POST /api/restart` lewat capability danger
228
+ `instance:admin` (§3), tidak lewat capability lain
204
229
 
205
230
  Route yang tak dikenal peta juga **default cookie-only** (aman): endpoint baru tak pernah terbuka
206
231
  karena kelalaian. Endpoint `/api/help*` (Help Center publik) punya otorisasi sendiri (kunci tiket)
207
- dan tak memakai agent token.
232
+ dan tak memakai agent token: ia anonim by design (honeypot + rate-limit dirancang untuk manusia
233
+ pelapor), jadi **tak ada tool MCP** untuknya (ADR-0201) — operator membaca tiket lewat
234
+ `hanoman_tickets_list`/`hanoman_ticket_get` dan URL publik lewat `hanoman_help_center_get`.
235
+
236
+ ## 5b. Permukaan admin yang dibuka opt-in (ADR-0196..0198)
237
+
238
+ Sampai ADR-0196..0198, `/api/agent-tokens*`, `/api/device-tokens*`, `/api/auth/users*`,
239
+ `/api/auth/change-password`, `/api/remote-control`, `/api/devices/:id/relay/*`, `/api/logs*`,
240
+ `/api/presence`, dan metrik custom agent cookie-only. Pemilik repo meminta mereka dibungkus, jadi
241
+ keputusan lama itu dibalik **dengan capability baru yang opt-in** (tak ada capability lama yang
242
+ membukanya) dan **pagar yang ditegakkan di server**, bukan hanya di katalog tool:
243
+
244
+ | Hal | Aturan |
245
+ |---|---|
246
+ | Cetak agent token (`POST /api/agent-tokens`) | `capabilities` token baru HARUS himpunan-bagian milik pemanggil (`write ⊇ read`; danger hanya bila dimiliki persis). Selebihnya **403** `{ error, exceeds: [...] }` |
247
+ | Ubah agent token (`PATCH`) | `capabilities` baru ⊆ milik pemanggil, **dan** token target tak boleh lebih berkuasa dari pemanggil (agen lemah tak bisa menonaktifkan/menurunkan token yang manusia beri hak lebih) |
248
+ | Cabut agent token (`DELETE`) | aturan target yang sama |
249
+ | Token sendiri | **tak bisa** diubah atau dicabut oleh dirinya sendiri (403 `{ self: true }`) — minta manusia |
250
+ | Plaintext token | hanya di balasan `POST` (agent maupun device token); tak pernah di daftar atau di log audit; runtime MCP melepas topeng token HANYA untuk hasil sukses dua tool pencetak |
251
+ | Device token | dicetak atas nama akun admin yang menerbitkan agent token pemanggil (rantai `agent:<id>` ditelusuri); tanpa penerbit manusia → **409**. Agen hanya melihat/mencabut device token milik akun itu |
252
+ | Akun | membuat akun admin, menghapus akun, menyetel ulang password akun lain = `access:admin` (danger). Reset password agen: `{ userId, newPassword }` tanpa password lama, mencabut semua sesi akun itu, dan **tidak** menerbitkan cookie. Admin terakhir tetap tak bisa dihapus. `access:admin` setara menyerahkan seluruh instance |
253
+ | Grant jarak jauh (`PUT /api/remote-control`) | `control.capabilities` ⊆ milik pemanggil; mengubah lajur log (data keluar ke hub) menuntut juga `logs:write` |
254
+ | Relay perangkat | `relay:exec` (danger) untuk semua method; aktor ditandai `agent:<id>`; allowlist route ditegakkan mesin tujuan; WebSocket relay tidak tersedia untuk agen |
255
+ | Jejak | setiap aksi admin oleh agent token tercatat di log event (`kind` `admin.*`) tanpa rahasia; perubahan retensi log dicatat **sebelum** nilainya berubah |
256
+
257
+ Tool MCP-nya (semua tulis bermode `danger`, hanya tampil dengan `--danger`):
258
+
259
+ | Tool | Mode | Capability |
260
+ |---|---|---|
261
+ | `hanoman_agent_tokens_list`, `hanoman_agent_token_capabilities` | baca | `tokens:read` |
262
+ | `hanoman_agent_token_create`, `hanoman_agent_token_update`, `hanoman_agent_token_revoke` | danger | `tokens:write` |
263
+ | `hanoman_device_tokens_list` | baca | `devices:read` |
264
+ | `hanoman_device_token_create`, `hanoman_device_token_revoke` | danger | `devices:write` |
265
+ | `hanoman_users_list` | baca | `access:read` |
266
+ | `hanoman_user_create`, `hanoman_user_delete`, `hanoman_user_password_reset` | danger | `access:admin` |
267
+ | `hanoman_remote_control_get` | baca | `remote:read` |
268
+ | `hanoman_remote_control_set` | danger | `remote:control` |
269
+ | `hanoman_device_relay` | danger | `relay:exec` |
270
+ | `hanoman_device_access_request` | danger | `relay:exec` |
271
+ | `hanoman_device_access_requests` | danger | `relay:exec` |
272
+ | `hanoman_logs_search`, `hanoman_log_transcript`, `hanoman_logs_retention_get` | baca | `logs:read` |
273
+ | `hanoman_logs_retention_set` | danger | `logs:write` |
274
+ | `hanoman_presence_get` | baca | `presence:read` |
275
+ | `hanoman_agent_metrics` | baca | `metrics:read` |
276
+ | `hanoman_agent_invocation_disposition` | danger | `metrics:write` |
208
277
 
209
278
  ## 6. Endpoint yang paling sering dipakai
210
279
 
@@ -218,61 +287,25 @@ dan tak memakai agent token.
218
287
  | `POST /api/specs` | `backlog:write` | buat backlog item — bentuk payload di §7. |
219
288
  | `PATCH /api/specs/:id` | `backlog:write` | ubah item; konten hanya selagi belum dimulai. |
220
289
  | `POST /api/specs/:id/done` | `backlog:write` | tandai item **selesai** tanpa menjalankan sesi — untuk pekerjaan yang beres di luar hanoman. Body `{ reason?: string (≤280), confirm?: boolean }`, keduanya opsional; balasannya `Spec` yang sudah `stage:"done"`. Tak menjalankan maupun menghentikan sesi apa pun. |
290
+ | `POST /api/specs/:id/reviewed` | `backlog:write` | tandai atau batalkan **sudah ditinjau** pada item yang sudah **selesai** (tool `hanoman_backlog_mark_reviewed`, ADR-0218). Body `{ reviewed?: boolean }`, default `true`; `false` membatalkan. Idempoten: menandai ulang tak mengubah stempelnya. Balasannya `Spec` dengan `reviewedAt`. 409 bila item belum selesai. Tak mengubah stage, diff, atau berkas. |
221
291
  | `GET /api/specs/:id/attachments` | `backlog:read` | lampiran backlog item — gambar & dokumen yang dilampirkan manusia sebagai konteks kerja. |
222
292
  | `POST /api/specs/:id/attachments` | `backlog:write` | unggah lampiran (`multipart/form-data`, beberapa berkas per request). Berkas yang ditolak **tak** menggagalkan yang lain — periksa `rejected[]`. |
223
293
  | `GET /api/specs/:id/attachments/:attId` | `backlog:read` | byte satu lampiran. |
224
294
  | `DELETE /api/specs/:id/attachments/:attId` | `backlog:write` | hapus satu lampiran. |
225
295
  | `GET /api/specs/:id/docs` | `backlog:read` | dokumen yang ditulis sesi item itu. |
226
- | `GET /api/specs/:id/review` | `backlog:read` | diff hasil kerja sesi. |
296
+ | `GET /api/specs/:id/review` | `backlog:read` | diff hasil kerja sesi. Bila belum ada worktree maupun commit: **200** dengan `empty: true` dan `reason` — `no-worktree` (belum pernah dimulai) atau `no-commit` (worktree sudah lenyap tanpa commit); `base`, `files`, dan `changed` kosong. Itu keadaan, bukan galat (ADR-0213). Sesi memakai pola yang sama (ADR-0215). |
297
+ | `DELETE /api/specs/:id/worktree` | `ide:git` (danger) | hapus worktree satu item (tool `hanoman_backlog_worktree_delete`, ADR-0213). Perubahan yang belum di-commit di dalamnya **hilang** dan sesi yang berjalan di sana ditutup; cabang dan commit tetap. Balasan **200** `{ name, ok, cleanup, closedSession?, error? }` — `ok: false` bila worktree tak ditemukan atau tak bisa dihapus. Capability-nya sama dengan `POST /api/projects/:id/worktrees/delete`, bukan `backlog:write`. |
227
298
  | `GET /api/projects/:id/docs` | `docs:read` | index Source of Truth project. |
228
299
  | `GET /api/projects/:id/docs/<path>` | `docs:read` | isi satu dokumen. |
229
- | `GET /api/projects/:id/changelog` | `docs:read` | changelog yang sudah dibangkitkan (paginated). |
230
- | `POST /api/projects/:id/changelog` | `docs:write` | bangkitkan changelog baru — bentuknya di **§6a**. |
231
300
  | `GET /api/terminal/sessions` | `sessions:read` | sesi yang sedang hidup. |
301
+ | `GET /api/terminal/sessions/:id/review` | `sessions:read` | diff worktree sesi project-level (PRD). Bentuknya sama dengan review backlog item. Bila sesi tak punya worktree (`no-worktree`) atau worktree-nya sudah lenyap tanpa commit (`no-commit`): **200** dengan `empty: true` dan `reason`; `base`, `files`, dan `changed` kosong. Itu keadaan, bukan galat (ADR-0215, amandemen ADR-0213). Tool `hanoman_session_review`. |
302
+ | `GET /api/terminal/sessions/:id/review/<path>` | `sessions:read` | isi diff satu berkas. **409** bila tak ada sumber sama sekali (tak ada berkas untuk dibaca); 404 bila path di luar daftar. |
232
303
  | `GET /api/notifications` | `notifications:read` | notifikasi. **Tanpa `limit` → 50 teratas**, bukan seluruhnya (lihat jebakan di §10). |
233
304
  | `GET /api/tickets` | `support:read` | tiket Help Center. |
234
305
  | `GET /api/lead/decisions` | `lead:read` | jejak keputusan hanoman-lead. Menerima `page`/`limit`; `take`/`skip` lama tetap jalan. |
235
306
  | `GET /api/scheduler/queue` | `settings:read` | antrean scheduler (`?status=queued\|launched\|done\|failed`). `GET /api/scheduler/state` **tak lagi** memuat `queue` — ia memuat `queueCounts`. |
236
307
  | `POST /api/lead/decisions` | `lead:write` | minta putusan — baca **§8** dan **§11** dulu. |
237
308
 
238
- ## 6a. Changelog per project
239
-
240
- Ringkasan perubahan **berorientasi pemakai** — bukan daftar commit. Tiga mode, satu endpoint;
241
- `mode` menentukan field lainnya:
242
-
243
- ```bash
244
- # 1) backlog yang selesai di rentang tanggal (dua field opsional → 30 hari terakhir)
245
- curl -sS -X POST "$HANOMAN_HOST/api/projects/<id>/changelog" \
246
- -H "Authorization: Bearer $HANOMAN_AGENT_TOKEN" -H 'Content-Type: application/json' \
247
- -d '{"mode":"backlog","from":"2026-07-01","to":"2026-07-31"}'
248
-
249
- # 2) rentang commit di repo project
250
- -d '{"mode":"commit","fromSha":"4f2a1c9","toSha":"HEAD"}'
251
-
252
- # 3) versi/tag rilis (fromTag opsional → sejak versi sebelumnya)
253
- -d '{"mode":"version","toTag":"v1.2.0"}'
254
- ```
255
-
256
- Jawaban **201** berisi `body` (markdown siap pakai), `title`, `itemCount`, `generator`, dan
257
- `warning`. Ambil ulang atau unduh kapan saja:
258
-
259
- ```bash
260
- curl -sS "$HANOMAN_HOST/api/projects/<id>/changelog/<cid>?download=md" \
261
- -H "Authorization: Bearer $HANOMAN_AGENT_TOKEN"
262
- ```
263
-
264
- Tiga hal yang perlu kamu tahu sebelum memanggilnya:
265
-
266
- - **Panggil `GET /api/projects/:id/changelog/sources` dulu.** Ia memberi tag yang tersedia, HEAD
267
- singkat, rentang default, dan — bila repo belum ditautkan di mesin itu atau belum punya tag — satu
268
- `reason` yang menjelaskan sebabnya. Ia menjawab **200**, bukan galat, jadi jangan perlakukan
269
- `reason` sebagai kegagalan.
270
- - **422 berarti permintaanmu sah tapi tak ada isinya** (rentang kosong, repo tanpa tag, revisi tak
271
- dikenal) — pesannya bisa langsung diteruskan ke manusia. **400** berarti bentuknya salah
272
- (mis. `from` lebih baru dari `to`).
273
- - **`generator:"fallback"` bukan kegagalan.** Artinya narasi otomatis tak tersedia dan yang kamu
274
- terima adalah draf ringkas deterministik; alasannya ada di `warning`.
275
-
276
309
  ## 7. `POST /api/specs` — bentuk payload per `source`
277
310
 
278
311
  `source` dan bentuk `payload` **saling mengikat**. Salah pasang → **400**
@@ -324,9 +357,11 @@ capability-nya. Capability menjawab "boleh?", bukan "sebaiknya?".
324
357
  | `POST /api/terminal/sessions` | melahirkan proses agen `--dangerously-skip-permissions` di sebuah worktree — **RCE efektif**. Batas satu-satunya adalah isolasi git worktree (ADR-0037). |
325
358
  | `POST`/`PUT`/`DELETE` di bawah `/api/vps` | **remote exec** di server produksi. |
326
359
  | `POST /api/lead/decisions` | putusannya bisa **menggerakkan sesi** (integrate ke main, menghentikan sesi) dan selalu melahirkan baris jejak permanen (ADR-0091/0098). |
360
+ | `POST /api/update/apply`, `POST /api/restart` | memasang paket npm dan **me-restart server** (ADR-0200): dashboard & koneksi MCP putus beberapa detik; pane tmux selamat. Selalu `confirm: false` dulu untuk membaca jumlah sesi hidup. |
361
+ | `POST /api/sync/conflicts/:entity/:recordId/resolve` | **membuang data satu sisi** (lokal atau hub) tanpa jalan pulang (ADR-0199). Baca `differs` dari `hanoman_sync_conflicts` dulu. |
327
362
 
328
- Perlakukan `POST /api/specs/:id/integrate`, `DELETE /api/specs/:id`, dan perubahan `stage` dengan
329
- disiplin yang sama: ketiganya mengubah sejarah git atau membuang pekerjaan. `POST /api/specs/:id/done`
363
+ Perlakukan `POST /api/specs/:id/integrate`, `DELETE /api/specs/:id`, `DELETE /api/specs/:id/worktree`,
364
+ dan perubahan `stage` dengan disiplin yang sama: keempatnya mengubah sejarah git atau membuang pekerjaan. `POST /api/specs/:id/done`
330
365
  tak menyentuh git, tetapi ia **menyatakan pekerjaan orang lain selesai** — pakai hanya bila kamu punya
331
366
  buktinya, dan tulis buktinya di `reason`.
332
367
 
@@ -506,7 +541,20 @@ Panduan siap salin untuk keempat klien, berikut tabel tool → capability, ada d
506
541
  | `hanoman_lead_decisions_list` | baca | `lead:read` |
507
542
  | `hanoman_backlog_create`, `hanoman_backlog_update` | tulis | `backlog:write` |
508
543
  | `hanoman_notifications_mark_read` | tulis | `notifications:write` |
544
+ | `hanoman_notification_mark_read` | tulis | `notifications:write` — satu notifikasi berdasarkan `id` (ADR-0204), idempoten |
545
+ | `hanoman_scheduler_queue_cancel`, `hanoman_scheduler_queue_requeue` | tulis | `settings:write`; requeue baris `failed` juga menuntut `sessions:spawn` (ADR-0204, 403 bila tak ada) |
509
546
  | `hanoman_lead_ask` | tulis | `lead:write` |
547
+ | `hanoman_health`, `hanoman_models`, `hanoman_codex_version`, `hanoman_methods_status` | baca | — (`GLOBAL_READ`, ADR-0187) |
548
+ | `hanoman_activity_list` | baca | `activity:read` |
549
+ | `hanoman_activity_purge` | **berbahaya** | `activity:write` |
550
+ | `hanoman_portal_admin_chat_sessions`, `hanoman_portal_admin_chat_session_get` | baca | `portal-chat:read` |
551
+ | `hanoman_portal_admin_chat_export` | **berbahaya** | `portal-chat:read` |
552
+ | `hanoman_portal_admin_chat_prd_write` | **berbahaya** | `portal-chat:write` |
553
+ | `hanoman_sync_conflicts`, `hanoman_sync_pending` | baca | `sync:read` |
554
+ | `hanoman_sync_now` | tulis | `sync:write` |
555
+ | `hanoman_sync_conflict_resolve` | **danger** | `sync:resolve` |
556
+ | `hanoman_update_apply`, `hanoman_instance_restart` | **danger** | `instance:admin` |
557
+ | `hanoman_agent_guide` | baca | — (membaca halaman ini lewat `GET /api/agent-integration.md`, publik) |
510
558
 
511
559
  ### Yang sengaja TIDAK tersedia lewat MCP
512
560
 
@@ -516,18 +564,70 @@ penghapusan backlog, dan perubahan `stage`. Batasan ini ada di katalog toolnya,
516
564
  token yang punya `sessions:write` sekalipun tak akan menemukan tool untuk memakainya. Lihat §8.
517
565
 
518
566
  **Menjawab dialog sesi** (`GET /api/terminal/sessions/:id/dialog`,
519
- `POST /api/terminal/sessions/:id/dialog/answer`, dan sejak SPEC-909 · ADR-0146 juga
520
- `POST /api/terminal/sessions/:id/dialog/takeover` — merebut sesi dari hanoman-lead) sengaja **tak
521
- punya tool** — dan alasannya lebih tajam dari sekadar "mengeksekusi": endpoint itu menjawab pertanyaan
522
- yang **secara desain ditujukan kepada manusia** (`AskUserQuestion`). Agen yang bisa memanggilnya
523
- bisa menjawab pertanyaannya sendiri, dan gerbang "manusia terakhir yang memutuskan" runtuh lewat
524
- pintu belakang. Capability-nya tetap ada (`sessions:read`/`sessions:write`) karena peta itu berlaku
525
- untuk seluruh permukaan HTTP, bukan hanya untuk yang muncul di MCP.
526
-
527
- **Lampiran backlog** (`/api/specs/:id/attachments*`, SPEC-843 · ADR-0124) juga sengaja **tak punya
528
- tool**: berkasnya lahir dari disk manusia, bukan dari model, dan tool MCP berbentuk JSON sehingga
529
- byte biner tak punya representasi di sana. REST-nya tetap terjangkau agent token
530
- ber-`backlog:read`/`backlog:write` — yang tak dipajang adalah **tool**-nya.
567
+ `POST /api/terminal/sessions/:id/dialog/answer`, `POST /api/terminal/sessions/:id/dialog/takeover`)
568
+ dulu sengaja tak punya tool (SPEC-899 · ADR-0142: agen yang bisa menjawab `AskUserQuestion` bisa
569
+ menjawab pertanyaannya sendiri, dan gerbang "manusia terakhir yang memutuskan" runtuh). Sejak
570
+ **ADR-0190/ADR-0192** pemilik membungkusnya sebagai tool `hanoman_session_dialog_get|answer|takeover`
571
+ (semuanya `danger`, hanya muncul dengan `--danger`) — dengan gerbang di **server**, bukan di
572
+ deskripsi:
573
+
574
+ - capability **`sessions:decide`** (domain `decisions`), terpisah dari `sessions:write`; `steer` ke
575
+ pane yang sedang menampilkan dialog pun menuntutnya;
576
+ - **403** bila token ITU yang meluncurkan sesinya (`reason: "launcher"`), atau bila panggilan datang
577
+ dari dalam sesi hanoman — proses MCP yang diwarisi `HANOMAN_SESSION_ID` mengirim header
578
+ `x-hanoman-session` (`reason: "self"` / `"in-session"`);
579
+ - **409** `deciding` (hanoman-lead sedang menyusun keputusan; rebut dulu lewat `takeover`) dan
580
+ `answering` tetap berlaku. 403 mendahului 409 supaya penolakan tak membocorkan keadaan lead.
581
+
582
+ **Batas yang jujur:** server tak bisa membuktikan siapa di balik sebuah request Bearer — sesi tidak
583
+ menerima agent token dari hanoman, jadi tak ada ikatan token↔sesi. Header `x-hanoman-session`
584
+ bisa dilepas oleh klien yang sengaja memakai token yang sama lewat `curl`. Yang menutupnya adalah
585
+ praktik: **jangan pasang token ber-`sessions:decide` di konfigurasi yang dibaca sesi hanoman**
586
+ (mis. `~/.claude.json` tingkat-user diwarisi setiap sesi). Pakai token terpisah, di klien MCP yang
587
+ dijalankan manusia di luar sesi.
588
+
589
+ **Cronjob** (`/api/scheduler/crons*`, ADR-0191 membalik ADR-0112) kini punya tool
590
+ `hanoman_scheduler_crons_list|cron_runs` (baca, `scheduler:read`) dan
591
+ `hanoman_scheduler_cron_create|update|delete|run` (`danger`, `scheduler:write`). Karena sebuah cron
592
+ adalah `POST /terminal/sessions` yang ditunda, **create, update yang menyentuh prompt/agen/model/
593
+ effort/jadwal atau mengaktifkan, dan run** juga menuntut `sessions:spawn` — ditegakkan di handler
594
+ server (403 `need: sessions:spawn`), bukan hanya disebut di deskripsi. Mengganti nama, menonaktifkan,
595
+ dan menghapus cron cukup dengan `scheduler:write`.
596
+
597
+ **Lampiran, ekspor/impor QA, unggah IDE, dan arsip git** punya tool sejak ADR-0183 — lihat
598
+ [Transfer berkas](#transfer-berkas-adr-0183) di bawah. Yang tetap tak punya tool hanya WebSocket
599
+ (`/terminal/sessions/:id/ws`, `/events/ws`) dan tiga route `dialog` di atas.
600
+
601
+ ### Transfer berkas (ADR-0183)
602
+
603
+ Tool MCP berbicara JSON teks, jadi **byte tak pernah lewat model**. Satu mekanisme seragam:
604
+
605
+ - **Unggah** — argumen `filePath` / `filePaths` / `files[].filePath` adalah path **lokal di mesin
606
+ tempat `hanoman mcp` berjalan**. Klien membacanya dan mengirimnya sebagai multipart.
607
+ - **Unduh** — byte disimpan ke `savePath` lokal (default: direktori sementara aman); jawabannya
608
+ `{savedTo, filename, bytes, mime, sha256}`. `overwrite:true` wajib untuk menimpa berkas yang ada.
609
+ Teks kecil (template Markdown, ekspor `md`/`csv`) dikembalikan **inline** oleh
610
+ `hanoman_qa_template_md` dan `hanoman_qa_report_export_text`.
611
+ - **Batas (ADR-0184)** — path hanya boleh di dalam direktori kerja MCP, direktori sementara MCP,
612
+ atau `HANOMAN_MCP_TRANSFER_DIRS`; `..`, symlink yang keluar, dan berkas rahasia (`.env`,
613
+ kunci SSH, `.git`, …) ditolak di kedua arah; ukuran dibatasi (default 100 MiB).
614
+ - **Mode (ADR-0186)** — tool yang menyentuh disk klien bermode `write` walau capability-nya `:read`,
615
+ sehingga `--read-only` tak pernah menyentuh disk.
616
+
617
+ | Tool | Mode | Capability |
618
+ |---|---|---|
619
+ | `hanoman_backlog_attachment_upload`, `hanoman_backlog_attachment_download` | tulis | `backlog:write` / `backlog:read` |
620
+ | `hanoman_ticket_attachment_download` | tulis | `support:read` |
621
+ | `hanoman_qa_attachment_upload`, `hanoman_qa_import`, `hanoman_qa_cases_import` | tulis | `qa:write` |
622
+ | `hanoman_qa_attachment_download`, `hanoman_qa_template_xlsx`, `hanoman_qa_report_export_file` | tulis | `qa:read` |
623
+ | `hanoman_qa_template_md`, `hanoman_qa_report_export_text` (inline) | baca | `qa:read` |
624
+ | `hanoman_ide_upload` | tulis | `ide:write` |
625
+ | `hanoman_ide_archive_download` | tulis | `ide:read` |
626
+ | `hanoman_session_attachment_upload` (PNG/JPEG/WebP ≤ 5 MB; jawabannya path di SERVER) | tulis | `sessions:write` |
627
+ | `hanoman_qa_report_delete`, `hanoman_qa_case_delete`, `hanoman_qa_finding_delete`, `hanoman_qa_attachment_delete` | **berbahaya** | `qa:delete` (ADR-0185) |
628
+
629
+ `qa:delete` adalah akses `danger` yang **tak diimplikasikan `qa:write`**: token lama ber-`qa:write`
630
+ tak lagi bisa menghapus data QA sampai manusia mencentang `qa:delete` di Settings → Akses AI Agent.
531
631
 
532
632
  ### Opsi
533
633
 
@@ -536,6 +636,8 @@ ber-`backlog:read`/`backlog:write` — yang tak dipajang adalah **tool**-nya.
536
636
  | `HANOMAN_HOST` / `--host <url>` | **Wajib.** Instance yang dituju. Agent token diterbitkan per-instance — token dari instance lain selalu 401 di sini, dan MCP server menjelaskannya, bukan meneruskan 401 telanjang. |
537
637
  | `HANOMAN_AGENT_TOKEN` | **Wajib.** Hanya dari env atau `~/.hanoman/agent-token` — **tak pernah** dari argumen baris perintah (ARGV terbaca proses lain di mesin yang sama). |
538
638
  | `HANOMAN_MCP_READ_ONLY=1` / `--read-only` | Menyembunyikan seluruh tool tulis dari `tools/list`. |
639
+ | `HANOMAN_MCP_TRANSFER_DIRS` / `--transfer-dir <d>` | Direktori tambahan (dipisah `:`) yang boleh dibaca/ditulis tool transfer berkas, di luar direktori kerja MCP dan direktori sementara MCP. Akar filesystem ditolak. |
640
+ | `HANOMAN_MCP_TRANSFER_MAX_BYTES` / `--transfer-max-bytes <n>` | Plafon ukuran per berkas dan total per panggilan transfer. Default 100 MiB, maks 2 GiB. |
539
641
  | `HANOMAN_MCP_MAX_BYTES` / `--max-bytes <n>` | Plafon ukuran balasan tool. Default 24576. Balasan yang dipotong ditandai `truncated: true` + `shown`/`total`. |
540
642
 
541
643
  Skema tool berversi (`MCP_TOOL_SCHEMA_VERSION`, saat ini **1**) dan aditif dalam satu versi:
@@ -547,3 +649,12 @@ menambah tool tak mematahkan klien lama.
547
649
  untuk permukaan MCP, [ADR-0099](../internal/docs/adr/0099-mcp-server-hanoman.md). Kontrak API penuh:
548
650
  [`internal/docs/architecture/api-contract.md`](../internal/docs/architecture/api-contract.md) —
549
651
  permukaan REST-nya identik dengan yang dipakai dashboard.*
652
+
653
+
654
+ ## Fitur yang dihapus — skema tool MCP versi 2
655
+
656
+ Memori project dan changelog dihapus (ADR-0182). Tool `hanoman_memory_*` dan
657
+ `hanoman_changelog_*` tidak tersedia; endpoint `/api/memories*` dan
658
+ `/api/projects/:id/changelog*` tidak terdaftar. Token tidak lagi memiliki
659
+ capability `memory:*` atau allowlist project khusus memori. Fungsi context/memory
660
+ kanal Telegram tetap tersedia melalui domain `telegram`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hanoman",
3
- "version": "0.9.19",
3
+ "version": "0.9.21",
4
4
  "description": "Orchestrator + dashboard workflow docs-driven untuk sesi Claude Code / Codex",
5
5
  "type": "module",
6
6
  "repository": {
@@ -0,0 +1,40 @@
1
+ -- ADR-0182: user requested removal of these features AND their stored data.
2
+ -- Purge sync payloads/outboxes first so retired records cannot be replayed.
3
+ DELETE FROM "SyncLog" WHERE "entity" IN ('projectMemory', 'memoryEvent');
4
+ DELETE FROM "SyncOutbox" WHERE "entity" IN ('projectMemory', 'memoryEvent');
5
+ DELETE FROM "SyncConflict" WHERE "entity" IN ('projectMemory', 'memoryEvent');
6
+ DELETE FROM "SyncTombstone" WHERE "entity" IN ('projectMemory', 'memoryEvent');
7
+ UPDATE "SyncState" SET "entities" = trim(replace(replace(',' || "entities" || ',', ',projectMemory,', ','), ',memoryEvent,', ','), ',');
8
+ DELETE FROM "Notification" WHERE "type" = 'memory';
9
+ UPDATE "Setting" SET "data" = json_remove("data", '$.changelog') WHERE json_type("data", '$.changelog') IS NOT NULL;
10
+ UPDATE "AgentToken" SET "capabilities" = (
11
+ SELECT json_group_array(value) FROM json_each("AgentToken"."capabilities")
12
+ WHERE value NOT IN ('memory:read', 'memory:write')
13
+ );
14
+
15
+ DROP TABLE "MemoryEvent";
16
+ DROP TABLE "MemoryLocalState";
17
+ DROP TABLE "ProjectMemory";
18
+ DROP TABLE "Changelog";
19
+
20
+ PRAGMA defer_foreign_keys=ON;
21
+ PRAGMA foreign_keys=OFF;
22
+ CREATE TABLE "new_AgentToken" (
23
+ "id" TEXT NOT NULL PRIMARY KEY,
24
+ "name" TEXT NOT NULL,
25
+ "tokenHash" TEXT NOT NULL,
26
+ "tokenPrefix" TEXT NOT NULL,
27
+ "capabilities" JSONB NOT NULL,
28
+ "enabled" BOOLEAN NOT NULL DEFAULT true,
29
+ "createdBy" TEXT,
30
+ "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
31
+ "lastUsedAt" DATETIME,
32
+ "revokedAt" DATETIME
33
+ );
34
+ INSERT INTO "new_AgentToken" ("capabilities", "createdAt", "createdBy", "enabled", "id", "lastUsedAt", "name", "revokedAt", "tokenHash", "tokenPrefix") SELECT "capabilities", "createdAt", "createdBy", "enabled", "id", "lastUsedAt", "name", "revokedAt", "tokenHash", "tokenPrefix" FROM "AgentToken";
35
+ DROP TABLE "AgentToken";
36
+ ALTER TABLE "new_AgentToken" RENAME TO "AgentToken";
37
+ CREATE UNIQUE INDEX "AgentToken_tokenHash_key" ON "AgentToken"("tokenHash");
38
+ PRAGMA foreign_keys=ON;
39
+ PRAGMA defer_foreign_keys=OFF;
40
+
@@ -0,0 +1,13 @@
1
+ -- ADR-0218 · penanda "sudah ditinjau" pada item selesai (filter Backlog "Perlu ditinjau").
2
+ --
3
+ -- Ditulis tangan (bukan `migrate dev`), pola SPEC-516/SPEC-804: worktree tetangga membuat
4
+ -- `migrate dev` me-reset DB saat ada drift. ADITIF murni — satu kolom NULLABLE tanpa default.
5
+ ALTER TABLE "Spec" ADD COLUMN "reviewedAt" DATETIME;
6
+
7
+ -- Backfill sekali-jalan: item yang SUDAH selesai dianggap sudah ditinjau pada `updatedAt`-nya.
8
+ -- Tanpa ini setiap item selesai yang pernah dikerjakan sesi (baseSha terisi) langsung masuk
9
+ -- "Perlu ditinjau" begitu migrasi jalan, dan filter yang sejak hari pertama berisi ratusan baris
10
+ -- tak lagi bisa dipercaya. Harganya diterima dan ditulis di ADR-0218: penanda hanya berlaku untuk
11
+ -- penyelesaian SETELAH migrasi ini. Item lama yang belum pernah ditinjau tetap bisa dibuka lewat
12
+ -- Review perubahan, hanya saja tidak lagi disorot oleh filter.
13
+ UPDATE "Spec" SET "reviewedAt" = "updatedAt" WHERE "stage" = 'done' AND "reviewedAt" IS NULL;
@@ -32,12 +32,10 @@ model Project {
32
32
  tickets Ticket[]
33
33
  customAgents CustomAgent[] // SPEC-450 · ADR-0094 · custom agent milik project ini
34
34
  githubIssues GithubIssue[] // SPEC-471 · ADR-0095 · cermin issue repo GitHub project ini
35
- changelogs Changelog[] // SPEC-516 · ADR-0105 · changelog tersimpan project ini
36
35
  clientAccess ClientProjectAccess[] // SPEC-617 · ADR-0110 · akun klien yang boleh melihat project ini
37
36
  portalChats PortalChatSession[] // SPEC-854 · ADR-0129 · percakapan portal klien project ini
38
37
  tasks Task[] // SPEC-945 · ADR-0150 · kartu kerja manusia di project ini
39
38
  qaReports QaReport[] // Workspace QA · laporan QA project ini
40
- memories ProjectMemory[] // ADR-0178 · memori project bersama lintas runtime
41
39
  }
42
40
 
43
41
  model Spec {
@@ -77,6 +75,12 @@ model Spec {
77
75
  // ditandai manual. Ditimpa tiap penandaan berikutnya: ia menjelaskan keadaan yang BERLAKU;
78
76
  // riwayat transisi stage tinggal di SessionResult (ADR-0047). Ikut FIELDS.spec sync.
79
77
  manualDone Json?
78
+ // ADR-0218 · kapan operator menandai item SELESAI ini sudah ditinjau ("Tandai ditinjau"). null =
79
+ // belum ditinjau, atau item belum selesai. Ditulis HANYA oleh `POST /specs/:id/reviewed`, dan
80
+ // di-reset ke null saat stage dikembalikan mundur dari `done` (PATCH stage, POST source). SENGAJA
81
+ // tak di-reset oleh recordCompletion: reconcile memanggilnya ulang untuk item yang sudah selesai,
82
+ // dan itu akan menghapus tanda yang baru dipasang. Ikut FIELDS.spec sync (ADR-0218).
83
+ reviewedAt DateTime?
80
84
  // SPEC-447 · ADR-0093 · id spec lain yang harus SELESAI & commit-nya sudah ada di branch basis
81
85
  // sebelum item ini boleh diluncurkan. Array JSON of string; null/[] = tak bergantung apa pun.
82
86
  // Sengaja kolom, bukan tabel join: SQLite melarang scalar list, `Json` sudah dipakai `payload`,
@@ -106,7 +110,7 @@ model Spec {
106
110
  // `storageKey` opaque, `filename` nama asli tersanitasi untuk tampilan saja.
107
111
  //
108
112
  // LOCAL-only: TANPA `version`, jadi ia tak pernah masuk changefeed sync (cermin LeadFlow,
109
- // WebhookEndpoint, Changelog). Byte memang tak menyeberang (ADR-0062), dan metadata yang menyeberang
113
+ // WebhookEndpoint). Byte memang tak menyeberang (ADR-0062), dan metadata yang menyeberang
110
114
  // TANPA byte-nya hanya menghasilkan lampiran yang tak bisa dibuka di mesin lain — keadaan terburuk
111
115
  // dari ketiga pilihan. Sengaja BUKAN TicketAttachment: tiket dan backlog dua domain dengan aturan
112
116
  // sync dan tingkat kepercayaan yang berbeda (ADR-0124 §1/§4).
@@ -124,29 +128,6 @@ model SpecAttachment {
124
128
  @@index([specId])
125
129
  }
126
130
 
127
- // SPEC-516 · ADR-0105 · changelog naratif per project yang sudah dibangkitkan.
128
- //
129
- // LOCAL-only: TANPA kolom `version`, jadi ia tak pernah masuk changefeed sync (cermin LeadFlow,
130
- // WebhookEndpoint, dan Project.autoMerge). Dua dari tiga modenya diturunkan dari checkout git di
131
- // MESIN INI, jadi barisnya fakta lokal; yang portabel adalah keluarannya, dan jalannya sudah ada
132
- // — unduh .md.
133
- model Changelog {
134
- id String @id @default(cuid())
135
- projectId String
136
- mode String // "backlog" | "commit" | "version" (zChangelogMode di @hanoman/shared)
137
- title String // judul di daftar: rentang tanggal / nama tag / rentang tag
138
- params Json // parameter pembangkitan apa adanya (zChangelogRequest)
139
- body String // markdown hasil akhir, sudah lewat scrubOutput
140
- generator String // "agent" | "fallback"
141
- warning String? // alasan fallback + catatan cakupan; null = mulus
142
- itemCount Int @default(0)
143
- createdAt DateTime @default(now())
144
- updatedAt DateTime @updatedAt
145
- project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
146
-
147
- @@index([projectId, createdAt])
148
- }
149
-
150
131
  // SPEC-450 · ADR-0094 · custom agent: persona yang bisa dipilih ulang & dibagi.
151
132
  // `projectId` null = GLOBAL (semua project); terisi = milik satu project, dan agen project
152
133
  // MENIMPA agen global bernama sama.
@@ -439,8 +420,6 @@ model AgentToken {
439
420
  tokenHash String @unique
440
421
  tokenPrefix String
441
422
  capabilities Json
442
- // ADR-0178 · allowlist project. null/[] = token tak boleh menyentuh route memori sama sekali.
443
- projectIds Json?
444
423
  enabled Boolean @default(true)
445
424
  createdBy String?
446
425
  createdAt DateTime @default(now())
@@ -1159,55 +1138,3 @@ model QaAttachment {
1159
1138
  @@index([ownerType, ownerId])
1160
1139
  @@index([syncState])
1161
1140
  }
1162
-
1163
- // ADR-0178 · satu fakta per baris. content/anchors/scopePaths/kind TAK PERNAH diedit — koreksi =
1164
- // baris baru dengan `supersedesId`. Hanya `status` yang bergerak, dan hanya naik (lattice).
1165
- model ProjectMemory {
1166
- id String @id @default(cuid())
1167
- projectId String
1168
- project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
1169
- kind String // convention | gotcha | decision | fact
1170
- content String
1171
- scopePaths Json // string[] glob relatif root repo; [] = seluruh project
1172
- anchors Json // [{path, blobSha, lines?}]
1173
- status String // proposed | active | invalidated | rejected
1174
- supersedesId String?
1175
- reviewReason String? // decision | no-anchor | anchor-unverified | untrusted-source
1176
- sourceRuntime String // claude | codex | external | human
1177
- sourceSessionId String?
1178
- sourceTokenId String?
1179
- sourceDeviceId String? // diisi tahap sync
1180
- commitSha String?
1181
- trusted Boolean @default(true)
1182
- version Int @default(0)
1183
- createdAt DateTime @default(now())
1184
- updatedAt DateTime @updatedAt
1185
- events MemoryEvent[]
1186
-
1187
- @@index([projectId, status])
1188
- }
1189
-
1190
- // ADR-0178 · jejak audit append-only. Tak pernah di-update atau dihapus kecuali ikut cascade.
1191
- model MemoryEvent {
1192
- id String @id @default(cuid())
1193
- memoryId String
1194
- memory ProjectMemory @relation(fields: [memoryId], references: [id], onDelete: Cascade)
1195
- op String // propose | activate | reject | invalidate | supersede | reverify
1196
- actorKind String // user | token | session | system
1197
- actorId String?
1198
- reason String?
1199
- createdAt DateTime @default(now())
1200
- version Int @default(0) // ADR-0180 · wajib bagi entitas sync
1201
- updatedAt DateTime @updatedAt
1202
-
1203
- @@index([memoryId, createdAt])
1204
- }
1205
-
1206
- // ADR-0178 · LOCAL-ONLY (tak pernah disync): hasil verifikasi jangkar bergantung HEAD mesin ini.
1207
- model MemoryLocalState {
1208
- memoryId String @id
1209
- verdict String // valid | stale | unverifiable
1210
- verifiedHead String?
1211
- lastVerifiedAt DateTime?
1212
- lastUsedAt DateTime?
1213
- }