hanoman 0.9.31 → 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 +290 -154
  3. package/dist/server.js +21875 -20313
  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
package/dist/cli.js CHANGED
@@ -4491,6 +4491,127 @@ var init_auto_merge = __esm({
4491
4491
  }
4492
4492
  });
4493
4493
 
4494
+ // ../shared/src/lead.ts
4495
+ var zLeadGate, zLeadKind, zLeadConfidence, zLeadStatus, zLeadFlowStatus, LEAD_ACTIONS, zLeadAction, zLeadChoice, zLeadSelect, zLeadVerdict, zLeadAsk, zLeadOverride, LEAD_AUTHORITY_MODES, zLeadAuthorityMode, zLeadProject, zLeadAct;
4496
+ var init_lead = __esm({
4497
+ "../shared/src/lead.ts"() {
4498
+ "use strict";
4499
+ init_zod();
4500
+ zLeadGate = external_exports.enum(["contract", "detected", "pulse"]);
4501
+ zLeadKind = external_exports.enum(["answer", "order", "collision", "quality", "refusal"]);
4502
+ zLeadConfidence = external_exports.enum(["tinggi", "sedang", "ragu"]);
4503
+ zLeadStatus = external_exports.enum(["berlaku", "ditimpa", "dibatalkan", "gagal"]);
4504
+ zLeadFlowStatus = external_exports.enum(["menunggu", "sebagian", "selesai", "dibatalkan"]);
4505
+ LEAD_ACTIONS = [
4506
+ "none",
4507
+ // keputusan murni jawaban; tak ada tindakan menyusul
4508
+ "answer-session",
4509
+ // ketik jawaban ke pane sesi (pintu deteksi otomatis)
4510
+ "start-session",
4511
+ // mulai sesi untuk sebuah backlog (lewat antrean scheduler)
4512
+ "stop-session",
4513
+ // hentikan sesi — worktree DIBIARKAN utuh (AC-32a)
4514
+ "resume-session",
4515
+ // lanjutkan sesi terputus (jalur ADR-0084)
4516
+ "restart-session",
4517
+ // ulangi pekerjaan dari awal
4518
+ "order-queue",
4519
+ // tata urutan antrean yang sudah ada (ADR-0072) — bukan antrean kedua
4520
+ "hold-work",
4521
+ // tunda satu pekerjaan karena tabrakan area kerja
4522
+ "push-branch",
4523
+ // dorong perubahan ke branch
4524
+ "integrate-main",
4525
+ // integrasikan ke branch utama (risiko yang diterima sadar, PRD §Risiko)
4526
+ "run-migration"
4527
+ // jalankan migration pada basis data LOKAL operator
4528
+ ];
4529
+ zLeadAction = external_exports.enum(LEAD_ACTIONS);
4530
+ zLeadChoice = external_exports.object({ index: external_exports.number().int().positive(), option: external_exports.string() });
4531
+ zLeadSelect = external_exports.object({
4532
+ mode: external_exports.enum(["single", "multi"]).default("single"),
4533
+ min: external_exports.number().int().min(0).max(20).default(0),
4534
+ max: external_exports.number().int().min(1).max(20).nullable().default(null)
4535
+ });
4536
+ zLeadVerdict = external_exports.object({
4537
+ decision: external_exports.string().min(1),
4538
+ reason: external_exports.string().min(1),
4539
+ refs: external_exports.array(external_exports.string()).default([]),
4540
+ confidence: zLeadConfidence.default("sedang"),
4541
+ // Sengaja `string`, BUKAN `zLeadAction`: tindakan di luar allowlist harus bisa MASUK supaya
4542
+ // server menolaknya secara sadar — mencatat penolakan & menotifikasi operator (AC-33). Kalau
4543
+ // enum yang menyaring di sini, permintaan "deploy ke produksi" hanya akan tampak sebagai
4544
+ // keluaran rusak, dan justru peristiwa paling layak dilaporkan itulah yang hilang dari jejak.
4545
+ action: external_exports.string().default("none"),
4546
+ /**
4547
+ * SPEC-480 · opsi yang dipilih — nomor ATAU label. Sengaja `string`, bukan enum/number: pilihan
4548
+ * di luar daftar harus BISA MASUK supaya server menolaknya secara sadar & mencatatnya, alasan
4549
+ * yang sama persis dengan `action` di atas.
4550
+ */
4551
+ choice: external_exports.string().default(""),
4552
+ /**
4553
+ * SPEC-485 · pilihan JAMAK. `string[]` dengan alasan yang sama persis seperti `choice` & `action`
4554
+ * di atas: pilihan di luar daftar harus BISA MASUK supaya server menolaknya secara sadar dan
4555
+ * mencatatnya. Kosong + `choice` terisi dibaca sebagai satu pilihan — keluaran agen berbentuk
4556
+ * ADR-0098 harus tetap terpakai, dan menuntut field baru berarti setiap agen lama mendadak
4557
+ * "tak memilih apa pun".
4558
+ */
4559
+ choices: external_exports.array(external_exports.string().max(2e3)).max(20).default([]),
4560
+ /**
4561
+ * SPEC-480 · apa yang KURANG bila konteksnya memang tak cukup untuk memutuskan. Bukan pengganti
4562
+ * `confidence: "ragu"` (bukti tipis, jawabannya tetap ada) melainkan untuk fakta konkret yang
4563
+ * tak ada di repo maupun konteks. Terisi ⇒ server memaksa `ragu` ⇒ operator dinotifikasi.
4564
+ */
4565
+ missing: external_exports.array(external_exports.string().max(200)).max(10).default([]),
4566
+ /** Teks yang benar-benar diketik ke pane sesi (pintu deteksi otomatis). */
4567
+ reply: external_exports.string().default("")
4568
+ });
4569
+ zLeadAsk = external_exports.object({
4570
+ projectId: external_exports.string().min(1),
4571
+ specId: external_exports.string().nullish(),
4572
+ sessionId: external_exports.string().nullish(),
4573
+ question: external_exports.string().min(1).max(8e3),
4574
+ options: external_exports.array(external_exports.string().max(2e3)).max(20).default([]),
4575
+ context: external_exports.string().max(2e4).default(""),
4576
+ /** SPEC-485 · bentuk pilihan yang diminta peminta. Default = perilaku hari ini (single). */
4577
+ select: zLeadSelect.default({ mode: "single", min: 0, max: null }),
4578
+ /** `true` = peminta akan mengajukan pertanyaan lanjutan; alurnya dibiarkan terbuka sampai submit. */
4579
+ chain: external_exports.boolean().default(false),
4580
+ /** Lanjutkan rantai yang sudah ada. Alur tertutup ditolak 409 — bukan dibuatkan diam-diam. */
4581
+ flowId: external_exports.string().min(1).nullish().default(null)
4582
+ });
4583
+ zLeadOverride = external_exports.object({
4584
+ answer: external_exports.string().min(1).max(8e3),
4585
+ reason: external_exports.string().max(8e3).default(""),
4586
+ /**
4587
+ * SPEC-485 · centang operator sebagai DATA, bukan prosa. Dipetakan ke opsi baris yang ditimpa,
4588
+ * lalu ikut diketikkan ke pane — jadi manusia mencentang di dashboard dan kotaknya benar-benar
4589
+ * tercentang di dialog sesi.
4590
+ */
4591
+ choices: external_exports.array(external_exports.string().max(2e3)).max(20).default([])
4592
+ });
4593
+ LEAD_AUTHORITY_MODES = ["auto", "ask", "off"];
4594
+ zLeadAuthorityMode = external_exports.enum(LEAD_AUTHORITY_MODES);
4595
+ zLeadProject = external_exports.object({
4596
+ // zod 3: record berkunci enum diinfer `Partial<Record<…>>` — tindakan yang tak disebut memakai default.
4597
+ authority: external_exports.record(zLeadAction, zLeadAuthorityMode).default({}),
4598
+ targetBranch: external_exports.string().min(1).default("main"),
4599
+ /** Kosong = integrasi lead SELALU minta izin (server tak bisa membuktikan test hijau). */
4600
+ verifyCommand: external_exports.string().default(""),
4601
+ quotaStopPct: external_exports.number().int().min(1).max(100).default(85),
4602
+ rampStep: external_exports.number().int().min(1).max(16).default(1),
4603
+ rampProbeMin: external_exports.number().int().min(1).max(240).default(10),
4604
+ failStreak: external_exports.number().int().min(1).max(20).default(2),
4605
+ answerSlaSec: external_exports.number().int().min(30).max(3600).default(300)
4606
+ });
4607
+ zLeadAct = external_exports.object({
4608
+ eventId: external_exports.string().min(1),
4609
+ verdict: zLeadVerdict,
4610
+ evidence: external_exports.array(external_exports.string().max(500)).max(50).default([])
4611
+ });
4612
+ }
4613
+ });
4614
+
4494
4615
  // ../shared/src/relay.ts
