hanoman 0.9.30 → 0.9.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build-info.json +3 -3
- package/dist/cli.js +12 -2
- package/dist/server.js +323 -235
- package/package.json +1 -1
- package/web/assets/{DalangHanomanScreen-Fd7_DcSP.js → DalangHanomanScreen-DdsEmtBl.js} +1 -1
- package/web/assets/{DocPreviewModal-SMRN2K98.js → DocPreviewModal-BbOqdfaH.js} +1 -1
- package/web/assets/{DocsWorkspace-pxj0N6QW.js → DocsWorkspace-Cr7AXz9z.js} +1 -1
- package/web/assets/{IdeScreen-Cr9Vqdrc.js → IdeScreen-hdbQW-ui.js} +1 -1
- package/web/assets/{LeadScreen-CQXpJy0y.js → LeadScreen-BWE4mfJI.js} +1 -1
- package/web/assets/{ReviewScreen-B7NNEjxk.js → ReviewScreen-BIkpEspG.js} +1 -1
- package/web/assets/{SchedulerScreen-D3rPdS-F.js → SchedulerScreen-3KVE-d_d.js} +1 -1
- package/web/assets/SettingsScreen-B0-W2TeR.js +62 -0
- package/web/assets/{TeamScreen-BBAsYLpT.js → TeamScreen-DNdt2P1g.js} +1 -1
- package/web/assets/{TerminalScreen-DpVp4h1n.js → TerminalScreen-BjQF48XI.js} +1 -1
- package/web/assets/{TriageScreen-wAPnttRe.js → TriageScreen-CzM99CrG.js} +1 -1
- package/web/assets/{VpsScreen-DeQVx9Jy.js → VpsScreen-SGB_BpbS.js} +1 -1
- package/web/assets/fields-of8G961J.js +1 -0
- package/web/assets/{index-BQw6xQI4.js → index-C5P4ykKO.js} +3 -3
- package/web/assets/{live-CONO0GTu.js → live-DQen7wLm.js} +1 -1
- package/web/assets/{sched-lead-shared-CDR0pOqs.js → sched-lead-shared-C34TvLVv.js} +1 -1
- package/web/assets/{settings-rebase-CpaFw0Tm.js → settings-rebase-DHIWk4mV.js} +1 -1
- package/web/index.html +1 -1
- package/web/assets/SettingsScreen-Bx13PWKQ.js +0 -62
- package/web/assets/fields-qnBoIMR_.js +0 -1
package/dist/server.js
CHANGED
|
@@ -5474,6 +5474,8 @@ var init_entities = __esm({
|
|
|
5474
5474
|
// SPEC-184
|
|
5475
5475
|
notifyDecisionSound: external_exports.enum(NOTIFY_SOUNDS).default("alert"),
|
|
5476
5476
|
// SPEC-184
|
|
5477
|
+
notifyDecisionTelegram: external_exports.boolean().default(true),
|
|
5478
|
+
// ADR-0226 · dorong "menunggu keputusan" ke Telegram
|
|
5477
5479
|
agentAccessEnabled: external_exports.boolean().default(false),
|
|
5478
5480
|
// SPEC-257 · master switch akses AI agent
|
|
5479
5481
|
scheduler: zScheduler.default(SCHEDULER_DEFAULTS),
|
|
@@ -17096,6 +17098,11 @@ var init_verify_scope = __esm({
|
|
|
17096
17098
|
' test-nya memang berjalan, jangan menerima "no test files" sebagai bukti.',
|
|
17097
17099
|
"- Typecheck: hanya paket yang tersentuh (mis. `pnpm --filter ./server typecheck`).",
|
|
17098
17100
|
" JANGAN `pnpm -r typecheck` \u2014 itu menyalakan satu proses tsc per paket sekaligus.",
|
|
17101
|
+
"- Ritme: per task jalankan berkas test yang task itu sentuh saja.",
|
|
17102
|
+
" JANGAN menjalankan ulang berkas test yang sudah hijau selama kode yang diujinya",
|
|
17103
|
+
" tak berubah sejak itu. Typecheck paket tersentuh sesudah task yang mengubah",
|
|
17104
|
+
" tipe/kontrak ekspor, bukan tiap task. Gabungan semua test tersentuh + typecheck",
|
|
17105
|
+
" dijalankan SEKALI di akhir, sebelum melapor.",
|
|
17099
17106
|
"- Lint: hanya berkas yang berubah, bukan seluruh repo.",
|
|
17100
17107
|
"- Build penuh: hanya bila yang kamu ubah memang soal build/bundling.",
|
|
17101
17108
|
"- Boot server + curl / smoke end-to-end: hanya bila task ini menyentuh endpoint atau perilaku",
|
|
@@ -17441,7 +17448,7 @@ Stack: ${project.stack || "\u2014"}`,
|
|
|
17441
17448
|
${cron.prompt}`
|
|
17442
17449
|
].join("\n\n");
|
|
17443
17450
|
}
|
|
17444
|
-
var PIPELINES, WORK_PHASES, DECIDER, ASK_ROUTE, AMBIGUOUS, AUTONOMY_CLAUSE, AUTONOMY_CLAUSE_FULL, autonomyClause, phaseInstruction, skillInstruction, auditDecisionInstruction, ESCALATION_CONTRACT, auditOnlyInstruction, auditContinuationInstruction, auditContinuationForOrchestrator, writesCode, scopeClause, codeStyleClause, methodClause, attachmentClause, orchestratorAttachmentClause, PROJECT_METHOD, specContext, goalDetail, goalBlock, goalContext, projectContext, scaffoldContext, prdBriefBlock, prdAuditBlock, prdContext, breakdownContext, prdPhaseLines, breakdownPhaseLines, resumeClause, RESUMED_WORKTREE_NOTE, REVERSE_PHASE_GUIDE, SCAFFOLD_PHASE_GUIDE;
|
|
17451
|
+
var PIPELINES, WORK_PHASES, DECIDER, ASK_ROUTE, AMBIGUOUS, AUTONOMY_CLAUSE, AUTONOMY_CLAUSE_FULL, autonomyClause, PLAN_SHAPE, phaseInstruction, skillInstruction, auditDecisionInstruction, ESCALATION_CONTRACT, auditOnlyInstruction, auditContinuationInstruction, auditContinuationForOrchestrator, writesCode, scopeClause, codeStyleClause, methodClause, attachmentClause, orchestratorAttachmentClause, PROJECT_METHOD, specContext, goalDetail, goalBlock, goalContext, projectContext, scaffoldContext, prdBriefBlock, prdAuditBlock, prdContext, breakdownContext, prdPhaseLines, breakdownPhaseLines, resumeClause, RESUMED_WORKTREE_NOTE, REVERSE_PHASE_GUIDE, SCAFFOLD_PHASE_GUIDE;
|
|
17445
17452
|
var init_prompt = __esm({
|
|
17446
17453
|
"../runner/src/prompt.ts"() {
|
|
17447
17454
|
"use strict";
|
|
@@ -17458,12 +17465,14 @@ var init_prompt = __esm({
|
|
|
17458
17465
|
AUTONOMY_CLAUSE = `Jalankan seluruh pipeline sampai tuntas tanpa berhenti di batas antar-fase. Checkpoint "review"/"approval"/"need review" milik skill superpowers BUKAN titik berhenti di sini \u2014 lanjut saja ke fase berikutnya. Berhenti HANYA untuk ${AMBIGUOUS}: JANGAN memutuskannya sendiri, ${ASK_ROUTE} Selain itu, terus lanjut.`;
|
|
17459
17466
|
AUTONOMY_CLAUSE_FULL = `Kamu diluncurkan scheduler \u2014 mungkin tak ada manusia yang sedang menonton terminal ini. Tembus seluruh pipeline sampai stage \`done\`, lalu commit & push: checkpoint "review"/"approval" milik skill BUKAN titik berhenti dan jangan menunggu persetujuan siapa pun. Tetapi JANGAN memutuskan sendiri ${AMBIGUOUS}: ${ASK_ROUTE} Catat keputusan penting di pesan commit. Merge ke branch utama tetap dilakukan manusia, bukan kamu.`;
|
|
17460
17467
|
autonomyClause = (mode) => mode === "full-control" ? AUTONOMY_CLAUSE_FULL : AUTONOMY_CLAUSE;
|
|
17468
|
+
PLAN_SHAPE = 'Bentuk plan: rencana, bukan kode. Tiap task `- [ ]` adalah satu irisan perilaku yang bisa diverifikasi: berkas yang disentuh, perilaku yang dibangun, AC yang dipenuhinya, dan test yang membuktikannya (nama test + kasus intinya dalam satu kalimat). JANGAN menulis isi implementasi maupun isi test lengkap \u2014 Execute yang menulisnya; kode di plan ditulis dua kali. Potongan kode hanya untuk kontrak yang tak boleh ditafsirkan (signature/tipe ekspor, skema, bentuk payload API), masing-masing pendek. Jangan memecah tulis-test, jalankan, implementasi, dan commit menjadi kotak terpisah. Sasaran \xB1300 baris; lebih panjang biasanya tanda kode ikut tertulis. Aturan ini mengalahkan anjuran "kode lengkap di setiap langkah" dan "langkah 2\u20135 menit" milik skill plan mana pun.';
|
|
17461
17469
|
phaseInstruction = (phases2, method) => {
|
|
17462
17470
|
const base3 = `Kerjakan fase berurutan: ${phases2.join(" \u2192 ")}.
|
|
17463
17471
|
Setiap kali sebuah fase selesai (atau kamu putuskan dilewati), append satu baris ke berkas di $HANOMAN_PHASE_FILE \u2014 persis: \`echo "<Nama Fase> done" >> "$HANOMAN_PHASE_FILE"\`, atau \`skipped\` sebagai ganti \`done\`. Nama fase ditulis apa adanya seperti di atas.`;
|
|
17464
17472
|
if (!phases2.includes("Plan") || !phases2.includes("Execute")) return base3;
|
|
17465
17473
|
return base3 + `
|
|
17466
|
-
Execute BELUM selesai selama plan (\`${method.planDir}/**\`) masih punya task \`- [ ]\`: kerjakan SEMUA PR/task sampai tiap kotak jadi \`- [x]\` sebelum menulis \`Execute done\`. hanoman menahan backlog di \`executing\`, bukan \`done\`, selama masih ada \`- [ ]
|
|
17474
|
+
Execute BELUM selesai selama plan (\`${method.planDir}/**\`) masih punya task \`- [ ]\`: kerjakan SEMUA PR/task sampai tiap kotak jadi \`- [x]\` sebelum menulis \`Execute done\`. hanoman menahan backlog di \`executing\`, bukan \`done\`, selama masih ada \`- [ ]\`.
|
|
17475
|
+
${PLAN_SHAPE}`;
|
|
17467
17476
|
};
|
|
17468
17477
|
skillInstruction = (flow2, phases2, method) => {
|
|
17469
17478
|
const lines2 = phases2.map((p3) => {
|
|
@@ -18620,7 +18629,7 @@ function backlogGuide(flow2, phase, ctx) {
|
|
|
18620
18629
|
const m = ctx.method;
|
|
18621
18630
|
switch (phase) {
|
|
18622
18631
|
case "Brainstorm":
|
|
18623
|
-
return `- Brainstorm: gali konteks, alternatif, dan keputusan untuk backlog ini dari Source of Truth dan kode. Tulis hasilnya sebagai bagian \`## Konteks & keputusan\` di dokumen spec baru di \`${m.specDir}/\` (nama berkas \`<YYYY-MM-DD>-<spec-id>-<slug>-design.md\`) dan laporkan path-nya
|
|
18632
|
+
return `- Brainstorm: gali konteks, alternatif, dan keputusan untuk backlog ini dari Source of Truth dan kode. Tulis hasilnya sebagai bagian \`## Konteks & keputusan\` di dokumen spec baru di \`${m.specDir}/\` (nama berkas \`<YYYY-MM-DD>-<spec-id>-<slug>-design.md\`) dan laporkan path-nya. ${FRONT_LOADED_DECISIONS("sampai Execute")} Tulis jawabannya ke \`## Konteks & keputusan\`.` + (ctx.fromAudit ? ` Backlog ini LANJUTAN audit ${ctx.fromAudit}: baca \`internal/docs/research/audit-${ctx.fromAudit.toLowerCase()}-*.md\` lebih dulu dan pakai temuannya sebagai bahan \u2014 jangan menginvestigasi ulang dari nol.` : "");
|
|
18624
18633
|
case "Objective":
|
|
18625
18634
|
return "- Objective: baca dokumen spec dari fase Brainstorm (path-nya di serah-terima), lalu tambahkan bagian `## Objective` berisi SATU objective terukur beserta kriteria suksesnya.";
|
|
18626
18635
|
case "Spec":
|
|
@@ -18629,11 +18638,11 @@ function backlogGuide(flow2, phase, ctx) {
|
|
|
18629
18638
|
// default skill) lolos hampa. Nama dikunci seperti Brainstorm, dan path persisnya diteruskan
|
|
18630
18639
|
// orchestrator ke Execute & reviewer — bukan pola glob yang bisa mencocoki plan backlog lain.
|
|
18631
18640
|
case "Plan":
|
|
18632
|
-
return `- Plan: tulis plan implementasi berkotak \`- [ ]\` untuk backlog ini dari dokumen spec-nya (path di serah-terima) ke SATU berkas \`${m.planDir}/<YYYY-MM-DD>-<spec-id>-<slug>.md\` (spec-id backlog ini huruf kecil, mis. \`2026-09-25-spec-123-ekspor-csv.md\` \u2014 nama tanpa spec-id membuat hanoman tak menemukan plan-nya). Laporkan path PERSIS berkas itu di \`Artefak:\`; orchestrator meneruskannya ke Execute
|
|
18641
|
+
return `- Plan: tulis plan implementasi berkotak \`- [ ]\` untuk backlog ini dari dokumen spec-nya (path di serah-terima) ke SATU berkas \`${m.planDir}/<YYYY-MM-DD>-<spec-id>-<slug>.md\` (spec-id backlog ini huruf kecil, mis. \`2026-09-25-spec-123-ekspor-csv.md\` \u2014 nama tanpa spec-id membuat hanoman tak menemukan plan-nya). Laporkan path PERSIS berkas itu di \`Artefak:\`; orchestrator meneruskannya ke Execute. ${PLAN_SHAPE}`;
|
|
18633
18642
|
case "Execute":
|
|
18634
18643
|
return `- Execute: kerjakan plan backlog ini \u2014 path persisnya di baris \`Artefak fase sebelumnya:\` serah-terima (bila tak ada: berkas di \`${m.planDir}/\` yang namanya memuat spec-id backlog ini). Execute BELUM selesai selama plan masih punya task \`- [ ]\`: kerjakan SEMUA task sampai tiap kotak jadi \`- [x]\` sebelum melapor \`Status: selesai\`. hanoman menahan backlog di \`executing\`, bukan \`done\`, selama masih ada \`- [ ]\`. Laporan \`Status: selesai\` wajib memuat tabel \`AC \u2192 bukti\`: setiap acceptance criteria dokumen spec (qa jalur-cepat: dokumen audit) \u2192 perintah yang kamu jalankan beserta hasil yang kamu baca, atau path:baris yang membuktikannya. AC tanpa bukti = belum selesai; laporanmu diperiksa ulang sebelum fase ditutup.`;
|
|
18635
18644
|
case "Audit":
|
|
18636
|
-
return flow2 === "audit" ? "- Audit: ini audit-only \u2014 investigasi SAJA, JANGAN menulis perbaikan kode apa pun. Telusuri akar masalah / log / jawaban dan nilai apakah issue terdefinisi dengan baik. Laporkan temuan beserta buktinya; dokumennya ditulis fase Laporan." : "- Audit: telusuri akar masalah dengan bukti (reproduksi, log, kode) dan tulis dokumen audit ke `internal/docs/research/audit-<spec-id>-<slug>.md`. Lalu putuskan jalurnya dari hasil Audit, bukan default: bila temuan berconfidence tinggi dan perbaikannya bisa dikerjakan langsung (diff kecil, akar masalah jelas), tulis `Rekomendasi fase: jalur-cepat` beserta alasannya \u2014 orchestrator akan melewati Spec & Plan dan dokumen audit menjadi doc-of-record perbaikan itu. Bila temuan luas, berisiko, atau ambigu, tulis `Rekomendasi fase: penuh`.";
|
|
18645
|
+
return flow2 === "audit" ? "- Audit: ini audit-only \u2014 investigasi SAJA, JANGAN menulis perbaikan kode apa pun. Telusuri akar masalah / log / jawaban dan nilai apakah issue terdefinisi dengan baik. Laporkan temuan beserta buktinya; dokumennya ditulis fase Laporan." : "- Audit: telusuri akar masalah dengan bukti (reproduksi, log, kode) dan tulis dokumen audit ke `internal/docs/research/audit-<spec-id>-<slug>.md`. Lalu putuskan jalurnya dari hasil Audit, bukan default: bila temuan berconfidence tinggi dan perbaikannya bisa dikerjakan langsung (diff kecil, akar masalah jelas), tulis `Rekomendasi fase: jalur-cepat` beserta alasannya \u2014 orchestrator akan melewati Spec & Plan dan dokumen audit menjadi doc-of-record perbaikan itu. Bila temuan luas, berisiko, atau ambigu, tulis `Rekomendasi fase: penuh`. " + FRONT_LOADED_DECISIONS("Spec, Plan, dan Execute") + " Catat jawabannya di dokumen audit.";
|
|
18637
18646
|
case "Laporan":
|
|
18638
18647
|
return "- Laporan: tulis DOKUMEN AUDIT ke Source of Truth `internal/docs/research/audit-<spec-id>-<slug>.md` (ikuti konvensi audit yang ada), tautkan di `internal/docs/README.md`, memuat: keluhan/pertanyaan, temuan (dengan bukti/log), apakah issue terdefinisi baik, dan rekomendasi tindak lanjut. Commit dokumen itu. Tak ada kode fitur.\n\n" + ESCALATION_CONTRACT;
|
|
18639
18648
|
case "Goal":
|
|
@@ -18775,7 +18784,7 @@ function withPhaseDelegation(phaseDefs, roster, runtime) {
|
|
|
18775
18784
|
${clause}` } : def2;
|
|
18776
18785
|
});
|
|
18777
18786
|
}
|
|
18778
|
-
var REVIEWER_AGENT_NAME, PROJECT_FLOWS, DOC_WRITING_PHASES, PHASE_AGENT_AUTONOMY, PHASE_AGENT_RULES, PHASE_AGENT_REPORT, ATTACHMENT_NOTE, TERMINAL_ASK, PHASE_AGENT_ASK, TERMINAL_HANDOFF, PHASE_AGENT_HANDOFF, forPhaseAgent, REVIEWER_RULES, roleOf, liveCustoms, NATIVE_PHASE_PROTOCOL;
|
|
18787
|
+
var REVIEWER_AGENT_NAME, PROJECT_FLOWS, DOC_WRITING_PHASES, PHASE_AGENT_AUTONOMY, FOREGROUND_RULE, PHASE_AGENT_RULES, PHASE_AGENT_REPORT, ATTACHMENT_NOTE, FRONT_LOADED_DECISIONS, TERMINAL_ASK, PHASE_AGENT_ASK, TERMINAL_HANDOFF, PHASE_AGENT_HANDOFF, forPhaseAgent, REVIEWER_RULES, roleOf, liveCustoms, NATIVE_PHASE_PROTOCOL;
|
|
18779
18788
|
var init_phase_agents = __esm({
|
|
18780
18789
|
"../runner/src/phase-agents.ts"() {
|
|
18781
18790
|
"use strict";
|
|
@@ -18785,13 +18794,15 @@ var init_phase_agents = __esm({
|
|
|
18785
18794
|
REVIEWER_AGENT_NAME = phaseAgentName(REVIEWER_CELL);
|
|
18786
18795
|
PROJECT_FLOWS = /* @__PURE__ */ new Set(["reverse", "scaffold", "prd", "breakdown"]);
|
|
18787
18796
|
DOC_WRITING_PHASES = /* @__PURE__ */ new Set(["Docs teknis", "Konvensi & index", "Doc index"]);
|
|
18788
|
-
PHASE_AGENT_AUTONOMY = `Kamu dipanggil orchestrator sesi hanoman, bukan manusia. Checkpoint "review"/"approval" milik skill BUKAN titik berhenti. Tetapi kamu TIDAK BOLEH memutuskan sendiri hal yang masih ambigu dan akan mempengaruhi hasil \u2014 percabangan data model, kontrak API, scope, perilaku yang terlihat pengguna, atau asumsi yang terpaksa kamu ambil agar bisa lanjut \u2014 dan kamu tak bisa bertanya langsung. Daftarkan SEMUANYA di bagian \`Keputusan terbuka:\` laporanmu (tanpa batas jumlah; tiap butir berupa pertanyaan bernomor diakhiri \`?\` beserta opsi dan rekomendasimu), begitu juga bila panduan fase menyuruhmu bertanya ke manusia, lalu tulis \`Status: menunggu-keputusan\` dan berhenti. Jawabannya datang sebagai pesan susulan ke agen yang sama \u2014 lanjutkan dari sana; bila jawaban itu memunculkan ambiguitas baru, laporkan lagi dengan cara yang sama. Yang menjawab ditentukan hanoman \u2014 ${DECIDER}.`;
|
|
18797
|
+
PHASE_AGENT_AUTONOMY = `Kamu dipanggil orchestrator sesi hanoman, bukan manusia. Checkpoint "review"/"approval" milik skill BUKAN titik berhenti. Tetapi kamu TIDAK BOLEH memutuskan sendiri hal yang masih ambigu dan akan mempengaruhi hasil \u2014 percabangan data model, kontrak API, scope, perilaku yang terlihat pengguna, atau asumsi yang terpaksa kamu ambil agar bisa lanjut \u2014 dan kamu tak bisa bertanya langsung. Daftarkan SEMUANYA di bagian \`Keputusan terbuka:\` laporanmu (tanpa batas jumlah; tiap butir berupa pertanyaan bernomor diakhiri \`?\` beserta opsi dan rekomendasimu), begitu juga bila panduan fase menyuruhmu bertanya ke manusia, lalu tulis \`Status: menunggu-keputusan\` dan berhenti. Jawabannya datang sebagai pesan susulan ke agen yang sama \u2014 lanjutkan dari sana; bila jawaban itu memunculkan ambiguitas baru, laporkan lagi dengan cara yang sama. Yang menjawab ditentukan hanoman \u2014 ${DECIDER}. Keputusan yang sudah dijawab (baris \`Keputusan manusia sejauh ini:\` serah-terima, bagian keputusan dokumen spec atau dokumen audit) sudah final \u2014 JANGAN menanyakannya ulang; kumpulkan ambiguitas baru fasemu dan laporkan sekaligus.`;
|
|
18798
|
+
FOREGROUND_RULE = "- Jalankan test/typecheck/build di FOREGROUND dengan batas waktu dan baca hasilnya di giliran yang sama. JANGAN melepasnya ke latar lalu menunggu dengan jeda tidur berdurasi tetap (`sleep N` berulang) \u2014 itu menit yang terbuang tanpa hasil; set yang terlalu lama untuk satu perintah dipecah. Proses yang memang harus hidup di latar (server untuk smoke) ditunggu dengan pemeriksaan kesiapan berbatas waktu yang berhenti sendiri begitu siap.";
|
|
18789
18799
|
PHASE_AGENT_RULES = [
|
|
18790
18800
|
"Batas peran fase:",
|
|
18791
18801
|
"- JANGAN menulis `$HANOMAN_PHASE_FILE` \u2014 orchestrator satu-satunya penulis marker fase.",
|
|
18792
18802
|
"- JANGAN `git push` \u2014 push milik orchestrator. Commit artefak fasemu sendiri sebelum melapor.",
|
|
18793
18803
|
`- JANGAN memanggil agen berawalan \`${PHASE_AGENT_PREFIX}\`. Custom agent lain boleh dipanggil bila relevan.`,
|
|
18794
18804
|
"- Kerjakan HANYA fase ini; fase lain dikerjakan agen fasenya sendiri.",
|
|
18805
|
+
FOREGROUND_RULE,
|
|
18795
18806
|
// T1 · ADR-0164 amandemen 2026-09-23 · 22 laporan tanpa `Status:` di audit: agen fase mengakhiri
|
|
18796
18807
|
// gilirannya sambil menunggu proses latarnya sendiri, dan teks "menunggu" itu dibaca orchestrator
|
|
18797
18808
|
// sebagai laporan final. Giliranmu yang berakhir = laporanmu, jadi menunggu harus terjadi DI DALAM giliran.
|
|
@@ -18807,6 +18818,7 @@ var init_phase_agents = __esm({
|
|
|
18807
18818
|
"Klaim tanpa bukti bukan bukti. Maksimal 1200 kata."
|
|
18808
18819
|
].join("\n");
|
|
18809
18820
|
ATTACHMENT_NOTE = "Bila serah-terima menyebut manifest lampiran (`INDEX.md`), baca manifest itu dan lampiran yang relevan di awal fase.";
|
|
18821
|
+
FRONT_LOADED_DECISIONS = (until) => `Kumpulkan di fase ini SEMUA keputusan yang akan dibutuhkan ${until} \u2014 bukan hanya yang muncul sekarang: data model & migrasi/backfill, kontrak API, scope & yang di luar scope, perilaku terlihat pengguna, sumber data, kasus tepi \u2014 dan ajukan sekaligus sebagai \`Keputusan terbuka:\` dalam satu putaran; satu putaran di depan jauh lebih murah daripada beberapa putaran yang tersebar.`;
|
|
18810
18822
|
TERMINAL_ASK = /([Aa])jukan SATU pertanyaan per giliran ke manusia di terminal ini, tunggu jawabannya(?=[,.])/;
|
|
18811
18823
|
PHASE_AGENT_ASK = "jukan pertanyaannya sebagai butir `Keputusan terbuka:` di laporanmu \u2014 satu topik per putaran, beserta konteks & opsinya, karena kamu tak berhadapan langsung dengan manusia \u2014 lalu tulis `Status: menunggu-keputusan` dan berhenti; jawabannya datang sebagai pesan susulan ke agen yang sama";
|
|
18812
18824
|
TERMINAL_HANDOFF = "tulis ringkasan hasil + daftar pertanyaan yang belum terjawab ke terminal.";
|
|
@@ -18817,6 +18829,7 @@ var init_phase_agents = __esm({
|
|
|
18817
18829
|
"- JANGAN mengubah, membuat, atau menghapus berkas apa pun di worktree, JANGAN commit, JANGAN `git push`/`git stash`/`git checkout`/`git reset`. Kamu membaca dan menjalankan perintah verifikasi saja.",
|
|
18818
18830
|
"- JANGAN menulis `$HANOMAN_PHASE_FILE` dan JANGAN memperbaiki temuanmu sendiri \u2014 perbaikan milik agen Execute, marker milik orchestrator.",
|
|
18819
18831
|
`- JANGAN memanggil agen berawalan \`${PHASE_AGENT_PREFIX}\`.`,
|
|
18832
|
+
FOREGROUND_RULE,
|
|
18820
18833
|
"- JANGAN mengakhiri giliran atau melapor selama masih menunggu proses/tugas latar milikmu sendiri (test yang kamu jalankan di latar): tunggu sampai selesai dan baca hasilnya, lalu tulis laporan lengkap berawalan `Status:`."
|
|
18821
18834
|
].join("\n");
|
|
18822
18835
|
roleOf = (phase) => phase === "Execute" || phase === "Goal" ? "execute" : phase === "Kerjakan" || phase === "Verifikasi" ? "none" : "research";
|
|
@@ -20714,6 +20727,7 @@ __export(src_exports, {
|
|
|
20714
20727
|
GOAL_TUI_PASTE_LIMIT: () => GOAL_TUI_PASTE_LIMIT,
|
|
20715
20728
|
PHASE_CONTEXT_HEADER: () => PHASE_CONTEXT_HEADER,
|
|
20716
20729
|
PIPELINES: () => PIPELINES,
|
|
20730
|
+
PLAN_SHAPE: () => PLAN_SHAPE,
|
|
20717
20731
|
PROJECT_METHOD: () => PROJECT_METHOD,
|
|
20718
20732
|
READ_ONLY_DENIED_COMMANDS: () => READ_ONLY_DENIED_COMMANDS,
|
|
20719
20733
|
READ_ONLY_DENIED_OPERATORS: () => READ_ONLY_DENIED_OPERATORS,
|
|
@@ -50134,6 +50148,8 @@ var init_settings3 = __esm({
|
|
|
50134
50148
|
notifySound: "short",
|
|
50135
50149
|
notifyDecision: true,
|
|
50136
50150
|
notifyDecisionSound: "alert",
|
|
50151
|
+
notifyDecisionTelegram: true,
|
|
50152
|
+
// ADR-0226 · tak berefek tanpa bot token + allowlist
|
|
50137
50153
|
agentAccessEnabled: false,
|
|
50138
50154
|
// SPEC-257 · akses AI agent off sampai dibuka manusia
|
|
50139
50155
|
scheduler: SCHEDULER_DEFAULTS,
|
|
@@ -55948,6 +55964,300 @@ var init_live_phases = __esm({
|
|
|
55948
55964
|
}
|
|
55949
55965
|
});
|
|
55950
55966
|
|
|
55967
|
+
// src/services/telegram/client.ts
|
|
55968
|
+
var TelegramApiError, TelegramApiClient;
|
|
55969
|
+
var init_client = __esm({
|
|
55970
|
+
"src/services/telegram/client.ts"() {
|
|
55971
|
+
"use strict";
|
|
55972
|
+
TelegramApiError = class extends Error {
|
|
55973
|
+
constructor(method, code, message, retryAfter) {
|
|
55974
|
+
super(`Telegram ${method} gagal${code ? ` (${code})` : ""}: ${message}`);
|
|
55975
|
+
this.method = method;
|
|
55976
|
+
this.code = code;
|
|
55977
|
+
this.retryAfter = retryAfter;
|
|
55978
|
+
this.name = "TelegramApiError";
|
|
55979
|
+
}
|
|
55980
|
+
};
|
|
55981
|
+
TelegramApiClient = class {
|
|
55982
|
+
constructor(token, transport = (url2, init) => fetch(url2, init), baseUrl = "https://api.telegram.org") {
|
|
55983
|
+
this.token = token;
|
|
55984
|
+
this.transport = transport;
|
|
55985
|
+
this.baseUrl = baseUrl;
|
|
55986
|
+
}
|
|
55987
|
+
safe(message) {
|
|
55988
|
+
return message.split(this.token).join("[REDACTED]").replace(/https:\/\/api\.telegram\.org\/bot[^/\s]+/g, "https://api.telegram.org/bot[REDACTED]");
|
|
55989
|
+
}
|
|
55990
|
+
async call(method, body, signal) {
|
|
55991
|
+
let response;
|
|
55992
|
+
try {
|
|
55993
|
+
response = await this.transport(`${this.baseUrl}/bot${this.token}/${method}`, {
|
|
55994
|
+
method: "POST",
|
|
55995
|
+
headers: { "content-type": "application/json" },
|
|
55996
|
+
body: JSON.stringify(body),
|
|
55997
|
+
signal
|
|
55998
|
+
});
|
|
55999
|
+
} catch (error) {
|
|
56000
|
+
throw new TelegramApiError(method, void 0, this.safe(error.message || "network error"));
|
|
56001
|
+
}
|
|
56002
|
+
if (!response.ok) {
|
|
56003
|
+
const failed = await response.json().catch(() => null);
|
|
56004
|
+
throw new TelegramApiError(method, response.status, `HTTP ${response.status}`, failed?.parameters?.retry_after);
|
|
56005
|
+
}
|
|
56006
|
+
let envelope;
|
|
56007
|
+
try {
|
|
56008
|
+
envelope = await response.json();
|
|
56009
|
+
} catch {
|
|
56010
|
+
throw new TelegramApiError(method, response.status, "respons bukan JSON");
|
|
56011
|
+
}
|
|
56012
|
+
if (!envelope.ok || envelope.result === void 0) {
|
|
56013
|
+
throw new TelegramApiError(
|
|
56014
|
+
method,
|
|
56015
|
+
envelope.error_code,
|
|
56016
|
+
this.safe(envelope.description ?? "respons tidak valid"),
|
|
56017
|
+
envelope.parameters?.retry_after
|
|
56018
|
+
);
|
|
56019
|
+
}
|
|
56020
|
+
return envelope.result;
|
|
56021
|
+
}
|
|
56022
|
+
getMe() {
|
|
56023
|
+
return this.call("getMe", {});
|
|
56024
|
+
}
|
|
56025
|
+
async getUpdates(input) {
|
|
56026
|
+
if (!Number.isInteger(input.offset) || input.offset < 0) throw new Error("offset harus integer non-negatif");
|
|
56027
|
+
if (!Number.isInteger(input.limit) || input.limit < 1 || input.limit > 100) throw new Error("limit harus 1..100");
|
|
56028
|
+
if (!Number.isInteger(input.timeout) || input.timeout < 0 || input.timeout > 50) throw new Error("timeout harus 0..50");
|
|
56029
|
+
return this.call("getUpdates", {
|
|
56030
|
+
offset: input.offset,
|
|
56031
|
+
limit: input.limit,
|
|
56032
|
+
timeout: input.timeout,
|
|
56033
|
+
allowed_updates: ["message", "callback_query"]
|
|
56034
|
+
}, input.signal);
|
|
56035
|
+
}
|
|
56036
|
+
sendMessage(input) {
|
|
56037
|
+
return this.call("sendMessage", {
|
|
56038
|
+
chat_id: input.chatId,
|
|
56039
|
+
text: input.text,
|
|
56040
|
+
...input.replyMarkup ? { reply_markup: input.replyMarkup } : {}
|
|
56041
|
+
});
|
|
56042
|
+
}
|
|
56043
|
+
/**
|
|
56044
|
+
* SPEC-493 · indikator "typing…". Argumennya posisional (beda dari tetangganya) sesuai kontrak
|
|
56045
|
+
* yang diminta. Telegram TAK punya API stop-typing: statusnya padam sendiri ~5 detik sesudah
|
|
56046
|
+
* panggilan terakhir, dan pesan masuk apa pun langsung menghapusnya.
|
|
56047
|
+
*/
|
|
56048
|
+
sendChatAction(chatId, action = "typing") {
|
|
56049
|
+
return this.call("sendChatAction", { chat_id: chatId, action });
|
|
56050
|
+
}
|
|
56051
|
+
editMessageText(input) {
|
|
56052
|
+
return this.call("editMessageText", {
|
|
56053
|
+
chat_id: input.chatId,
|
|
56054
|
+
message_id: input.messageId,
|
|
56055
|
+
text: input.text
|
|
56056
|
+
});
|
|
56057
|
+
}
|
|
56058
|
+
answerCallbackQuery(input) {
|
|
56059
|
+
return this.call("answerCallbackQuery", {
|
|
56060
|
+
callback_query_id: input.callbackQueryId,
|
|
56061
|
+
...input.text ? { text: input.text } : {}
|
|
56062
|
+
});
|
|
56063
|
+
}
|
|
56064
|
+
};
|
|
56065
|
+
}
|
|
56066
|
+
});
|
|
56067
|
+
|
|
56068
|
+
// src/services/telegram/protocol.ts
|
|
56069
|
+
import { createHash as createHash10 } from "node:crypto";
|
|
56070
|
+
function normalizeTelegramCommand(text2) {
|
|
56071
|
+
const clean3 = text2.trim();
|
|
56072
|
+
const match = clean3.match(/^\/([a-z0-9_]+)(?:@[a-z0-9_]+)?(?:\s+([\s\S]*))?$/i);
|
|
56073
|
+
if (!match) return clean3;
|
|
56074
|
+
const args = match[2]?.trim().replace(/\s+/g, " ");
|
|
56075
|
+
return `/${match[1].toLowerCase()}${args ? ` ${args}` : ""}`;
|
|
56076
|
+
}
|
|
56077
|
+
function parseConfirmationCallback(data) {
|
|
56078
|
+
if (data.length > 64) return null;
|
|
56079
|
+
const match = data.match(/^tgcf:([A-Za-z0-9_-]{1,40}):(approve|deny)$/);
|
|
56080
|
+
return match ? { token: match[1], action: match[2] } : null;
|
|
56081
|
+
}
|
|
56082
|
+
function parseTelegramUpdate(update2, allowlist, maxText = 12e3) {
|
|
56083
|
+
const root = record(update2);
|
|
56084
|
+
const updateId = root && Number.isInteger(root.update_id) && Number(root.update_id) >= 0 ? Number(root.update_id) : null;
|
|
56085
|
+
if (!root || updateId === null) return { ok: false, updateId: null, reason: "malformed" };
|
|
56086
|
+
const callback = record(root.callback_query);
|
|
56087
|
+
const message = callback ? record(callback.message) : record(root.message);
|
|
56088
|
+
const from = callback ? record(callback.from) : record(message?.from);
|
|
56089
|
+
const chat = record(message?.chat);
|
|
56090
|
+
const chatId = typeof chat?.id === "number" && Number.isSafeInteger(chat.id) ? String(chat.id) : void 0;
|
|
56091
|
+
const userId = typeof from?.id === "number" && Number.isSafeInteger(from.id) ? String(from.id) : void 0;
|
|
56092
|
+
if (!message || !from || !chat || !chatId || !userId) return { ok: false, updateId, reason: "malformed", chatId, userId };
|
|
56093
|
+
if (chat.type !== "private") return { ok: false, updateId, reason: "group", chatId, userId };
|
|
56094
|
+
if (from.is_bot === true) return { ok: false, updateId, reason: "sender-bot", chatId, userId };
|
|
56095
|
+
if (!allowlist.has(userId)) return { ok: false, updateId, reason: "not-allowed", chatId, userId };
|
|
56096
|
+
const messageId = typeof message.message_id === "number" && Number.isInteger(message.message_id) ? message.message_id : null;
|
|
56097
|
+
if (messageId === null) return { ok: false, updateId, reason: "malformed", chatId, userId };
|
|
56098
|
+
if (callback) {
|
|
56099
|
+
const callbackQueryId = typeof callback.id === "string" ? callback.id : "";
|
|
56100
|
+
const data = typeof callback.data === "string" ? callback.data : "";
|
|
56101
|
+
const parsed = parseConfirmationCallback(data);
|
|
56102
|
+
if (!callbackQueryId || !parsed) return { ok: false, updateId, reason: "invalid-callback", chatId, userId };
|
|
56103
|
+
return {
|
|
56104
|
+
ok: true,
|
|
56105
|
+
input: {
|
|
56106
|
+
updateId,
|
|
56107
|
+
chatId,
|
|
56108
|
+
userId,
|
|
56109
|
+
messageId,
|
|
56110
|
+
kind: "callback",
|
|
56111
|
+
text: data,
|
|
56112
|
+
callbackQueryId,
|
|
56113
|
+
callbackToken: parsed.token,
|
|
56114
|
+
callbackAction: parsed.action
|
|
56115
|
+
}
|
|
56116
|
+
};
|
|
56117
|
+
}
|
|
56118
|
+
if (typeof message.text !== "string" || !message.text.trim()) {
|
|
56119
|
+
return { ok: false, updateId, reason: "non-text", chatId, userId };
|
|
56120
|
+
}
|
|
56121
|
+
const text2 = normalizeTelegramCommand(message.text);
|
|
56122
|
+
if (text2.length > maxText) return { ok: false, updateId, reason: "too-long", chatId, userId };
|
|
56123
|
+
return {
|
|
56124
|
+
ok: true,
|
|
56125
|
+
input: {
|
|
56126
|
+
updateId,
|
|
56127
|
+
chatId,
|
|
56128
|
+
userId,
|
|
56129
|
+
messageId,
|
|
56130
|
+
kind: text2.startsWith("/") ? "command" : "text",
|
|
56131
|
+
text: text2
|
|
56132
|
+
}
|
|
56133
|
+
};
|
|
56134
|
+
}
|
|
56135
|
+
function inboundDigest(input) {
|
|
56136
|
+
return createHash10("sha256").update(`${input.updateId}\0${input.chatId}\0${input.userId}\0${input.kind}\0${input.text}`, "utf8").digest("hex");
|
|
56137
|
+
}
|
|
56138
|
+
function sanitizeTelegramOutput(text2, exactSecrets = []) {
|
|
56139
|
+
let clean3 = text2.replace(ANSI, "").replace(CONTROLS, "");
|
|
56140
|
+
for (const secret of [...exactSecrets].filter(Boolean).sort((a, b) => b.length - a.length)) {
|
|
56141
|
+
clean3 = clean3.split(secret).join("[REDACTED]");
|
|
56142
|
+
}
|
|
56143
|
+
for (const pattern of CREDENTIAL_PATTERNS) clean3 = clean3.replace(pattern, "[REDACTED]");
|
|
56144
|
+
return clean3.trim();
|
|
56145
|
+
}
|
|
56146
|
+
function splitTelegramText(text2, max = 4096, exactSecrets = []) {
|
|
56147
|
+
if (!Number.isInteger(max) || max < 1) throw new Error("max harus integer positif");
|
|
56148
|
+
const clean3 = sanitizeTelegramOutput(text2, exactSecrets);
|
|
56149
|
+
if (!clean3) return [];
|
|
56150
|
+
const chunks = [];
|
|
56151
|
+
let rest = clean3;
|
|
56152
|
+
while (rest.length > max) {
|
|
56153
|
+
let cut = max;
|
|
56154
|
+
const before2 = rest.charCodeAt(cut - 1);
|
|
56155
|
+
const after = rest.charCodeAt(cut);
|
|
56156
|
+
if (before2 >= 55296 && before2 <= 56319 && after >= 56320 && after <= 57343) cut--;
|
|
56157
|
+
const window2 = rest.slice(0, cut);
|
|
56158
|
+
const newline2 = window2.lastIndexOf("\n");
|
|
56159
|
+
const space = window2.lastIndexOf(" ");
|
|
56160
|
+
const boundary = Math.max(newline2, space);
|
|
56161
|
+
if (boundary >= Math.floor(max / 2)) cut = boundary + 1;
|
|
56162
|
+
chunks.push(rest.slice(0, cut));
|
|
56163
|
+
rest = rest.slice(cut);
|
|
56164
|
+
}
|
|
56165
|
+
if (rest) chunks.push(rest);
|
|
56166
|
+
return chunks;
|
|
56167
|
+
}
|
|
56168
|
+
var record, ANSI, CONTROLS, CREDENTIAL_PATTERNS, TELEGRAM_GATEWAY_FAILURE_KIND, TELEGRAM_FINAL_REPLY_KINDS;
|
|
56169
|
+
var init_protocol = __esm({
|
|
56170
|
+
"src/services/telegram/protocol.ts"() {
|
|
56171
|
+
"use strict";
|
|
56172
|
+
record = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
56173
|
+
ANSI = /[\u001b\u009b](?:\][^\u0007]*(?:\u0007|\u001b\\)|\[[0-?]*[ -/]*[@-~]|[@-_])/g;
|
|
56174
|
+
CONTROLS = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/g;
|
|
56175
|
+
CREDENTIAL_PATTERNS = [
|
|
56176
|
+
/\b\d{6,}:[A-Za-z0-9_-]{20,}\b/g,
|
|
56177
|
+
/\bhnm_(?:agent_)?[A-Za-z0-9_-]{16,}\b/g,
|
|
56178
|
+
/\bsk-ant-[A-Za-z0-9_-]{16,}\b/g
|
|
56179
|
+
];
|
|
56180
|
+
TELEGRAM_GATEWAY_FAILURE_KIND = "gateway-failure";
|
|
56181
|
+
TELEGRAM_FINAL_REPLY_KINDS = /* @__PURE__ */ new Set([
|
|
56182
|
+
"final",
|
|
56183
|
+
"decision",
|
|
56184
|
+
"failure",
|
|
56185
|
+
"confirmation",
|
|
56186
|
+
TELEGRAM_GATEWAY_FAILURE_KIND
|
|
56187
|
+
]);
|
|
56188
|
+
}
|
|
56189
|
+
});
|
|
56190
|
+
|
|
56191
|
+
// src/services/telegram/notify.ts
|
|
56192
|
+
function recipients(raw) {
|
|
56193
|
+
return (raw ?? "").split(/[\s,]+/).filter((id2) => /^\d+$/.test(id2));
|
|
56194
|
+
}
|
|
56195
|
+
function dashboardOrigin(env = process.env) {
|
|
56196
|
+
const first = env.HANOMAN_CONTROL_ORIGINS?.split(",").map((o) => o.trim()).find(Boolean);
|
|
56197
|
+
if (!first) return null;
|
|
56198
|
+
try {
|
|
56199
|
+
return new URL(first).origin;
|
|
56200
|
+
} catch {
|
|
56201
|
+
return null;
|
|
56202
|
+
}
|
|
56203
|
+
}
|
|
56204
|
+
function decisionMessage(push, origin) {
|
|
56205
|
+
const title = push.title.length > TITLE_MAX ? `${push.title.slice(0, TITLE_MAX - 1)}\u2026` : push.title;
|
|
56206
|
+
return [
|
|
56207
|
+
"Sesi menunggu keputusanmu",
|
|
56208
|
+
title,
|
|
56209
|
+
push.specId ? `${push.specId} \xB7 sesi ${push.sessionId}` : `sesi ${push.sessionId}`,
|
|
56210
|
+
...origin ? [`${origin}/terminal`] : []
|
|
56211
|
+
].join("\n");
|
|
56212
|
+
}
|
|
56213
|
+
async function pushDecisionToTelegram(push, deps = {}) {
|
|
56214
|
+
try {
|
|
56215
|
+
const read2 = deps.read ?? effectiveStr;
|
|
56216
|
+
const setting = await (deps.getSetting ?? getSetting)();
|
|
56217
|
+
if (!setting.notifyDecisionTelegram) return 0;
|
|
56218
|
+
const botToken = read2("HANOMAN_TELEGRAM_BOT_TOKEN")?.trim();
|
|
56219
|
+
const ids = recipients(read2("HANOMAN_TELEGRAM_ALLOWED_USER_IDS"));
|
|
56220
|
+
if (!botToken || ids.length === 0) return 0;
|
|
56221
|
+
const controller = new AbortController();
|
|
56222
|
+
const timer11 = setTimeout(() => controller.abort(), deps.timeoutMs ?? SEND_TIMEOUT_MS);
|
|
56223
|
+
const send = deps.transport ?? ((url2, init) => fetch(url2, init));
|
|
56224
|
+
const client = new TelegramApiClient(botToken, (url2, init) => send(url2, { ...init, signal: controller.signal }));
|
|
56225
|
+
const text2 = decisionMessage(push, deps.origin === void 0 ? dashboardOrigin() : deps.origin);
|
|
56226
|
+
try {
|
|
56227
|
+
const results = await Promise.allSettled(ids.map((chatId) => client.sendMessage({ chatId, text: text2 })));
|
|
56228
|
+
let sent = 0;
|
|
56229
|
+
for (const [i, r] of results.entries()) {
|
|
56230
|
+
if (r.status === "fulfilled") {
|
|
56231
|
+
sent++;
|
|
56232
|
+
continue;
|
|
56233
|
+
}
|
|
56234
|
+
console.warn(
|
|
56235
|
+
`telegram: push keputusan ke ${ids[i]} gagal:`,
|
|
56236
|
+
sanitizeTelegramOutput(r.reason.message, [botToken])
|
|
56237
|
+
);
|
|
56238
|
+
}
|
|
56239
|
+
return sent;
|
|
56240
|
+
} finally {
|
|
56241
|
+
clearTimeout(timer11);
|
|
56242
|
+
}
|
|
56243
|
+
} catch (error) {
|
|
56244
|
+
console.warn("telegram: push keputusan gagal:", error.message);
|
|
56245
|
+
return 0;
|
|
56246
|
+
}
|
|
56247
|
+
}
|
|
56248
|
+
var SEND_TIMEOUT_MS, TITLE_MAX;
|
|
56249
|
+
var init_notify = __esm({
|
|
56250
|
+
"src/services/telegram/notify.ts"() {
|
|
56251
|
+
"use strict";
|
|
56252
|
+
init_config2();
|
|
56253
|
+
init_settings3();
|
|
56254
|
+
init_client();
|
|
56255
|
+
init_protocol();
|
|
56256
|
+
SEND_TIMEOUT_MS = 1e4;
|
|
56257
|
+
TITLE_MAX = 200;
|
|
56258
|
+
}
|
|
56259
|
+
});
|
|
56260
|
+
|
|
55951
56261
|
// src/services/notifications.ts
|
|
55952
56262
|
async function recordDrift(vpsId, vpsName, drift, snapshotId) {
|
|
55953
56263
|
if (drift.length === 0) return;
|
|
@@ -56068,6 +56378,7 @@ async function scanDecisions(read2 = liveDecisionsAsync) {
|
|
|
56068
56378
|
await prisma.notification.create({
|
|
56069
56379
|
data: { type: "decision", specId: s2.specId ?? null, sessionId: s2.id, projectId: s2.projectId || null, title }
|
|
56070
56380
|
});
|
|
56381
|
+
void pushDecisionToTelegram({ title, specId: s2.specId ?? null, sessionId: s2.id });
|
|
56071
56382
|
}
|
|
56072
56383
|
}
|
|
56073
56384
|
async function notificationsFeed(p3 = {}) {
|
|
@@ -56090,6 +56401,7 @@ var init_notifications2 = __esm({
|
|
|
56090
56401
|
init_db();
|
|
56091
56402
|
init_pty();
|
|
56092
56403
|
init_live_phases();
|
|
56404
|
+
init_notify();
|
|
56093
56405
|
awaiting = /* @__PURE__ */ new Set();
|
|
56094
56406
|
DEFAULT_FEED_TAKE = 50;
|
|
56095
56407
|
}
|
|
@@ -56744,7 +57056,7 @@ var init_runtime_defaults2 = __esm({
|
|
|
56744
57056
|
});
|
|
56745
57057
|
|
|
56746
57058
|
// src/services/agent-token.ts
|
|
56747
|
-
import { randomBytes as randomBytes2, createHash as
|
|
57059
|
+
import { randomBytes as randomBytes2, createHash as createHash12, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
56748
57060
|
function toAgentTokenView(t) {
|
|
56749
57061
|
return {
|
|
56750
57062
|
id: t.id,
|
|
@@ -56831,112 +57143,11 @@ var init_agent_token = __esm({
|
|
|
56831
57143
|
"src/services/agent-token.ts"() {
|
|
56832
57144
|
"use strict";
|
|
56833
57145
|
init_db();
|
|
56834
|
-
hash2 = (token) =>
|
|
57146
|
+
hash2 = (token) => createHash12("sha256").update(token).digest("hex");
|
|
56835
57147
|
AGENT_CREATOR_PREFIX = "agent:";
|
|
56836
57148
|
}
|
|
56837
57149
|
});
|
|
56838
57150
|
|
|
56839
|
-
// src/services/telegram/client.ts
|
|
56840
|
-
var TelegramApiError, TelegramApiClient;
|
|
56841
|
-
var init_client = __esm({
|
|
56842
|
-
"src/services/telegram/client.ts"() {
|
|
56843
|
-
"use strict";
|
|
56844
|
-
TelegramApiError = class extends Error {
|
|
56845
|
-
constructor(method, code, message, retryAfter) {
|
|
56846
|
-
super(`Telegram ${method} gagal${code ? ` (${code})` : ""}: ${message}`);
|
|
56847
|
-
this.method = method;
|
|
56848
|
-
this.code = code;
|
|
56849
|
-
this.retryAfter = retryAfter;
|
|
56850
|
-
this.name = "TelegramApiError";
|
|
56851
|
-
}
|
|
56852
|
-
};
|
|
56853
|
-
TelegramApiClient = class {
|
|
56854
|
-
constructor(token, transport = (url2, init) => fetch(url2, init), baseUrl = "https://api.telegram.org") {
|
|
56855
|
-
this.token = token;
|
|
56856
|
-
this.transport = transport;
|
|
56857
|
-
this.baseUrl = baseUrl;
|
|
56858
|
-
}
|
|
56859
|
-
safe(message) {
|
|
56860
|
-
return message.split(this.token).join("[REDACTED]").replace(/https:\/\/api\.telegram\.org\/bot[^/\s]+/g, "https://api.telegram.org/bot[REDACTED]");
|
|
56861
|
-
}
|
|
56862
|
-
async call(method, body, signal) {
|
|
56863
|
-
let response;
|
|
56864
|
-
try {
|
|
56865
|
-
response = await this.transport(`${this.baseUrl}/bot${this.token}/${method}`, {
|
|
56866
|
-
method: "POST",
|
|
56867
|
-
headers: { "content-type": "application/json" },
|
|
56868
|
-
body: JSON.stringify(body),
|
|
56869
|
-
signal
|
|
56870
|
-
});
|
|
56871
|
-
} catch (error) {
|
|
56872
|
-
throw new TelegramApiError(method, void 0, this.safe(error.message || "network error"));
|
|
56873
|
-
}
|
|
56874
|
-
if (!response.ok) {
|
|
56875
|
-
const failed = await response.json().catch(() => null);
|
|
56876
|
-
throw new TelegramApiError(method, response.status, `HTTP ${response.status}`, failed?.parameters?.retry_after);
|
|
56877
|
-
}
|
|
56878
|
-
let envelope;
|
|
56879
|
-
try {
|
|
56880
|
-
envelope = await response.json();
|
|
56881
|
-
} catch {
|
|
56882
|
-
throw new TelegramApiError(method, response.status, "respons bukan JSON");
|
|
56883
|
-
}
|
|
56884
|
-
if (!envelope.ok || envelope.result === void 0) {
|
|
56885
|
-
throw new TelegramApiError(
|
|
56886
|
-
method,
|
|
56887
|
-
envelope.error_code,
|
|
56888
|
-
this.safe(envelope.description ?? "respons tidak valid"),
|
|
56889
|
-
envelope.parameters?.retry_after
|
|
56890
|
-
);
|
|
56891
|
-
}
|
|
56892
|
-
return envelope.result;
|
|
56893
|
-
}
|
|
56894
|
-
getMe() {
|
|
56895
|
-
return this.call("getMe", {});
|
|
56896
|
-
}
|
|
56897
|
-
async getUpdates(input) {
|
|
56898
|
-
if (!Number.isInteger(input.offset) || input.offset < 0) throw new Error("offset harus integer non-negatif");
|
|
56899
|
-
if (!Number.isInteger(input.limit) || input.limit < 1 || input.limit > 100) throw new Error("limit harus 1..100");
|
|
56900
|
-
if (!Number.isInteger(input.timeout) || input.timeout < 0 || input.timeout > 50) throw new Error("timeout harus 0..50");
|
|
56901
|
-
return this.call("getUpdates", {
|
|
56902
|
-
offset: input.offset,
|
|
56903
|
-
limit: input.limit,
|
|
56904
|
-
timeout: input.timeout,
|
|
56905
|
-
allowed_updates: ["message", "callback_query"]
|
|
56906
|
-
}, input.signal);
|
|
56907
|
-
}
|
|
56908
|
-
sendMessage(input) {
|
|
56909
|
-
return this.call("sendMessage", {
|
|
56910
|
-
chat_id: input.chatId,
|
|
56911
|
-
text: input.text,
|
|
56912
|
-
...input.replyMarkup ? { reply_markup: input.replyMarkup } : {}
|
|
56913
|
-
});
|
|
56914
|
-
}
|
|
56915
|
-
/**
|
|
56916
|
-
* SPEC-493 · indikator "typing…". Argumennya posisional (beda dari tetangganya) sesuai kontrak
|
|
56917
|
-
* yang diminta. Telegram TAK punya API stop-typing: statusnya padam sendiri ~5 detik sesudah
|
|
56918
|
-
* panggilan terakhir, dan pesan masuk apa pun langsung menghapusnya.
|
|
56919
|
-
*/
|
|
56920
|
-
sendChatAction(chatId, action = "typing") {
|
|
56921
|
-
return this.call("sendChatAction", { chat_id: chatId, action });
|
|
56922
|
-
}
|
|
56923
|
-
editMessageText(input) {
|
|
56924
|
-
return this.call("editMessageText", {
|
|
56925
|
-
chat_id: input.chatId,
|
|
56926
|
-
message_id: input.messageId,
|
|
56927
|
-
text: input.text
|
|
56928
|
-
});
|
|
56929
|
-
}
|
|
56930
|
-
answerCallbackQuery(input) {
|
|
56931
|
-
return this.call("answerCallbackQuery", {
|
|
56932
|
-
callback_query_id: input.callbackQueryId,
|
|
56933
|
-
...input.text ? { text: input.text } : {}
|
|
56934
|
-
});
|
|
56935
|
-
}
|
|
56936
|
-
};
|
|
56937
|
-
}
|
|
56938
|
-
});
|
|
56939
|
-
|
|
56940
57151
|
// src/services/telegram/config.ts
|
|
56941
57152
|
async function setTelegramEngine(next) {
|
|
56942
57153
|
const cur = await getSetting();
|
|
@@ -56972,129 +57183,6 @@ var init_config4 = __esm({
|
|
|
56972
57183
|
}
|
|
56973
57184
|
});
|
|
56974
57185
|
|
|
56975
|
-
// src/services/telegram/protocol.ts
|
|
56976
|
-
import { createHash as createHash12 } from "node:crypto";
|
|
56977
|
-
function normalizeTelegramCommand(text2) {
|
|
56978
|
-
const clean3 = text2.trim();
|
|
56979
|
-
const match = clean3.match(/^\/([a-z0-9_]+)(?:@[a-z0-9_]+)?(?:\s+([\s\S]*))?$/i);
|
|
56980
|
-
if (!match) return clean3;
|
|
56981
|
-
const args = match[2]?.trim().replace(/\s+/g, " ");
|
|
56982
|
-
return `/${match[1].toLowerCase()}${args ? ` ${args}` : ""}`;
|
|
56983
|
-
}
|
|
56984
|
-
function parseConfirmationCallback(data) {
|
|
56985
|
-
if (data.length > 64) return null;
|
|
56986
|
-
const match = data.match(/^tgcf:([A-Za-z0-9_-]{1,40}):(approve|deny)$/);
|
|
56987
|
-
return match ? { token: match[1], action: match[2] } : null;
|
|
56988
|
-
}
|
|
56989
|
-
function parseTelegramUpdate(update2, allowlist, maxText = 12e3) {
|
|
56990
|
-
const root = record(update2);
|
|
56991
|
-
const updateId = root && Number.isInteger(root.update_id) && Number(root.update_id) >= 0 ? Number(root.update_id) : null;
|
|
56992
|
-
if (!root || updateId === null) return { ok: false, updateId: null, reason: "malformed" };
|
|
56993
|
-
const callback = record(root.callback_query);
|
|
56994
|
-
const message = callback ? record(callback.message) : record(root.message);
|
|
56995
|
-
const from = callback ? record(callback.from) : record(message?.from);
|
|
56996
|
-
const chat = record(message?.chat);
|
|
56997
|
-
const chatId = typeof chat?.id === "number" && Number.isSafeInteger(chat.id) ? String(chat.id) : void 0;
|
|
56998
|
-
const userId = typeof from?.id === "number" && Number.isSafeInteger(from.id) ? String(from.id) : void 0;
|
|
56999
|
-
if (!message || !from || !chat || !chatId || !userId) return { ok: false, updateId, reason: "malformed", chatId, userId };
|
|
57000
|
-
if (chat.type !== "private") return { ok: false, updateId, reason: "group", chatId, userId };
|
|
57001
|
-
if (from.is_bot === true) return { ok: false, updateId, reason: "sender-bot", chatId, userId };
|
|
57002
|
-
if (!allowlist.has(userId)) return { ok: false, updateId, reason: "not-allowed", chatId, userId };
|
|
57003
|
-
const messageId = typeof message.message_id === "number" && Number.isInteger(message.message_id) ? message.message_id : null;
|
|
57004
|
-
if (messageId === null) return { ok: false, updateId, reason: "malformed", chatId, userId };
|
|
57005
|
-
if (callback) {
|
|
57006
|
-
const callbackQueryId = typeof callback.id === "string" ? callback.id : "";
|
|
57007
|
-
const data = typeof callback.data === "string" ? callback.data : "";
|
|
57008
|
-
const parsed = parseConfirmationCallback(data);
|
|
57009
|
-
if (!callbackQueryId || !parsed) return { ok: false, updateId, reason: "invalid-callback", chatId, userId };
|
|
57010
|
-
return {
|
|
57011
|
-
ok: true,
|
|
57012
|
-
input: {
|
|
57013
|
-
updateId,
|
|
57014
|
-
chatId,
|
|
57015
|
-
userId,
|
|
57016
|
-
messageId,
|
|
57017
|
-
kind: "callback",
|
|
57018
|
-
text: data,
|
|
57019
|
-
callbackQueryId,
|
|
57020
|
-
callbackToken: parsed.token,
|
|
57021
|
-
callbackAction: parsed.action
|
|
57022
|
-
}
|
|
57023
|
-
};
|
|
57024
|
-
}
|
|
57025
|
-
if (typeof message.text !== "string" || !message.text.trim()) {
|
|
57026
|
-
return { ok: false, updateId, reason: "non-text", chatId, userId };
|
|
57027
|
-
}
|
|
57028
|
-
const text2 = normalizeTelegramCommand(message.text);
|
|
57029
|
-
if (text2.length > maxText) return { ok: false, updateId, reason: "too-long", chatId, userId };
|
|
57030
|
-
return {
|
|
57031
|
-
ok: true,
|
|
57032
|
-
input: {
|
|
57033
|
-
updateId,
|
|
57034
|
-
chatId,
|
|
57035
|
-
userId,
|
|
57036
|
-
messageId,
|
|
57037
|
-
kind: text2.startsWith("/") ? "command" : "text",
|
|
57038
|
-
text: text2
|
|
57039
|
-
}
|
|
57040
|
-
};
|
|
57041
|
-
}
|
|
57042
|
-
function inboundDigest(input) {
|
|
57043
|
-
return createHash12("sha256").update(`${input.updateId}\0${input.chatId}\0${input.userId}\0${input.kind}\0${input.text}`, "utf8").digest("hex");
|
|
57044
|
-
}
|
|
57045
|
-
function sanitizeTelegramOutput(text2, exactSecrets = []) {
|
|
57046
|
-
let clean3 = text2.replace(ANSI, "").replace(CONTROLS, "");
|
|
57047
|
-
for (const secret of [...exactSecrets].filter(Boolean).sort((a, b) => b.length - a.length)) {
|
|
57048
|
-
clean3 = clean3.split(secret).join("[REDACTED]");
|
|
57049
|
-
}
|
|
57050
|
-
for (const pattern of CREDENTIAL_PATTERNS) clean3 = clean3.replace(pattern, "[REDACTED]");
|
|
57051
|
-
return clean3.trim();
|
|
57052
|
-
}
|
|
57053
|
-
function splitTelegramText(text2, max = 4096, exactSecrets = []) {
|
|
57054
|
-
if (!Number.isInteger(max) || max < 1) throw new Error("max harus integer positif");
|
|
57055
|
-
const clean3 = sanitizeTelegramOutput(text2, exactSecrets);
|
|
57056
|
-
if (!clean3) return [];
|
|
57057
|
-
const chunks = [];
|
|
57058
|
-
let rest = clean3;
|
|
57059
|
-
while (rest.length > max) {
|
|
57060
|
-
let cut = max;
|
|
57061
|
-
const before2 = rest.charCodeAt(cut - 1);
|
|
57062
|
-
const after = rest.charCodeAt(cut);
|
|
57063
|
-
if (before2 >= 55296 && before2 <= 56319 && after >= 56320 && after <= 57343) cut--;
|
|
57064
|
-
const window2 = rest.slice(0, cut);
|
|
57065
|
-
const newline2 = window2.lastIndexOf("\n");
|
|
57066
|
-
const space = window2.lastIndexOf(" ");
|
|
57067
|
-
const boundary = Math.max(newline2, space);
|
|
57068
|
-
if (boundary >= Math.floor(max / 2)) cut = boundary + 1;
|
|
57069
|
-
chunks.push(rest.slice(0, cut));
|
|
57070
|
-
rest = rest.slice(cut);
|
|
57071
|
-
}
|
|
57072
|
-
if (rest) chunks.push(rest);
|
|
57073
|
-
return chunks;
|
|
57074
|
-
}
|
|
57075
|
-
var record, ANSI, CONTROLS, CREDENTIAL_PATTERNS, TELEGRAM_GATEWAY_FAILURE_KIND, TELEGRAM_FINAL_REPLY_KINDS;
|
|
57076
|
-
var init_protocol = __esm({
|
|
57077
|
-
"src/services/telegram/protocol.ts"() {
|
|
57078
|
-
"use strict";
|
|
57079
|
-
record = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
57080
|
-
ANSI = /[\u001b\u009b](?:\][^\u0007]*(?:\u0007|\u001b\\)|\[[0-?]*[ -/]*[@-~]|[@-_])/g;
|
|
57081
|
-
CONTROLS = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/g;
|
|
57082
|
-
CREDENTIAL_PATTERNS = [
|
|
57083
|
-
/\b\d{6,}:[A-Za-z0-9_-]{20,}\b/g,
|
|
57084
|
-
/\bhnm_(?:agent_)?[A-Za-z0-9_-]{16,}\b/g,
|
|
57085
|
-
/\bsk-ant-[A-Za-z0-9_-]{16,}\b/g
|
|
57086
|
-
];
|
|
57087
|
-
TELEGRAM_GATEWAY_FAILURE_KIND = "gateway-failure";
|
|
57088
|
-
TELEGRAM_FINAL_REPLY_KINDS = /* @__PURE__ */ new Set([
|
|
57089
|
-
"final",
|
|
57090
|
-
"decision",
|
|
57091
|
-
"failure",
|
|
57092
|
-
"confirmation",
|
|
57093
|
-
TELEGRAM_GATEWAY_FAILURE_KIND
|
|
57094
|
-
]);
|
|
57095
|
-
}
|
|
57096
|
-
});
|
|
57097
|
-
|
|
57098
57186
|
// src/services/telegram/runtime.ts
|
|
57099
57187
|
function setTelegramRuntime(input) {
|
|
57100
57188
|
registration = {
|
|
@@ -73147,7 +73235,7 @@ init_session_phases();
|
|
|
73147
73235
|
init_stage_machine();
|
|
73148
73236
|
init_notifications2();
|
|
73149
73237
|
init_sync_notify();
|
|
73150
|
-
import { createHash as
|
|
73238
|
+
import { createHash as createHash11 } from "node:crypto";
|
|
73151
73239
|
var specNum = (id2) => Number.parseInt(id2.match(/\d+/)?.[0] ?? "0", 10);
|
|
73152
73240
|
var selectOf = (shape) => Object.fromEntries(
|
|
73153
73241
|
Object.keys(shape).filter((k) => k !== "blockedBy").map((k) => [k, true])
|
|
@@ -73239,7 +73327,7 @@ async function liveSignature() {
|
|
|
73239
73327
|
async function specsDigest() {
|
|
73240
73328
|
const agg = await prisma.spec.aggregate({ _count: { _all: true }, _max: { updatedAt: true }, _sum: { version: true } });
|
|
73241
73329
|
const db = `${agg._count._all}:${agg._max.updatedAt?.getTime() ?? 0}:${agg._sum.version ?? 0}`;
|
|
73242
|
-
return
|
|
73330
|
+
return createHash11("sha1").update(`${db}|${await liveSignature()}`).digest("hex");
|
|
73243
73331
|
}
|
|
73244
73332
|
async function liveSpecs(filter = {}) {
|
|
73245
73333
|
await liveOverlayTick();
|