4495
4616
  function utf8Bytes(s2) {
4496
4617
  let n2 = 0;
@@ -4887,6 +5008,7 @@ var init_entities = __esm({
4887
5008
  init_telegram();
4888
5009
  init_agent_engine();
4889
5010
  init_auto_merge();
5011
+ init_lead();
4890
5012
  init_method_catalog();
4891
5013
  init_relay();
4892
5014
  init_logs();
@@ -5064,26 +5186,12 @@ var init_entities = __esm({
5064
5186
  // rem per project (AC-15/US-4)
5065
5187
  everyMin: external_exports.number().int().min(1).max(1440).default(5),
5066
5188
  // denyut proaktif (OQ-2)
5067
- // SPEC-432 · batas waktu satu putusan (AC-4/35). Default 120 dtk MELAWAN prompt lead sendiri
5068
- // ("kumpulkan bukti dulu: SoT, ADR, plan, kode, riwayat git") dan kalah: satu keputusan `order`
5069
- // nyata terukur 306 dtk pada claude-opus-5 · xhigh, jadi 7/7 baris jejak operator berstatus
5070
- // `gagal`. 600 dtk memberi kelonggaran di atas ongkos terukur itu; yang memangkas ongkosnya
5071
- // sendiri adalah paragraf anggaran waktu di `leadPrompt` (306 dtk → 101 dtk).
5072
- timeoutSec: external_exports.number().int().min(10).max(900).default(600),
5189
+ // ADR-0228 · `timeoutSec` (batas satu proses brain), `maxConcurrent` & `queueWaitSec` (gerbang
5190
+ // konkurensi brain, SPEC-479) DICABUT bersama brain sekali-panggil. Tenggatnya kini milik inbox:
5191
+ // `projects[<id>].answerSlaSec` untuk pertanyaan, `deliverTimeoutSec` untuk pengantaran. Baris
5192
+ // Setting lama yang masih membawa kunci itu tetap ter-parse — zod membuang kunci tak dikenal.
5073
5193
  // AC-11 / OQ-10 · berapa jawaban otomatis berturut-turut untuk SATU sesi sebelum lead berhenti.
5074
5194
  maxAutoAnswers: external_exports.number().int().min(1).max(20).default(3),
5075
- // SPEC-479 (QA) · berapa putusan boleh DISUSUN SEKALIGUS, untuk KETIGA pintu bersama. Sebelum
5076
- // knob ini jawabannya tak pernah dinyatakan, jadi ia jatuh ke bentuk kode tiap pintu: 1 di pintu
5077
- // deteksi (`for`+`await`, terukur `maxInFlight = 1` dengan tangga tunggu linier) dan tak hingga
5078
- // di pintu kontrak (Fastify konkuren, terukur 12 permintaan → 12 proses agen). Default 2 diambil
5079
- // dari mesin tempat keluhan lahir — 8 GB / 8 core yang sudah menanggung sesi pekerja di tmux,
5080
- // sementara satu `claude -p --effort xhigh` adalah runtime Node penuh, bukan panggilan HTTP tipis.
5081
- maxConcurrent: external_exports.number().int().min(1).max(16).default(2),
5082
- // Deadline penerimaan: menunggu slot lebih lama dari ini → penolakan EKSPLISIT yang bisa dicoba
5083
- // ulang (503 + Retry-After di pintu kontrak), bukan gantung. Wajib ada karena Fastify menyetel
5084
- // `requestTimeout: 0` (terukur dari `buildApp()`) — tak ada pihak lain yang akan memutus peminta.
5085
- // 0 = tanpa antrean sama sekali: penuh berarti langsung ditolak.
5086
- queueWaitSec: external_exports.number().int().min(0).max(900).default(120),
5087
5195
  // SPEC-485 · ADR-0102 · umur maksimum satu RANTAI keputusan yang dibiarkan terbuka. Peminta bisa
5088
5196
  // mati di tengah rantai (sesi ditutup, agen crash); tanpa batas ini alurnya `sebagian` selamanya
5089
5197
  // dan tak ada yang tahu apakah ia masih ditunggu. Penyapunya menumpang tick lead yang sudah ada —
@@ -5092,6 +5200,14 @@ var init_entities = __esm({
5092
5200
  // OQ-3 · syarat objektif sebelum lead boleh mengintegrasikan ke branch utama. Default MENYALA:
5093
5201
  // risiko "kode masuk main tanpa mata manusia" diterima sadar, tapi syaratnya tetap terukur.
5094
5202
  requireGreenBeforeIntegrate: external_exports.boolean().default(true),
5203
+ // ADR-0228 · sesi lead. Knob GLOBAL: tenggat antar-jemput event, umur izin, dan kapan pane
5204
+ // lead dirotasi supaya konteksnya tak menumpuk tanpa batas.
5205
+ deliverTimeoutSec: external_exports.number().int().min(60).max(3600).default(600),
5206
+ approvalTtlMin: external_exports.number().int().min(5).max(10080).default(1440),
5207
+ rotateAfterEvents: external_exports.number().int().min(5).max(500).default(40),
5208
+ // Setelan PER PROYEK (wewenang, branch target, perintah verifikasi, jatah). Proyek yang tak
5209
+ // disebut memakai default `zLeadProject` — penggabungnya `lead/project-config.ts`.
5210
+ projects: external_exports.record(external_exports.string(), zLeadProject.partial()).default({}),
5095
5211
  engine: zLeadEngine.default({})
5096
5212
  });
5097
5213
  LEAD_DEFAULTS = zLead.parse({});
@@ -5289,6 +5405,10 @@ var init_agent = __esm({
5289
5405
  // endpoint tulis menumpang prefix status yang dipetakan tanpa melihat method (AC-5).
5290
5406
  "lead:read",
5291
5407
  "lead:write",
5408
+ // ADR-0228 · menyetujui tindakan lead yang menunggu izin (bisa MERGE ke branch target). Akses
5409
+ // `danger`, tak diimplikasikan `lead:write`: pane lead & agen peminta putusan memegang write,
5410
+ // dan mereka tak boleh menyetujui tindakannya sendiri (ADR-0192).
5411
+ "lead:approve",
5292
5412
  // SPEC-450 · ADR-0094 · custom agent. Domain TERSENDIRI, dipetakan MENURUT METHOD: menulis
5293
5413
  // definisi agen mengubah apa yang dilihat SETIAP sesi baru di seluruh workspace, jadi izin
5294
5414
  // baca tak pernah cukup untuk itu (kelas bug SPEC-405).
@@ -6556,108 +6676,6 @@ var init_github = __esm({
6556
6676
  }
6557
6677
  });
6558
6678
 
6559
- // ../shared/src/lead.ts
6560
- var zLeadGate, zLeadKind, zLeadConfidence, zLeadStatus, zLeadFlowStatus, LEAD_ACTIONS, zLeadAction, zLeadChoice, zLeadSelect, zLeadVerdict, zLeadAsk, zLeadOverride;
6561
- var init_lead = __esm({
6562
- "../shared/src/lead.ts"() {
6563
- "use strict";
6564
- init_zod();
6565
- zLeadGate = external_exports.enum(["contract", "detected", "pulse"]);
6566
- zLeadKind = external_exports.enum(["answer", "order", "collision", "quality", "refusal"]);
6567
- zLeadConfidence = external_exports.enum(["tinggi", "sedang", "ragu"]);
6568
- zLeadStatus = external_exports.enum(["berlaku", "ditimpa", "dibatalkan", "gagal"]);
6569
- zLeadFlowStatus = external_exports.enum(["menunggu", "sebagian", "selesai", "dibatalkan"]);
6570
- LEAD_ACTIONS = [
6571
- "none",
6572
- // keputusan murni jawaban; tak ada tindakan menyusul
6573
- "answer-session",
6574
- // ketik jawaban ke pane sesi (pintu deteksi otomatis)
6575
- "start-session",
6576
- // mulai sesi untuk sebuah backlog (lewat antrean scheduler)
6577
- "stop-session",
6578
- // hentikan sesi — worktree DIBIARKAN utuh (AC-32a)
6579
- "resume-session",
6580
- // lanjutkan sesi terputus (jalur ADR-0084)
6581
- "restart-session",
6582
- // ulangi pekerjaan dari awal
6583
- "order-queue",
6584
- // tata urutan antrean yang sudah ada (ADR-0072) — bukan antrean kedua
6585
- "hold-work",
6586
- // tunda satu pekerjaan karena tabrakan area kerja
6587
- "push-branch",
6588
- // dorong perubahan ke branch
6589
- "integrate-main",
6590
- // integrasikan ke branch utama (risiko yang diterima sadar, PRD §Risiko)
6591
- "run-migration"
6592
- // jalankan migration pada basis data LOKAL operator
6593
- ];
6594
- zLeadAction = external_exports.enum(LEAD_ACTIONS);
6595
- zLeadChoice = external_exports.object({ index: external_exports.number().int().positive(), option: external_exports.string() });
6596
- zLeadSelect = external_exports.object({
6597
- mode: external_exports.enum(["single", "multi"]).default("single"),
6598
- min: external_exports.number().int().min(0).max(20).default(0),
6599
- max: external_exports.number().int().min(1).max(20).nullable().default(null)
6600
- });
6601
- zLeadVerdict = external_exports.object({
6602
- decision: external_exports.string().min(1),
6603
- reason: external_exports.string().min(1),
6604
- refs: external_exports.array(external_exports.string()).default([]),
6605
- confidence: zLeadConfidence.default("sedang"),
6606
- // Sengaja `string`, BUKAN `zLeadAction`: tindakan di luar allowlist harus bisa MASUK supaya
6607
- // server menolaknya secara sadar — mencatat penolakan & menotifikasi operator (AC-33). Kalau
6608
- // enum yang menyaring di sini, permintaan "deploy ke produksi" hanya akan tampak sebagai
6609
- // keluaran rusak, dan justru peristiwa paling layak dilaporkan itulah yang hilang dari jejak.
6610
- action: external_exports.string().default("none"),
6611
- /**
6612
- * SPEC-480 · opsi yang dipilih — nomor ATAU label. Sengaja `string`, bukan enum/number: pilihan
6613
- * di luar daftar harus BISA MASUK supaya server menolaknya secara sadar & mencatatnya, alasan
6614
- * yang sama persis dengan `action` di atas.
6615
- */
6616
- choice: external_exports.string().default(""),
6617
- /**
6618
- * SPEC-485 · pilihan JAMAK. `string[]` dengan alasan yang sama persis seperti `choice` & `action`
6619
- * di atas: pilihan di luar daftar harus BISA MASUK supaya server menolaknya secara sadar dan
6620
- * mencatatnya. Kosong + `choice` terisi dibaca sebagai satu pilihan — keluaran agen berbentuk
6621
- * ADR-0098 harus tetap terpakai, dan menuntut field baru berarti setiap agen lama mendadak
6622
- * "tak memilih apa pun".
6623
- */
6624
- choices: external_exports.array(external_exports.string().max(2e3)).max(20).default([]),
6625
- /**
6626
- * SPEC-480 · apa yang KURANG bila konteksnya memang tak cukup untuk memutuskan. Bukan pengganti
6627
- * `confidence: "ragu"` (bukti tipis, jawabannya tetap ada) melainkan untuk fakta konkret yang
6628
- * tak ada di repo maupun konteks. Terisi ⇒ server memaksa `ragu` ⇒ operator dinotifikasi.
6629
- */
6630
- missing: external_exports.array(external_exports.string().max(200)).max(10).default([]),
6631
- /** Teks yang benar-benar diketik ke pane sesi (pintu deteksi otomatis). */
6632
- reply: external_exports.string().default("")
6633
- });
6634
- zLeadAsk = external_exports.object({
6635
- projectId: external_exports.string().min(1),
6636
- specId: external_exports.string().nullish(),
6637
- sessionId: external_exports.string().nullish(),
6638
- question: external_exports.string().min(1).max(8e3),
6639
- options: external_exports.array(external_exports.string().max(2e3)).max(20).default([]),
6640
- context: external_exports.string().max(2e4).default(""),
6641
- /** SPEC-485 · bentuk pilihan yang diminta peminta. Default = perilaku hari ini (single). */
6642
- select: zLeadSelect.default({ mode: "single", min: 0, max: null }),
6643
- /** `true` = peminta akan mengajukan pertanyaan lanjutan; alurnya dibiarkan terbuka sampai submit. */
6644
- chain: external_exports.boolean().default(false),
6645
- /** Lanjutkan rantai yang sudah ada. Alur tertutup ditolak 409 — bukan dibuatkan diam-diam. */
6646
- flowId: external_exports.string().min(1).nullish().default(null)
6647
- });
6648
- zLeadOverride = external_exports.object({
6649
- answer: external_exports.string().min(1).max(8e3),
6650
- reason: external_exports.string().max(8e3).default(""),
6651
- /**
6652
- * SPEC-485 · centang operator sebagai DATA, bukan prosa. Dipetakan ke opsi baris yang ditimpa,
6653
- * lalu ikut diketikkan ke pane — jadi manusia mencentang di dashboard dan kotaknya benar-benar
6654
- * tercentang di dialog sesi.
6655
- */
6656
- choices: external_exports.array(external_exports.string().max(2e3)).max(20).default([])
6657
- });
6658
- }
6659
- });
6660
-
6661
6679
  // ../shared/src/handled-by.ts
6662
6680
  var zHandledByEntry, HANDLED_BY_MAX, zHandledBy, zHandledByView;
6663
6681
  var init_handled_by = __esm({
@@ -6783,7 +6801,11 @@ var init_session_admission = __esm({
6783
6801
  });
6784
6802
 
6785
6803
  // ../shared/src/dto.ts
6786
- var zIssueDeviceToken, zSessionResult, zSessionHistory, zCreateProject, zProjectId, zRenameProject, zUpdateProject, zCreateSpec, zPatchSpec, zChangeSpecSource, zMarkSpecDone, zMarkSpecReviewed, zIntegrate, zSessionSummary, zProjectView, zSchedulerQueueItem, zSchedulerSourceView, zSchedulerSessionView, zSchedulerQueueCounts, zSchedulerState, zLeadDecisionView, zLeadFlowView, zLeadAnswer, zLeadProjectStatus, zLeadStatusView, zFlow, zPrdBrief, zPrdDoc, zBreakdownItem, zBreakdownDoc, zBatchCreateSpec, zEscalationTarget, zEscalationPrefill, zAuditEscalation, zAuditEscalationView, zTerminalSession, zDocFileContent, zDocIndexCat, zDocIndex, HOST_RE, USER_RE, zCreateVps, zPatchVps, USER_ROLES, zUserRole, zLogin, zSignup, zSetup, zSetupApply, zChangePassword, zAdminPasswordReset, zCreateClientAccount, zUpdateClientAccount, zVpsCheck, zMarkNa, zMarkNaBulk, zAttest, zRemediate, PROVISION_PROFILES, COMPONENT_IDS, zProvision, UPDATE_RESTART_EXIT, CONFIG_RESTART_EXIT, MANUAL_RESTART_EXIT, zUpdateApplyBody, MAX_SUBS, zSubPage, zSubLimit, zTopicParams, zEventsClientMsg, zTicketView, zTicketAttachmentView, zSpecAttachmentView, zTicketDetail, zTicketEditInput, zHelpInfo, zPublicTicketStatus, zCronExpr, zCreateCron, zPatchCron, zSchedulerCron, zSchedulerCronRun;
6804
+ function parseExitIntent(text) {
6805
+ const m = /^(\d+) (\d+)\s*$/.exec(text ?? "");
6806
+ return m ? { pid: Number(m[1]), code: Number(m[2]) } : null;
6807
+ }
6808
+ var zIssueDeviceToken, zSessionResult, zSessionHistory, zCreateProject, zProjectId, zRenameProject, zUpdateProject, zCreateSpec, zPatchSpec, zChangeSpecSource, zMarkSpecDone, zMarkSpecReviewed, zIntegrate, zSessionSummary, zProjectView, zSchedulerQueueItem, zSchedulerSourceView, zSchedulerSessionView, zSchedulerQueueCounts, zSchedulerState, zLeadDecisionView, zLeadFlowView, zLeadAnswer, zLeadProjectStatus, zLeadStatusView, zFlow, zPrdBrief, zPrdDoc, zBreakdownItem, zBreakdownDoc, zBatchCreateSpec, zEscalationTarget, zEscalationPrefill, zAuditEscalation, zAuditEscalationView, zTerminalSession, zDocFileContent, zDocIndexCat, zDocIndex, HOST_RE, USER_RE, zCreateVps, zPatchVps, USER_ROLES, zUserRole, zLogin, zSignup, zSetup, zSetupApply, zChangePassword, zAdminPasswordReset, zCreateClientAccount, zUpdateClientAccount, zVpsCheck, zMarkNa, zMarkNaBulk, zAttest, zRemediate, PROVISION_PROFILES, COMPONENT_IDS, zProvision, UPDATE_RESTART_EXIT, CONFIG_RESTART_EXIT, MANUAL_RESTART_EXIT, EXIT_INTENT_FILE, zUpdateApplyBody, MAX_SUBS, zSubPage, zSubLimit, zTopicParams, zEventsClientMsg, zTicketView, zTicketAttachmentView, zSpecAttachmentView, zTicketDetail, zTicketEditInput, zHelpInfo, zPublicTicketStatus, zCronExpr, zCreateCron, zPatchCron, zSchedulerCron, zSchedulerCronRun;
6787
6809
  var init_dto = __esm({
6788
6810
  "../shared/src/dto.ts"() {
6789
6811
  "use strict";
@@ -7082,7 +7104,12 @@ var init_dto = __esm({
7082
7104
  inFlight: external_exports.number(),
7083
7105
  queued: external_exports.number(),
7084
7106
  capacity: external_exports.number()
7085
- }).default({ inFlight: 0, queued: 0, capacity: 1 })
7107
+ }).default({ inFlight: 0, queued: 0, capacity: 1 }),
7108
+ // ADR-0228 · sesi lead: inbox terbuka/terlambat, izin yang menunggu, dan pane lead per proyek.
7109
+ // `.default([])` supaya klien lama tetap mem-parse respons ini.
7110
+ inbox: external_exports.array(external_exports.custom()).default([]),
7111
+ approvals: external_exports.array(external_exports.custom()).default([]),
7112
+ sessions: external_exports.array(external_exports.custom()).default([])
7086
7113
  });
7087
7114
  zFlow = external_exports.enum(["feature", "qa", "scaffold", "reverse", "prd", "audit", "breakdown", "goal", "no_effort"]);
7088
7115
  zPrdBrief = external_exports.object({
@@ -7317,6 +7344,7 @@ var init_dto = __esm({
7317
7344
  UPDATE_RESTART_EXIT = 75;
7318
7345
  CONFIG_RESTART_EXIT = 76;
7319
7346
  MANUAL_RESTART_EXIT = 77;
7347
+ EXIT_INTENT_FILE = "exit-intent";
7320
7348
  zUpdateApplyBody = external_exports.object({ confirm: external_exports.boolean().optional() });
7321
7349
  MAX_SUBS = 16;
7322
7350
  zSubPage = external_exports.number().int().min(1).max(1e4);
@@ -7735,6 +7763,10 @@ var init_api = __esm({
7735
7763
  leadFlows: `${API}/lead/flows`,
7736
7764
  leadFlowSubmit: (id) => `${API}/lead/flows/${encodeURIComponent(id)}/submit`,
7737
7765
  leadFlowCancel: (id) => `${API}/lead/flows/${encodeURIComponent(id)}/cancel`,
7766
+ // ADR-0228 · sesi lead: inbox, antrean izin, dan pane lead per proyek.
7767
+ leadInboxTake: (id) => `${API}/lead/inbox/${encodeURIComponent(id)}/take`,
7768
+ leadApprovalDecide: (id) => `${API}/lead/approvals/${encodeURIComponent(id)}/decide`,
7769
+ leadSession: (projectId, op) => `${API}/lead/sessions/${encodeURIComponent(projectId)}/${op}`,
7738
7770
  // SPEC-450 · ADR-0094 · katalog custom agent. `?projectId=` → himpunan EFEKTIF (global+project).
7739
7771
  customAgents: `${API}/custom-agents`,
7740
7772
  customAgentMetrics: `${API}/custom-agents/metrics`,
@@ -7929,7 +7961,10 @@ var init_session_kind = __esm({
7929
7961
  "shell",
7930
7962
  "worktree",
7931
7963
  "terminal",
7932
- "telegram"
7964
+ "telegram",
7965
+ // ADR-0228 · pane hanoman-lead per proyek (`lead-<proyek>`). Tak restartable: ia dilahirkan
7966
+ // ulang oleh server sendiri, bukan oleh tombol "Mulai lagi".
7967
+ "lead"
7933
7968
  ];
7934
7969
  zSessionKind = external_exports.enum(SESSION_KINDS);
7935
7970
  }
@@ -12005,7 +12040,7 @@ var init_support = __esm({
12005
12040
  });
12006
12041
 
12007
12042
  // ../shared/src/mcp-catalog/lead.ts
12008
- var CORE6, MORE5, LEAD_TOOLS;
12043
+ var CORE6, MORE5, listParams, listQuery, SESSION, LEAD_TOOLS;
12009
12044
  var init_lead2 = __esm({
12010
12045
  "../shared/src/mcp-catalog/lead.ts"() {
12011
12046
  "use strict";
@@ -12043,7 +12078,7 @@ var init_lead2 = __esm({
12043
12078
  {
12044
12079
  name: "hanoman_lead_ask",
12045
12080
  title: "Minta putusan hanoman-lead",
12046
- description: "Minta putusan ke hanoman-lead saat menemui persimpangan yang biasanya butuh manusia. Jawabannya terbaca mesin (`decision`, `reason`, `refs`, `confidence`, `action`, `choices`) dan `refs` hanya memuat rujukan yang benar-benar ada di repo. Bila opsinya TIDAK saling eksklusif, set `multi: true` \u2014 balasannya memuat `choices` (daftar), bukan hanya `choice`. Panggilan ini melahirkan jejak permanen dan putusannya bisa menggerakkan sesi \u2014 pakai hanya saat memang buntu. 409 = lead tak aktif atau proyek belum opt-in: kembali ke perilaku biasa, berhenti dan tunggu manusia; 400 = bentuk `multi`/`minChoices`/`maxChoices` mustahil dipenuhi oleh daftar opsi yang kamu kirim.",
12081
+ description: "Minta putusan ke hanoman-lead saat menemui persimpangan yang biasanya butuh manusia. Pertanyaanmu masuk inbox sesi lead proyek itu dan dijawab pane lead setelah ia memeriksa; panggilan ini MENUNGGU sampai dijawab atau tenggat `answerSlaSec` proyek (default 300 dtk). Jawabannya terbaca mesin (`decision`, `reason`, `refs`, `confidence`, `action`, `choices`) dan `refs` hanya memuat rujukan yang benar-benar ada di repo. Bila opsinya TIDAK saling eksklusif, set `multi: true` \u2014 balasannya memuat `choices` (daftar), bukan hanya `choice`. Panggilan ini melahirkan jejak permanen dan putusannya bisa menggerakkan sesi \u2014 pakai hanya saat memang buntu. 409 = lead tak aktif atau proyek belum opt-in: berhenti dan tunggu manusia; 504 = sesi lead tak menjawab dalam tenggat (diserahkan ke manusia); 400 = bentuk `multi`/`minChoices`/`maxChoices` mustahil dipenuhi oleh daftar opsi yang kamu kirim.",
12047
12082
  inputSchema: obj({
12048
12083
  properties: {
12049
12084
  project: str("Id proyek."),
@@ -12208,7 +12243,87 @@ var init_lead2 = __esm({
12208
12243
  shape: (raw) => raw
12209
12244
  }
12210
12245
  ];
12211
- LEAD_TOOLS = [...CORE6, ...MORE5];
12246
+ listParams = {
12247
+ project: str("Saring menurut id proyek."),
12248
+ status: str("Saring menurut status."),
12249
+ page: int("Halaman, mulai 1.", { minimum: 1 }),
12250
+ limit: int("Item per halaman.", { minimum: 1, maximum: 100 })
12251
+ };
12252
+ listQuery = (a) => query({ projectId: s(a.project), status: s(a.status), page: n(a.page)?.toString(), limit: n(a.limit)?.toString() });
12253
+ SESSION = [
12254
+ {
12255
+ name: "hanoman_lead_inbox",
12256
+ title: "Inbox sesi lead",
12257
+ description: "Event yang diantarkan ke sesi hanoman-lead, terbaru dulu: pertanyaan sesi pekerja (`ask`), sesi selesai/gagal (`session-ended`), pilihan backlog (`pulse`), penolakan manusia (`rejected`), integrasi gagal (`integrate-failed`). `status`: `open` (menunggu diantar), `delivered` (sudah di pane lead), `done`, `late` (lewat tenggat \u2192 manusia), `human` (diambil manusia).",
12258
+ inputSchema: obj({ properties: listParams }),
12259
+ mode: "read",
12260
+ capability: "lead:read",
12261
+ samplePath: "/lead/inbox",
12262
+ sampleMethod: "GET",
12263
+ build: (a) => ({ method: "GET", path: "/lead/inbox", query: listQuery(a) }),
12264
+ shape: (raw) => raw
12265
+ },
12266
+ {
12267
+ name: "hanoman_lead_inbox_take",
12268
+ title: "Ambil alih event lead",
12269
+ description: "Mengambil satu event inbox dari sesi lead supaya MANUSIA yang memutuskannya. Jawaban lead yang datang sesudahnya ditolak server. Event yang sudah ditutup menjawab 409.",
12270
+ inputSchema: obj({ properties: { event: str("Id event, dari hanoman_lead_inbox.") }, required: ["event"] }),
12271
+ mode: "write",
12272
+ capability: "lead:write",
12273
+ samplePath: "/lead/inbox/e1/take",
12274
+ sampleMethod: "POST",
12275
+ build: (a) => ({ method: "POST", path: `/lead/inbox/${enc(String(a.event))}/take` }),
12276
+ shape: (raw) => raw
12277
+ },
12278
+ {
12279
+ name: "hanoman_lead_approvals_list",
12280
+ title: "Tindakan lead yang menunggu izin",
12281
+ description: "Tindakan hanoman-lead yang ditahan gerbang server menunggu izin manusia (mis. merge ke branch target, jawaban yang menyentuh produksi/kredensial). Tiap kartu membawa `reason` lead, `evidence` yang ia periksa, dan `gateReport` dari server. `status`: `pending`, `approved`, `rejected`, `expired` (kedaluwarsa = ditolak).",
12282
+ inputSchema: obj({ properties: listParams }),
12283
+ mode: "read",
12284
+ capability: "lead:read",
12285
+ samplePath: "/lead/approvals",
12286
+ sampleMethod: "GET",
12287
+ build: (a) => ({ method: "GET", path: "/lead/approvals", query: listQuery(a) }),
12288
+ shape: (raw) => raw
12289
+ },
12290
+ {
12291
+ name: "hanoman_lead_approval_decide",
12292
+ title: "Setujui/tolak tindakan lead (BERBAHAYA)",
12293
+ description: "BERBAHAYA \u2014 menyetujui atau menolak tindakan hanoman-lead yang menunggu izin. Menyetujui MENJALANKAN tindakan itu apa adanya, termasuk merge branch sesi ke branch target (server tetap menjalankan verifikasinya lebih dulu). Baca dulu kartunya lewat hanoman_lead_approvals_list. Penolakan dikirim balik ke lead beserta `note`. Izin yang sudah diputuskan menjawab 409. Butuh capability `lead:approve`. Hanya muncul saat tingkat `--danger` menyala.",
12294
+ inputSchema: obj({
12295
+ properties: {
12296
+ approval: str("Id izin, dari hanoman_lead_approvals_list."),
12297
+ approve: bool("`true` = setujui, `false` = tolak."),
12298
+ note: str("Opsional. Catatan untuk lead, maksimum 2000 karakter.")
12299
+ },
12300
+ required: ["approval", "approve"]
12301
+ }),
12302
+ mode: "danger",
12303
+ capability: "lead:approve",
12304
+ samplePath: "/lead/approvals/a1/decide",
12305
+ sampleMethod: "POST",
12306
+ build: (a) => ({
12307
+ method: "POST",
12308
+ path: `/lead/approvals/${enc(String(a.approval))}/decide`,
12309
+ body: { approve: a.approve === true, ...s(a.note) ? { note: a.note } : {} }
12310
+ }),
12311
+ shape: (raw) => raw
12312
+ },
12313
+ ...["start", "stop", "rotate"].map((op) => ({
12314
+ name: `hanoman_lead_session_${op}`,
12315
+ title: op === "start" ? "Mulai sesi lead (BERBAHAYA)" : op === "stop" ? "Hentikan sesi lead (BERBAHAYA)" : "Rotasi sesi lead (BERBAHAYA)",
12316
+ description: op === "start" ? "BERBAHAYA \u2014 melahirkan pane `lead-<proyek>` (agen hidup yang menjawab pertanyaan sesi pekerja, memilih backlog, dan memutuskan nasib sesi yang selesai). 409 = lead tak aktif/proyek belum opt-in atau belum di-bind, atau pane sedang backoff sesudah gagal lahir. Hanya muncul saat tingkat `--danger` menyala." : op === "stop" ? "BERBAHAYA \u2014 menghentikan pane lead proyek. Pertanyaan sesi pekerja jatuh ke manusia sampai pane lahir lagi (tick lead melahirkannya kembali bila inbox berisi dan lead tak dijeda \u2014 jeda proyeknya untuk benar-benar menghentikan). Hanya muncul saat tingkat `--danger` menyala." : "BERBAHAYA \u2014 menghentikan pane lead lalu melahirkannya lagi dengan konteks bersih (state ada di DB). Event yang sedang diproses dikembalikan ke inbox oleh sapuan. Hanya muncul saat tingkat `--danger` menyala.",
12317
+ inputSchema: obj({ properties: { project: str("Id proyek.") }, required: ["project"] }),
12318
+ mode: "danger",
12319
+ capability: "lead:write",
12320
+ samplePath: `/lead/sessions/p1/${op}`,
12321
+ sampleMethod: "POST",
12322
+ build: (a) => ({ method: "POST", path: `/lead/sessions/${enc(String(a.project))}/${op}` }),
12323
+ shape: (raw) => raw
12324
+ }))
12325
+ ];
12326
+ LEAD_TOOLS = [...CORE6, ...MORE5, ...SESSION];
12212
12327
  }
12213
12328
  });
12214
12329
 
@@ -13465,13 +13580,13 @@ var init_transfer = __esm({
13465
13580
  });
13466
13581
 
13467
13582
  // ../shared/src/mcp-catalog/gates.ts
13468
- var SESSION, CRON, intArray, DECIDE_NOTE, DIALOG, SPAWN_NOTE, CRON_FIELDS, CRONS, GATES_TOOLS;
13583
+ var SESSION2, CRON, intArray, DECIDE_NOTE, DIALOG, SPAWN_NOTE, CRON_FIELDS, CRONS, GATES_TOOLS;
13469
13584
  var init_gates = __esm({
13470
13585
  "../shared/src/mcp-catalog/gates.ts"() {
13471
13586
  "use strict";
13472
13587
  init_mcp_schema();
13473
13588
  init_helpers();
13474
- SESSION = str("Id sesi hidup, dari hanoman_sessions_list.");
13589
+ SESSION2 = str("Id sesi hidup, dari hanoman_sessions_list.");
13475
13590
  CRON = str("Id cron, dari hanoman_scheduler_crons_list.");
13476
13591
  intArray = (description) => ({ type: "array", description, items: { type: "integer", minimum: 1 }, maxItems: 32 });
13477
13592
  DECIDE_NOTE = "Menuntut capability `sessions:decide` (BUKAN `sessions:write`). Server menolak 403 bila token ini yang meluncurkan sesi tersebut, atau bila panggilan datang dari dalam sebuah sesi hanoman \u2014 agen tak boleh memutuskan pertanyaan sesinya sendiri.";
@@ -13480,7 +13595,7 @@ var init_gates = __esm({
13480
13595
  name: "hanoman_session_dialog_get",
13481
13596
  title: "Baca dialog pertanyaan sesi (BERBAHAYA)",
13482
13597
  description: `BERBAHAYA \u2014 membaca dialog AskUserQuestion yang sedang tampil di pane sebuah sesi: judul, opsi bernomor, dan \`screenHash\` yang wajib dikirim balik saat menjawab. Hasil kosong (\`dialog: null\`) berarti layar bukan dialog yang bisa dijawab (atau sesinya sudah berakhir). Dialog trust & prompt izin sengaja TIDAK pernah muncul di sini. ${DECIDE_NOTE} Hanya muncul saat tingkat \`--danger\` menyala.`,
13483
- inputSchema: obj({ properties: { session: SESSION }, required: ["session"] }),
13598
+ inputSchema: obj({ properties: { session: SESSION2 }, required: ["session"] }),
13484
13599
  mode: "danger",
13485
13600
  capability: "sessions:decide",
13486
13601
  samplePath: "/terminal/sessions/s1/dialog",
@@ -13494,7 +13609,7 @@ var init_gates = __esm({
13494
13609
  description: `BERBAHAYA \u2014 mengetikkan jawaban ke dialog AskUserQuestion sebuah sesi; sesi langsung bergerak dan tak ada undo. Ambil \`screenHash\` dari hanoman_session_dialog_get tepat sebelumnya: 409 \`stale\` berarti layar sudah berganti \u2014 baca ulang, jangan menebak. 409 \`deciding\` berarti hanoman-lead sedang menyusun keputusan untuk sesi itu (jangan dipaksa; rebut dulu lewat hanoman_session_dialog_takeover bila memang perlu). 409 \`answering\` berarti jawaban lain sedang dikirim. Isi TEPAT SATU bentuk: \`choice\` (single-select), \`choices\` (multi-select; [] = kosongkan semua lalu submit), atau \`text\` (prosa untuk kolom bebas/catatan; boleh menyertai \`choices\`). ${DECIDE_NOTE} Ini gerbang "manusia yang memutuskan": jawablah hanya atas instruksi manusia. Hanya muncul saat tingkat \`--danger\` menyala.`,
13495
13610
  inputSchema: obj({
13496
13611
  properties: {
13497
- session: SESSION,
13612
+ session: SESSION2,
13498
13613
  screenHash: str("Nilai `screenHash` dari hanoman_session_dialog_get untuk layar yang sedang dijawab."),
13499
13614
  choice: int("Nomor baris opsi (`options[].n`) untuk dialog single-select.", { minimum: 1 }),
13500
13615
  choices: intArray("Nomor baris yang dicentang untuk dialog multi-select; [] mengosongkan semuanya. Tak boleh bersama `choice`."),
@@ -13519,7 +13634,7 @@ var init_gates = __esm({
13519
13634
  name: "hanoman_session_dialog_takeover",
13520
13635
  title: "Rebut sesi dari hanoman-lead (BERBAHAYA)",
13521
13636
  description: `BERBAHAYA \u2014 merebut sesi yang pertanyaannya sedang ditangani hanoman-lead: lead dibatalkan dan hanya pemegang \`sessions:decide\` yang bisa menjawab sesudahnya. 404 berarti tak ada pertanyaan hidup untuk sesi itu; 409 \`answering\` berarti lead sudah terlanjur mengetik jawabannya ke pane (rebutan kalah). ${DECIDE_NOTE} Hanya muncul saat tingkat \`--danger\` menyala.`,
13522
- inputSchema: obj({ properties: { session: SESSION }, required: ["session"] }),
13637
+ inputSchema: obj({ properties: { session: SESSION2 }, required: ["session"] }),
13523
13638
  mode: "danger",
13524
13639
  capability: "sessions:decide",
13525
13640
  samplePath: "/terminal/sessions/s1/dialog/takeover",
@@ -16187,13 +16302,14 @@ __export(start_exports, {
16187
16302
  parseStartArgs: () => parseStartArgs,
16188
16303
  planSupervisorStep: () => planSupervisorStep,
16189
16304
  prismaClientUsable: () => prismaClientUsable,
16305
+ resolveChildExit: () => resolveChildExit,
16190
16306
  serverEnv: () => serverEnv,
16191
16307
  spawnEnv: () => spawnEnv
16192
16308
  });
16193
16309
  import { spawn, execFileSync as execFileSync3 } from "node:child_process";
16194
16310
  import { createRequire as createRequire2 } from "node:module";
16195
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, chmodSync as chmodSync3 } from "node:fs";
16196
- import { dirname as dirname4, resolve as resolvePath } from "node:path";
16311
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, chmodSync as chmodSync3, readFileSync as readFileSync3, rmSync } from "node:fs";
16312
+ import { dirname as dirname4, join as join6, resolve as resolvePath } from "node:path";
16197
16313
  import { fileURLToPath } from "node:url";
16198
16314
  function parseStartArgs(argv) {
16199
16315
  const out = { port: null, host: null, db: null, migrate: true };
@@ -16318,6 +16434,23 @@ function installLatest() {
16318
16434
  function spawnEnv(fileEnv, processEnv, server) {
16319
16435
  return { ...fileEnv, ...processEnv, ...server };
16320
16436
  }
16437
+ function resolveChildExit(exit, intentText) {
16438
+ const intent = parseExitIntent(intentText);
16439
+ if (exit.signal === "SIGKILL" && intent && exit.pid !== void 0 && intent.pid === exit.pid) {
16440
+ return { code: intent.code, forced: true };
16441
+ }
16442
+ return { code: exit.code ?? 0, forced: false };
16443
+ }
16444
+ function takeExitIntent(home) {
16445
+ const path = join6(home, EXIT_INTENT_FILE);
16446
+ try {
16447
+ const text = readFileSync3(path, "utf8");
16448
+ rmSync(path, { force: true });
16449
+ return text;
16450
+ } catch {
16451
+ return null;
16452
+ }
16453
+ }
16321
16454
  function runServer(serverJs, env, fileEnv) {
16322
16455
  const child = spawn(process.execPath, [serverJs], {
16323
16456
  stdio: "inherit",
@@ -16325,9 +16458,9 @@ function runServer(serverJs, env, fileEnv) {
16325
16458
  });
16326
16459
  const handlers = ["SIGINT", "SIGTERM"].map((sig) => [sig, () => child.kill(sig)]);
16327
16460
  for (const [sig, h] of handlers) process.on(sig, h);
16328
- return new Promise((res) => child.on("exit", (code) => {
16461
+ return new Promise((res) => child.on("exit", (code, signal) => {
16329
16462
  for (const [sig, h] of handlers) process.off(sig, h);
16330
- res(code ?? 0);
16463
+ res({ code, signal, pid: child.pid });
16331
16464
  }));
16332
16465
  }
16333
16466
  async function start(argv, ctx) {
@@ -16386,7 +16519,10 @@ ${hint}
16386
16519
  let restartsUsed = 0;
16387
16520
  let configRestartsUsed = 0;
16388
16521
  for (; ; ) {
16389
- const code = await runServer(layout.server, env, readConfigEnv(home));
16522
+ const exit = await runServer(layout.server, env, readConfigEnv(home));
16523
+ const { code, forced } = resolveChildExit(exit, takeExitIntent(home));
16524
+ if (forced) ctx.stderr(`hanoman: server macet saat keluar \u2014 dihentikan watchdog; dilanjutkan sebagai exit ${code} (ADR-0227)
16525
+ `);
16390
16526
  const step = planSupervisorStep(code, restartsUsed, configRestartsUsed);
16391
16527
  if (step.action === "exit") {
16392
16528
  if (code === UPDATE_RESTART_EXIT) {
@@ -37833,7 +37969,7 @@ __export(mcp_exports, {
37833
37969
  agentTokenPath: () => agentTokenPath,
37834
37970
  default: () => mcp
37835
37971
  });
37836
- import { readFileSync as readFileSync3 } from "node:fs";
37972
+ import { readFileSync as readFileSync4 } from "node:fs";
37837
37973
  import { join as join8 } from "node:path";
37838
37974
  async function mcp(argv, ctx) {
37839
37975
  const cfg = resolveMcpConfig(argv, ctx.env, () => tokenFile(ctx.env));
@@ -37868,7 +38004,7 @@ var init_mcp2 = __esm({
37868
38004
  agentTokenPath = (env) => join8(resolveHome(env), "agent-token");
37869
38005
  tokenFile = (env) => {
37870
38006
  try {
37871
- return readFileSync3(agentTokenPath(env), "utf8");
38007
+ return readFileSync4(agentTokenPath(env), "utf8");
37872
38008
  } catch {
37873
38009
  return null;
37874
38010
  }
@@ -38209,11 +38345,11 @@ var init_migrate_pg = __esm({
38209
38345
  });
38210
38346
 
38211
38347
  // src/config.ts
38212
- import { readFileSync as readFileSync4, existsSync as existsSync7 } from "node:fs";
38348
+ import { readFileSync as readFileSync5, existsSync as existsSync7 } from "node:fs";
38213
38349
  import { join as join10 } from "node:path";
38214
38350
  function loadConfig(repoRoot) {
38215
38351
  const p3 = join10(repoRoot, "hanoman.config.json");
38216
- const raw = existsSync7(p3) ? JSON.parse(readFileSync4(p3, "utf8")) : {};
38352
+ const raw = existsSync7(p3) ? JSON.parse(readFileSync5(p3, "utf8")) : {};
38217
38353
  return zHanomanConfig.parse(raw);
38218
38354
  }
38219
38355
  var init_config3 = __esm({
@@ -38240,10 +38376,10 @@ var init_repo = __esm({
38240
38376
  });
38241
38377
 
38242
38378
  // src/docs-model.ts
38243
- import { readFileSync as readFileSync5, readdirSync as readdirSync3, statSync as statSync2 } from "node:fs";
38379
+ import { readFileSync as readFileSync6, readdirSync as readdirSync3, statSync as statSync2 } from "node:fs";
38244
38380
  import { join as join12, relative as relative2 } from "node:path";
38245
38381
  function parseIndex(indexPath) {
38246
- const md = readFileSync5(indexPath, "utf8");
38382
+ const md = readFileSync6(indexPath, "utf8");
38247
38383
  const out = /* @__PURE__ */ new Set();
38248
38384
  for (const m of md.matchAll(LINK_RE2)) {
38249
38385
  let t = m[1].trim();
@@ -38291,7 +38427,7 @@ var init_docs_model = __esm({
38291
38427
 
38292
38428
  // src/verify.ts
38293
38429
  import { join as join13 } from "node:path";
38294
- import { existsSync as existsSync8, readFileSync as readFileSync6 } from "node:fs";
38430
+ import { existsSync as existsSync8, readFileSync as readFileSync7 } from "node:fs";
38295
38431
  function scanCoverage(cwd) {
38296
38432
  const { root, docsDir, indexPath } = resolveRepo(cwd);
38297
38433
  const docsRoot = join13(root, docsDir);
@@ -38300,7 +38436,7 @@ function scanCoverage(cwd) {
38300
38436
  const corpus = walkDocs(docsRoot);
38301
38437
  const read = (rel) => {
38302
38438
  try {
38303
- return readFileSync6(join13(docsRoot, rel), "utf8");
38439
+ return readFileSync7(join13(docsRoot, rel), "utf8");
38304
38440
  } catch {
38305
38441
  return null;
38306
38442
  }
@@ -38343,9 +38479,9 @@ var init_docs_scan = __esm({
38343
38479
  });
38344
38480
 
38345
38481
  // src/index-edit.ts
38346
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "node:fs";
38482
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync2 } from "node:fs";
38347
38483
  function addLink(indexPath, relPath, category) {
38348
- let md = readFileSync7(indexPath, "utf8");
38484
+ let md = readFileSync8(indexPath, "utf8");
38349
38485
  if (md.includes(`(${relPath})`)) return;
38350
38486
  const base2 = relPath.split("/").pop().replace(/\.md$/, "");
38351
38487
  const line = `- [${base2}](${relPath})`;
@@ -38375,7 +38511,7 @@ __export(docs_index_exports, {
38375
38511
  });
38376
38512
  import { parseArgs as parseArgs2 } from "node:util";
38377
38513
  import { join as join14 } from "node:path";
38378
- import { existsSync as existsSync9, readFileSync as readFileSync8 } from "node:fs";
38514
+ import { existsSync as existsSync9, readFileSync as readFileSync9 } from "node:fs";
38379
38515
  async function docs_index_default(args, ctx) {
38380
38516
  const { values } = parseArgs2({ args, options: { check: { type: "boolean" }, fix: { type: "boolean" } }, allowPositionals: true });
38381
38517
  const { root, docsDir, indexPath } = resolveRepo(ctx.cwd);
@@ -38383,7 +38519,7 @@ async function docs_index_default(args, ctx) {
38383
38519
  const corpus = walkDocs(docsRoot);
38384
38520
  const read = (rel) => {
38385
38521
  try {
38386
- return readFileSync8(join14(docsRoot, rel), "utf8");
38522
+ return readFileSync9(join14(docsRoot, rel), "utf8");
38387
38523
  } catch {
38388
38524
  return null;
38389
38525
  }
@@ -38548,10 +38684,10 @@ var pack_exports = {};
38548
38684
  __export(pack_exports, {
38549
38685
  default: () => pack
38550
38686
  });
38551
- import { cpSync, existsSync as existsSync10, mkdirSync as mkdirSync3, readFileSync as readFileSync9, rmSync, writeFileSync as writeFileSync3 } from "node:fs";
38687
+ import { cpSync, existsSync as existsSync10, mkdirSync as mkdirSync3, readFileSync as readFileSync10, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "node:fs";
38552
38688
  import { dirname as dirname8, join as join16 } from "node:path";
38553
38689
  function depVersions(repo) {
38554
- const read = (p3) => JSON.parse(readFileSync9(join16(repo, p3), "utf8"));
38690
+ const read = (p3) => JSON.parse(readFileSync10(join16(repo, p3), "utf8"));
38555
38691
  const server = read("server/package.json"), cli = read("cli/package.json");
38556
38692
  const pools = [server.dependencies, server.devDependencies, cli.dependencies, cli.devDependencies];
38557
38693
  const out = {};
@@ -38577,7 +38713,7 @@ async function pack(argv, ctx) {
38577
38713
  }
38578
38714
  const outIdx = argv.indexOf("--out");
38579
38715
  const out = outIdx === -1 ? join16(repo, "dist-npm") : join16(repo, argv[outIdx + 1] ?? "dist-npm");
38580
- const version2 = JSON.parse(readFileSync9(join16(repo, "package.json"), "utf8")).version;
38716
+ const version2 = JSON.parse(readFileSync10(join16(repo, "package.json"), "utf8")).version;
38581
38717
  if (!version2) {
38582
38718
  ctx.stderr("pack: root package.json tanpa field version\n");
38583
38719
  return 1;
@@ -38590,7 +38726,7 @@ async function pack(argv, ctx) {
38590
38726
  `);
38591
38727
  return 1;
38592
38728
  }
38593
- rmSync(out, { recursive: true, force: true });
38729
+ rmSync2(out, { recursive: true, force: true });
38594
38730
  mkdirSync3(join16(out, "bin"), { recursive: true });
38595
38731
  for (const item of copyPlan(repo)) {
38596
38732
  if (!existsSync10(item.from)) {
@@ -38629,13 +38765,13 @@ var verify_packed_exports = {};
38629
38765
  __export(verify_packed_exports, {
38630
38766
  default: () => verifyPacked
38631
38767
  });
38632
- import { readFileSync as readFileSync10 } from "node:fs";
38768
+ import { readFileSync as readFileSync11 } from "node:fs";
38633
38769
  import { join as join17 } from "node:path";
38634
38770
  function verifyPacked(_argv, ctx) {
38635
38771
  const path = join17(process.cwd(), "package.json");
38636
38772
  let pkg;
38637
38773
  try {
38638
- pkg = JSON.parse(readFileSync10(path, "utf8"));
38774
+ pkg = JSON.parse(readFileSync11(path, "utf8"));
38639
38775
  } catch {
38640
38776
  ctx.stderr(`verify: tak bisa membaca ${path}
38641
38777
  `);