hanoman 0.9.30 → 0.9.32
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 +62 -26
- package/dist/server.js +471 -336
- 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),
|
|
@@ -7613,12 +7615,16 @@ var init_session_admission = __esm({
|
|
|
7613
7615
|
function flowForSource(source) {
|
|
7614
7616
|
return source === "qa" ? "qa" : source === "audit" ? "audit" : source === "goal" ? "goal" : source === "no_effort" ? "no_effort" : "feature";
|
|
7615
7617
|
}
|
|
7618
|
+
function formatExitIntent(pid, code) {
|
|
7619
|
+
return `${pid} ${code}
|
|
7620
|
+
`;
|
|
7621
|
+
}
|
|
7616
7622
|
function subKey(topic, params) {
|
|
7617
7623
|
const canon = {};
|
|
7618
7624
|
for (const k of Object.keys(params).sort()) if (params[k] !== void 0) canon[k] = params[k];
|
|
7619
7625
|
return `${topic}|${JSON.stringify(canon)}`;
|
|
7620
7626
|
}
|
|
7621
|
-
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, isGoalShapedFlow, 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;
|
|
7627
|
+
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, isGoalShapedFlow, 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;
|
|
7622
7628
|
var init_dto = __esm({
|
|
7623
7629
|
"../shared/src/dto.ts"() {
|
|
7624
7630
|
"use strict";
|
|
@@ -8153,6 +8159,7 @@ var init_dto = __esm({
|
|
|
8153
8159
|
UPDATE_RESTART_EXIT = 75;
|
|
8154
8160
|
CONFIG_RESTART_EXIT = 76;
|
|
8155
8161
|
MANUAL_RESTART_EXIT = 77;
|
|
8162
|
+
EXIT_INTENT_FILE = "exit-intent";
|
|
8156
8163
|
zUpdateApplyBody = external_exports.object({ confirm: external_exports.boolean().optional() });
|
|
8157
8164
|
MAX_SUBS = 16;
|
|
8158
8165
|
zSubPage = external_exports.number().int().min(1).max(1e4);
|
|
@@ -17096,6 +17103,11 @@ var init_verify_scope = __esm({
|
|
|
17096
17103
|
' test-nya memang berjalan, jangan menerima "no test files" sebagai bukti.',
|
|
17097
17104
|
"- Typecheck: hanya paket yang tersentuh (mis. `pnpm --filter ./server typecheck`).",
|
|
17098
17105
|
" JANGAN `pnpm -r typecheck` \u2014 itu menyalakan satu proses tsc per paket sekaligus.",
|
|
17106
|
+
"- Ritme: per task jalankan berkas test yang task itu sentuh saja.",
|
|
17107
|
+
" JANGAN menjalankan ulang berkas test yang sudah hijau selama kode yang diujinya",
|
|
17108
|
+
" tak berubah sejak itu. Typecheck paket tersentuh sesudah task yang mengubah",
|
|
17109
|
+
" tipe/kontrak ekspor, bukan tiap task. Gabungan semua test tersentuh + typecheck",
|
|
17110
|
+
" dijalankan SEKALI di akhir, sebelum melapor.",
|
|
17099
17111
|
"- Lint: hanya berkas yang berubah, bukan seluruh repo.",
|
|
17100
17112
|
"- Build penuh: hanya bila yang kamu ubah memang soal build/bundling.",
|
|
17101
17113
|
"- Boot server + curl / smoke end-to-end: hanya bila task ini menyentuh endpoint atau perilaku",
|
|
@@ -17441,7 +17453,7 @@ Stack: ${project.stack || "\u2014"}`,
|
|
|
17441
17453
|
${cron.prompt}`
|
|
17442
17454
|
].join("\n\n");
|
|
17443
17455
|
}
|
|
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;
|
|
17456
|
+
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
17457
|
var init_prompt = __esm({
|
|
17446
17458
|
"../runner/src/prompt.ts"() {
|
|
17447
17459
|
"use strict";
|
|
@@ -17458,12 +17470,14 @@ var init_prompt = __esm({
|
|
|
17458
17470
|
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
17471
|
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
17472
|
autonomyClause = (mode) => mode === "full-control" ? AUTONOMY_CLAUSE_FULL : AUTONOMY_CLAUSE;
|
|
17473
|
+
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
17474
|
phaseInstruction = (phases2, method) => {
|
|
17462
17475
|
const base3 = `Kerjakan fase berurutan: ${phases2.join(" \u2192 ")}.
|
|
17463
17476
|
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
17477
|
if (!phases2.includes("Plan") || !phases2.includes("Execute")) return base3;
|
|
17465
17478
|
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 \`- [ ]
|
|
17479
|
+
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 \`- [ ]\`.
|
|
17480
|
+
${PLAN_SHAPE}`;
|
|
17467
17481
|
};
|
|
17468
17482
|
skillInstruction = (flow2, phases2, method) => {
|
|
17469
17483
|
const lines2 = phases2.map((p3) => {
|
|
@@ -18620,7 +18634,7 @@ function backlogGuide(flow2, phase, ctx) {
|
|
|
18620
18634
|
const m = ctx.method;
|
|
18621
18635
|
switch (phase) {
|
|
18622
18636
|
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
|
|
18637
|
+
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
18638
|
case "Objective":
|
|
18625
18639
|
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
18640
|
case "Spec":
|
|
@@ -18629,11 +18643,11 @@ function backlogGuide(flow2, phase, ctx) {
|
|
|
18629
18643
|
// default skill) lolos hampa. Nama dikunci seperti Brainstorm, dan path persisnya diteruskan
|
|
18630
18644
|
// orchestrator ke Execute & reviewer — bukan pola glob yang bisa mencocoki plan backlog lain.
|
|
18631
18645
|
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
|
|
18646
|
+
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
18647
|
case "Execute":
|
|
18634
18648
|
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
18649
|
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`.";
|
|
18650
|
+
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
18651
|
case "Laporan":
|
|
18638
18652
|
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
18653
|
case "Goal":
|
|
@@ -18775,7 +18789,7 @@ function withPhaseDelegation(phaseDefs, roster, runtime) {
|
|
|
18775
18789
|
${clause}` } : def2;
|
|
18776
18790
|
});
|
|
18777
18791
|
}
|
|
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;
|
|
18792
|
+
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
18793
|
var init_phase_agents = __esm({
|
|
18780
18794
|
"../runner/src/phase-agents.ts"() {
|
|
18781
18795
|
"use strict";
|
|
@@ -18785,13 +18799,15 @@ var init_phase_agents = __esm({
|
|
|
18785
18799
|
REVIEWER_AGENT_NAME = phaseAgentName(REVIEWER_CELL);
|
|
18786
18800
|
PROJECT_FLOWS = /* @__PURE__ */ new Set(["reverse", "scaffold", "prd", "breakdown"]);
|
|
18787
18801
|
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}.`;
|
|
18802
|
+
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.`;
|
|
18803
|
+
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
18804
|
PHASE_AGENT_RULES = [
|
|
18790
18805
|
"Batas peran fase:",
|
|
18791
18806
|
"- JANGAN menulis `$HANOMAN_PHASE_FILE` \u2014 orchestrator satu-satunya penulis marker fase.",
|
|
18792
18807
|
"- JANGAN `git push` \u2014 push milik orchestrator. Commit artefak fasemu sendiri sebelum melapor.",
|
|
18793
18808
|
`- JANGAN memanggil agen berawalan \`${PHASE_AGENT_PREFIX}\`. Custom agent lain boleh dipanggil bila relevan.`,
|
|
18794
18809
|
"- Kerjakan HANYA fase ini; fase lain dikerjakan agen fasenya sendiri.",
|
|
18810
|
+
FOREGROUND_RULE,
|
|
18795
18811
|
// T1 · ADR-0164 amandemen 2026-09-23 · 22 laporan tanpa `Status:` di audit: agen fase mengakhiri
|
|
18796
18812
|
// gilirannya sambil menunggu proses latarnya sendiri, dan teks "menunggu" itu dibaca orchestrator
|
|
18797
18813
|
// sebagai laporan final. Giliranmu yang berakhir = laporanmu, jadi menunggu harus terjadi DI DALAM giliran.
|
|
@@ -18807,6 +18823,7 @@ var init_phase_agents = __esm({
|
|
|
18807
18823
|
"Klaim tanpa bukti bukan bukti. Maksimal 1200 kata."
|
|
18808
18824
|
].join("\n");
|
|
18809
18825
|
ATTACHMENT_NOTE = "Bila serah-terima menyebut manifest lampiran (`INDEX.md`), baca manifest itu dan lampiran yang relevan di awal fase.";
|
|
18826
|
+
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
18827
|
TERMINAL_ASK = /([Aa])jukan SATU pertanyaan per giliran ke manusia di terminal ini, tunggu jawabannya(?=[,.])/;
|
|
18811
18828
|
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
18829
|
TERMINAL_HANDOFF = "tulis ringkasan hasil + daftar pertanyaan yang belum terjawab ke terminal.";
|
|
@@ -18817,6 +18834,7 @@ var init_phase_agents = __esm({
|
|
|
18817
18834
|
"- 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
18835
|
"- JANGAN menulis `$HANOMAN_PHASE_FILE` dan JANGAN memperbaiki temuanmu sendiri \u2014 perbaikan milik agen Execute, marker milik orchestrator.",
|
|
18819
18836
|
`- JANGAN memanggil agen berawalan \`${PHASE_AGENT_PREFIX}\`.`,
|
|
18837
|
+
FOREGROUND_RULE,
|
|
18820
18838
|
"- 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
18839
|
].join("\n");
|
|
18822
18840
|
roleOf = (phase) => phase === "Execute" || phase === "Goal" ? "execute" : phase === "Kerjakan" || phase === "Verifikasi" ? "none" : "research";
|
|
@@ -20714,6 +20732,7 @@ __export(src_exports, {
|
|
|
20714
20732
|
GOAL_TUI_PASTE_LIMIT: () => GOAL_TUI_PASTE_LIMIT,
|
|
20715
20733
|
PHASE_CONTEXT_HEADER: () => PHASE_CONTEXT_HEADER,
|
|
20716
20734
|
PIPELINES: () => PIPELINES,
|
|
20735
|
+
PLAN_SHAPE: () => PLAN_SHAPE,
|
|
20717
20736
|
PROJECT_METHOD: () => PROJECT_METHOD,
|
|
20718
20737
|
READ_ONLY_DENIED_COMMANDS: () => READ_ONLY_DENIED_COMMANDS,
|
|
20719
20738
|
READ_ONLY_DENIED_OPERATORS: () => READ_ONLY_DENIED_OPERATORS,
|
|
@@ -50134,6 +50153,8 @@ var init_settings3 = __esm({
|
|
|
50134
50153
|
notifySound: "short",
|
|
50135
50154
|
notifyDecision: true,
|
|
50136
50155
|
notifyDecisionSound: "alert",
|
|
50156
|
+
notifyDecisionTelegram: true,
|
|
50157
|
+
// ADR-0226 · tak berefek tanpa bot token + allowlist
|
|
50137
50158
|
agentAccessEnabled: false,
|
|
50138
50159
|
// SPEC-257 · akses AI agent off sampai dibuka manusia
|
|
50139
50160
|
scheduler: SCHEDULER_DEFAULTS,
|
|
@@ -55948,6 +55969,300 @@ var init_live_phases = __esm({
|
|
|
55948
55969
|
}
|
|
55949
55970
|
});
|
|
55950
55971
|
|
|
55972
|
+
// src/services/telegram/client.ts
|
|
55973
|
+
var TelegramApiError, TelegramApiClient;
|
|
55974
|
+
var init_client = __esm({
|
|
55975
|
+
"src/services/telegram/client.ts"() {
|
|
55976
|
+
"use strict";
|
|
55977
|
+
TelegramApiError = class extends Error {
|
|
55978
|
+
constructor(method, code, message, retryAfter) {
|
|
55979
|
+
super(`Telegram ${method} gagal${code ? ` (${code})` : ""}: ${message}`);
|
|
55980
|
+
this.method = method;
|
|
55981
|
+
this.code = code;
|
|
55982
|
+
this.retryAfter = retryAfter;
|
|
55983
|
+
this.name = "TelegramApiError";
|
|
55984
|
+
}
|
|
55985
|
+
};
|
|
55986
|
+
TelegramApiClient = class {
|
|
55987
|
+
constructor(token, transport = (url2, init) => fetch(url2, init), baseUrl = "https://api.telegram.org") {
|
|
55988
|
+
this.token = token;
|
|
55989
|
+
this.transport = transport;
|
|
55990
|
+
this.baseUrl = baseUrl;
|
|
55991
|
+
}
|
|
55992
|
+
safe(message) {
|
|
55993
|
+
return message.split(this.token).join("[REDACTED]").replace(/https:\/\/api\.telegram\.org\/bot[^/\s]+/g, "https://api.telegram.org/bot[REDACTED]");
|
|
55994
|
+
}
|
|
55995
|
+
async call(method, body, signal) {
|
|
55996
|
+
let response;
|
|
55997
|
+
try {
|
|
55998
|
+
response = await this.transport(`${this.baseUrl}/bot${this.token}/${method}`, {
|
|
55999
|
+
method: "POST",
|
|
56000
|
+
headers: { "content-type": "application/json" },
|
|
56001
|
+
body: JSON.stringify(body),
|
|
56002
|
+
signal
|
|
56003
|
+
});
|
|
56004
|
+
} catch (error) {
|
|
56005
|
+
throw new TelegramApiError(method, void 0, this.safe(error.message || "network error"));
|
|
56006
|
+
}
|
|
56007
|
+
if (!response.ok) {
|
|
56008
|
+
const failed = await response.json().catch(() => null);
|
|
56009
|
+
throw new TelegramApiError(method, response.status, `HTTP ${response.status}`, failed?.parameters?.retry_after);
|
|
56010
|
+
}
|
|
56011
|
+
let envelope;
|
|
56012
|
+
try {
|
|
56013
|
+
envelope = await response.json();
|
|
56014
|
+
} catch {
|
|
56015
|
+
throw new TelegramApiError(method, response.status, "respons bukan JSON");
|
|
56016
|
+
}
|
|
56017
|
+
if (!envelope.ok || envelope.result === void 0) {
|
|
56018
|
+
throw new TelegramApiError(
|
|
56019
|
+
method,
|
|
56020
|
+
envelope.error_code,
|
|
56021
|
+
this.safe(envelope.description ?? "respons tidak valid"),
|
|
56022
|
+
envelope.parameters?.retry_after
|
|
56023
|
+
);
|
|
56024
|
+
}
|
|
56025
|
+
return envelope.result;
|
|
56026
|
+
}
|
|
56027
|
+
getMe() {
|
|
56028
|
+
return this.call("getMe", {});
|
|
56029
|
+
}
|
|
56030
|
+
async getUpdates(input) {
|
|
56031
|
+
if (!Number.isInteger(input.offset) || input.offset < 0) throw new Error("offset harus integer non-negatif");
|
|
56032
|
+
if (!Number.isInteger(input.limit) || input.limit < 1 || input.limit > 100) throw new Error("limit harus 1..100");
|
|
56033
|
+
if (!Number.isInteger(input.timeout) || input.timeout < 0 || input.timeout > 50) throw new Error("timeout harus 0..50");
|
|
56034
|
+
return this.call("getUpdates", {
|
|
56035
|
+
offset: input.offset,
|
|
56036
|
+
limit: input.limit,
|
|
56037
|
+
timeout: input.timeout,
|
|
56038
|
+
allowed_updates: ["message", "callback_query"]
|
|
56039
|
+
}, input.signal);
|
|
56040
|
+
}
|
|
56041
|
+
sendMessage(input) {
|
|
56042
|
+
return this.call("sendMessage", {
|
|
56043
|
+
chat_id: input.chatId,
|
|
56044
|
+
text: input.text,
|
|
56045
|
+
...input.replyMarkup ? { reply_markup: input.replyMarkup } : {}
|
|
56046
|
+
});
|
|
56047
|
+
}
|
|
56048
|
+
/**
|
|
56049
|
+
* SPEC-493 · indikator "typing…". Argumennya posisional (beda dari tetangganya) sesuai kontrak
|
|
56050
|
+
* yang diminta. Telegram TAK punya API stop-typing: statusnya padam sendiri ~5 detik sesudah
|
|
56051
|
+
* panggilan terakhir, dan pesan masuk apa pun langsung menghapusnya.
|
|
56052
|
+
*/
|
|
56053
|
+
sendChatAction(chatId, action = "typing") {
|
|
56054
|
+
return this.call("sendChatAction", { chat_id: chatId, action });
|
|
56055
|
+
}
|
|
56056
|
+
editMessageText(input) {
|
|
56057
|
+
return this.call("editMessageText", {
|
|
56058
|
+
chat_id: input.chatId,
|
|
56059
|
+
message_id: input.messageId,
|
|
56060
|
+
text: input.text
|
|
56061
|
+
});
|
|
56062
|
+
}
|
|
56063
|
+
answerCallbackQuery(input) {
|
|
56064
|
+
return this.call("answerCallbackQuery", {
|
|
56065
|
+
callback_query_id: input.callbackQueryId,
|
|
56066
|
+
...input.text ? { text: input.text } : {}
|
|
56067
|
+
});
|
|
56068
|
+
}
|
|
56069
|
+
};
|
|
56070
|
+
}
|
|
56071
|
+
});
|
|
56072
|
+
|
|
56073
|
+
// src/services/telegram/protocol.ts
|
|
56074
|
+
import { createHash as createHash10 } from "node:crypto";
|
|
56075
|
+
function normalizeTelegramCommand(text2) {
|
|
56076
|
+
const clean3 = text2.trim();
|
|
56077
|
+
const match = clean3.match(/^\/([a-z0-9_]+)(?:@[a-z0-9_]+)?(?:\s+([\s\S]*))?$/i);
|
|
56078
|
+
if (!match) return clean3;
|
|
56079
|
+
const args = match[2]?.trim().replace(/\s+/g, " ");
|
|
56080
|
+
return `/${match[1].toLowerCase()}${args ? ` ${args}` : ""}`;
|
|
56081
|
+
}
|
|
56082
|
+
function parseConfirmationCallback(data) {
|
|
56083
|
+
if (data.length > 64) return null;
|
|
56084
|
+
const match = data.match(/^tgcf:([A-Za-z0-9_-]{1,40}):(approve|deny)$/);
|
|
56085
|
+
return match ? { token: match[1], action: match[2] } : null;
|
|
56086
|
+
}
|
|
56087
|
+
function parseTelegramUpdate(update2, allowlist, maxText = 12e3) {
|
|
56088
|
+
const root = record(update2);
|
|
56089
|
+
const updateId = root && Number.isInteger(root.update_id) && Number(root.update_id) >= 0 ? Number(root.update_id) : null;
|
|
56090
|
+
if (!root || updateId === null) return { ok: false, updateId: null, reason: "malformed" };
|
|
56091
|
+
const callback = record(root.callback_query);
|
|
56092
|
+
const message = callback ? record(callback.message) : record(root.message);
|
|
56093
|
+
const from = callback ? record(callback.from) : record(message?.from);
|
|
56094
|
+
const chat = record(message?.chat);
|
|
56095
|
+
const chatId = typeof chat?.id === "number" && Number.isSafeInteger(chat.id) ? String(chat.id) : void 0;
|
|
56096
|
+
const userId = typeof from?.id === "number" && Number.isSafeInteger(from.id) ? String(from.id) : void 0;
|
|
56097
|
+
if (!message || !from || !chat || !chatId || !userId) return { ok: false, updateId, reason: "malformed", chatId, userId };
|
|
56098
|
+
if (chat.type !== "private") return { ok: false, updateId, reason: "group", chatId, userId };
|
|
56099
|
+
if (from.is_bot === true) return { ok: false, updateId, reason: "sender-bot", chatId, userId };
|
|
56100
|
+
if (!allowlist.has(userId)) return { ok: false, updateId, reason: "not-allowed", chatId, userId };
|
|
56101
|
+
const messageId = typeof message.message_id === "number" && Number.isInteger(message.message_id) ? message.message_id : null;
|
|
56102
|
+
if (messageId === null) return { ok: false, updateId, reason: "malformed", chatId, userId };
|
|
56103
|
+
if (callback) {
|
|
56104
|
+
const callbackQueryId = typeof callback.id === "string" ? callback.id : "";
|
|
56105
|
+
const data = typeof callback.data === "string" ? callback.data : "";
|
|
56106
|
+
const parsed = parseConfirmationCallback(data);
|
|
56107
|
+
if (!callbackQueryId || !parsed) return { ok: false, updateId, reason: "invalid-callback", chatId, userId };
|
|
56108
|
+
return {
|
|
56109
|
+
ok: true,
|
|
56110
|
+
input: {
|
|
56111
|
+
updateId,
|
|
56112
|
+
chatId,
|
|
56113
|
+
userId,
|
|
56114
|
+
messageId,
|
|
56115
|
+
kind: "callback",
|
|
56116
|
+
text: data,
|
|
56117
|
+
callbackQueryId,
|
|
56118
|
+
callbackToken: parsed.token,
|
|
56119
|
+
callbackAction: parsed.action
|
|
56120
|
+
}
|
|
56121
|
+
};
|
|
56122
|
+
}
|
|
56123
|
+
if (typeof message.text !== "string" || !message.text.trim()) {
|
|
56124
|
+
return { ok: false, updateId, reason: "non-text", chatId, userId };
|
|
56125
|
+
}
|
|
56126
|
+
const text2 = normalizeTelegramCommand(message.text);
|
|
56127
|
+
if (text2.length > maxText) return { ok: false, updateId, reason: "too-long", chatId, userId };
|
|
56128
|
+
return {
|
|
56129
|
+
ok: true,
|
|
56130
|
+
input: {
|
|
56131
|
+
updateId,
|
|
56132
|
+
chatId,
|
|
56133
|
+
userId,
|
|
56134
|
+
messageId,
|
|
56135
|
+
kind: text2.startsWith("/") ? "command" : "text",
|
|
56136
|
+
text: text2
|
|
56137
|
+
}
|
|
56138
|
+
};
|
|
56139
|
+
}
|
|
56140
|
+
function inboundDigest(input) {
|
|
56141
|
+
return createHash10("sha256").update(`${input.updateId}\0${input.chatId}\0${input.userId}\0${input.kind}\0${input.text}`, "utf8").digest("hex");
|
|
56142
|
+
}
|
|
56143
|
+
function sanitizeTelegramOutput(text2, exactSecrets = []) {
|
|
56144
|
+
let clean3 = text2.replace(ANSI, "").replace(CONTROLS, "");
|
|
56145
|
+
for (const secret of [...exactSecrets].filter(Boolean).sort((a, b) => b.length - a.length)) {
|
|
56146
|
+
clean3 = clean3.split(secret).join("[REDACTED]");
|
|
56147
|
+
}
|
|
56148
|
+
for (const pattern of CREDENTIAL_PATTERNS) clean3 = clean3.replace(pattern, "[REDACTED]");
|
|
56149
|
+
return clean3.trim();
|
|
56150
|
+
}
|
|
56151
|
+
function splitTelegramText(text2, max = 4096, exactSecrets = []) {
|
|
56152
|
+
if (!Number.isInteger(max) || max < 1) throw new Error("max harus integer positif");
|
|
56153
|
+
const clean3 = sanitizeTelegramOutput(text2, exactSecrets);
|
|
56154
|
+
if (!clean3) return [];
|
|
56155
|
+
const chunks = [];
|
|
56156
|
+
let rest = clean3;
|
|
56157
|
+
while (rest.length > max) {
|
|
56158
|
+
let cut = max;
|
|
56159
|
+
const before2 = rest.charCodeAt(cut - 1);
|
|
56160
|
+
const after = rest.charCodeAt(cut);
|
|
56161
|
+
if (before2 >= 55296 && before2 <= 56319 && after >= 56320 && after <= 57343) cut--;
|
|
56162
|
+
const window2 = rest.slice(0, cut);
|
|
56163
|
+
const newline2 = window2.lastIndexOf("\n");
|
|
56164
|
+
const space = window2.lastIndexOf(" ");
|
|
56165
|
+
const boundary = Math.max(newline2, space);
|
|
56166
|
+
if (boundary >= Math.floor(max / 2)) cut = boundary + 1;
|
|
56167
|
+
chunks.push(rest.slice(0, cut));
|
|
56168
|
+
rest = rest.slice(cut);
|
|
56169
|
+
}
|
|
56170
|
+
if (rest) chunks.push(rest);
|
|
56171
|
+
return chunks;
|
|
56172
|
+
}
|
|
56173
|
+
var record, ANSI, CONTROLS, CREDENTIAL_PATTERNS, TELEGRAM_GATEWAY_FAILURE_KIND, TELEGRAM_FINAL_REPLY_KINDS;
|
|
56174
|
+
var init_protocol = __esm({
|
|
56175
|
+
"src/services/telegram/protocol.ts"() {
|
|
56176
|
+
"use strict";
|
|
56177
|
+
record = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
56178
|
+
ANSI = /[\u001b\u009b](?:\][^\u0007]*(?:\u0007|\u001b\\)|\[[0-?]*[ -/]*[@-~]|[@-_])/g;
|
|
56179
|
+
CONTROLS = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/g;
|
|
56180
|
+
CREDENTIAL_PATTERNS = [
|
|
56181
|
+
/\b\d{6,}:[A-Za-z0-9_-]{20,}\b/g,
|
|
56182
|
+
/\bhnm_(?:agent_)?[A-Za-z0-9_-]{16,}\b/g,
|
|
56183
|
+
/\bsk-ant-[A-Za-z0-9_-]{16,}\b/g
|
|
56184
|
+
];
|
|
56185
|
+
TELEGRAM_GATEWAY_FAILURE_KIND = "gateway-failure";
|
|
56186
|
+
TELEGRAM_FINAL_REPLY_KINDS = /* @__PURE__ */ new Set([
|
|
56187
|
+
"final",
|
|
56188
|
+
"decision",
|
|
56189
|
+
"failure",
|
|
56190
|
+
"confirmation",
|
|
56191
|
+
TELEGRAM_GATEWAY_FAILURE_KIND
|
|
56192
|
+
]);
|
|
56193
|
+
}
|
|
56194
|
+
});
|
|
56195
|
+
|
|
56196
|
+
// src/services/telegram/notify.ts
|
|
56197
|
+
function recipients(raw) {
|
|
56198
|
+
return (raw ?? "").split(/[\s,]+/).filter((id2) => /^\d+$/.test(id2));
|
|
56199
|
+
}
|
|
56200
|
+
function dashboardOrigin(env = process.env) {
|
|
56201
|
+
const first = env.HANOMAN_CONTROL_ORIGINS?.split(",").map((o) => o.trim()).find(Boolean);
|
|
56202
|
+
if (!first) return null;
|
|
56203
|
+
try {
|
|
56204
|
+
return new URL(first).origin;
|
|
56205
|
+
} catch {
|
|
56206
|
+
return null;
|
|
56207
|
+
}
|
|
56208
|
+
}
|
|
56209
|
+
function decisionMessage(push, origin) {
|
|
56210
|
+
const title = push.title.length > TITLE_MAX ? `${push.title.slice(0, TITLE_MAX - 1)}\u2026` : push.title;
|
|
56211
|
+
return [
|
|
56212
|
+
"Sesi menunggu keputusanmu",
|
|
56213
|
+
title,
|
|
56214
|
+
push.specId ? `${push.specId} \xB7 sesi ${push.sessionId}` : `sesi ${push.sessionId}`,
|
|
56215
|
+
...origin ? [`${origin}/terminal`] : []
|
|
56216
|
+
].join("\n");
|
|
56217
|
+
}
|
|
56218
|
+
async function pushDecisionToTelegram(push, deps = {}) {
|
|
56219
|
+
try {
|
|
56220
|
+
const read2 = deps.read ?? effectiveStr;
|
|
56221
|
+
const setting = await (deps.getSetting ?? getSetting)();
|
|
56222
|
+
if (!setting.notifyDecisionTelegram) return 0;
|
|
56223
|
+
const botToken = read2("HANOMAN_TELEGRAM_BOT_TOKEN")?.trim();
|
|
56224
|
+
const ids = recipients(read2("HANOMAN_TELEGRAM_ALLOWED_USER_IDS"));
|
|
56225
|
+
if (!botToken || ids.length === 0) return 0;
|
|
56226
|
+
const controller = new AbortController();
|
|
56227
|
+
const timer11 = setTimeout(() => controller.abort(), deps.timeoutMs ?? SEND_TIMEOUT_MS);
|
|
56228
|
+
const send = deps.transport ?? ((url2, init) => fetch(url2, init));
|
|
56229
|
+
const client = new TelegramApiClient(botToken, (url2, init) => send(url2, { ...init, signal: controller.signal }));
|
|
56230
|
+
const text2 = decisionMessage(push, deps.origin === void 0 ? dashboardOrigin() : deps.origin);
|
|
56231
|
+
try {
|
|
56232
|
+
const results = await Promise.allSettled(ids.map((chatId) => client.sendMessage({ chatId, text: text2 })));
|
|
56233
|
+
let sent = 0;
|
|
56234
|
+
for (const [i, r] of results.entries()) {
|
|
56235
|
+
if (r.status === "fulfilled") {
|
|
56236
|
+
sent++;
|
|
56237
|
+
continue;
|
|
56238
|
+
}
|
|
56239
|
+
console.warn(
|
|
56240
|
+
`telegram: push keputusan ke ${ids[i]} gagal:`,
|
|
56241
|
+
sanitizeTelegramOutput(r.reason.message, [botToken])
|
|
56242
|
+
);
|
|
56243
|
+
}
|
|
56244
|
+
return sent;
|
|
56245
|
+
} finally {
|
|
56246
|
+
clearTimeout(timer11);
|
|
56247
|
+
}
|
|
56248
|
+
} catch (error) {
|
|
56249
|
+
console.warn("telegram: push keputusan gagal:", error.message);
|
|
56250
|
+
return 0;
|
|
56251
|
+
}
|
|
56252
|
+
}
|
|
56253
|
+
var SEND_TIMEOUT_MS, TITLE_MAX;
|
|
56254
|
+
var init_notify = __esm({
|
|
56255
|
+
"src/services/telegram/notify.ts"() {
|
|
56256
|
+
"use strict";
|
|
56257
|
+
init_config2();
|
|
56258
|
+
init_settings3();
|
|
56259
|
+
init_client();
|
|
56260
|
+
init_protocol();
|
|
56261
|
+
SEND_TIMEOUT_MS = 1e4;
|
|
56262
|
+
TITLE_MAX = 200;
|
|
56263
|
+
}
|
|
56264
|
+
});
|
|
56265
|
+
|
|
55951
56266
|
// src/services/notifications.ts
|
|
55952
56267
|
async function recordDrift(vpsId, vpsName, drift, snapshotId) {
|
|
55953
56268
|
if (drift.length === 0) return;
|
|
@@ -56068,6 +56383,7 @@ async function scanDecisions(read2 = liveDecisionsAsync) {
|
|
|
56068
56383
|
await prisma.notification.create({
|
|
56069
56384
|
data: { type: "decision", specId: s2.specId ?? null, sessionId: s2.id, projectId: s2.projectId || null, title }
|
|
56070
56385
|
});
|
|
56386
|
+
void pushDecisionToTelegram({ title, specId: s2.specId ?? null, sessionId: s2.id });
|
|
56071
56387
|
}
|
|
56072
56388
|
}
|
|
56073
56389
|
async function notificationsFeed(p3 = {}) {
|
|
@@ -56090,6 +56406,7 @@ var init_notifications2 = __esm({
|
|
|
56090
56406
|
init_db();
|
|
56091
56407
|
init_pty();
|
|
56092
56408
|
init_live_phases();
|
|
56409
|
+
init_notify();
|
|
56093
56410
|
awaiting = /* @__PURE__ */ new Set();
|
|
56094
56411
|
DEFAULT_FEED_TAKE = 50;
|
|
56095
56412
|
}
|
|
@@ -56167,6 +56484,45 @@ var init_device_sockets = __esm({
|
|
|
56167
56484
|
}
|
|
56168
56485
|
});
|
|
56169
56486
|
|
|
56487
|
+
// src/services/exit-guard.ts
|
|
56488
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
56489
|
+
import { writeFileSync as writeFileSync15 } from "node:fs";
|
|
56490
|
+
import { join as join29 } from "node:path";
|
|
56491
|
+
function watchdogScript(pid, graceMs) {
|
|
56492
|
+
const secs = Math.max(1, Math.ceil(graceMs / 1e3));
|
|
56493
|
+
return `s=$(ps -o lstart= -p ${pid}); sleep ${secs}; [ -n "$s" ] && [ "$(ps -o lstart= -p ${pid})" = "$s" ] && kill -9 ${pid}`;
|
|
56494
|
+
}
|
|
56495
|
+
function launchWatchdog(script2, env = process.env) {
|
|
56496
|
+
if (env.VITEST) return false;
|
|
56497
|
+
const child = spawn3("/bin/sh", ["-c", script2], { detached: true, stdio: "ignore" });
|
|
56498
|
+
child.on("error", () => {
|
|
56499
|
+
});
|
|
56500
|
+
child.unref();
|
|
56501
|
+
return true;
|
|
56502
|
+
}
|
|
56503
|
+
function exitGuarded(code, d = {}) {
|
|
56504
|
+
const pid = d.pid ?? process.pid;
|
|
56505
|
+
try {
|
|
56506
|
+
const write2 = d.writeIntent ?? ((p3, t) => writeFileSync15(p3, t, { mode: 384 }));
|
|
56507
|
+
write2(join29(d.home ?? resolveHome(), EXIT_INTENT_FILE), formatExitIntent(pid, code));
|
|
56508
|
+
} catch {
|
|
56509
|
+
}
|
|
56510
|
+
try {
|
|
56511
|
+
(d.launch ?? launchWatchdog)(watchdogScript(pid, d.graceMs ?? EXIT_WATCHDOG_MS));
|
|
56512
|
+
} catch {
|
|
56513
|
+
}
|
|
56514
|
+
(d.exit ?? ((c) => process.exit(c)))(code);
|
|
56515
|
+
}
|
|
56516
|
+
var EXIT_WATCHDOG_MS;
|
|
56517
|
+
var init_exit_guard = __esm({
|
|
56518
|
+
"src/services/exit-guard.ts"() {
|
|
56519
|
+
"use strict";
|
|
56520
|
+
init_src();
|
|
56521
|
+
init_src2();
|
|
56522
|
+
EXIT_WATCHDOG_MS = 5e3;
|
|
56523
|
+
}
|
|
56524
|
+
});
|
|
56525
|
+
|
|
56170
56526
|
// src/services/update.ts
|
|
56171
56527
|
import { readFileSync as readFileSync16 } from "node:fs";
|
|
56172
56528
|
import { resolve as resolve20, dirname as dirname13 } from "node:path";
|
|
@@ -56202,7 +56558,7 @@ function supervised() {
|
|
|
56202
56558
|
}
|
|
56203
56559
|
function requestRestartForUpdate() {
|
|
56204
56560
|
const delay2 = effectiveInt("HANOMAN_UPDATE_RESTART_DELAY_MS") ?? 250;
|
|
56205
|
-
setTimeout(() => (exiter ??
|
|
56561
|
+
setTimeout(() => (exiter ?? exitGuarded)(UPDATE_RESTART_EXIT), delay2);
|
|
56206
56562
|
}
|
|
56207
56563
|
async function maybeFetch() {
|
|
56208
56564
|
if (!effectiveBool("HANOMAN_UPDATE_FETCH")) return;
|
|
@@ -56248,6 +56604,7 @@ var init_update = __esm({
|
|
|
56248
56604
|
"use strict";
|
|
56249
56605
|
init_src();
|
|
56250
56606
|
init_config2();
|
|
56607
|
+
init_exit_guard();
|
|
56251
56608
|
UPDATE_COMMAND = "npm i -g hanoman@latest --prefer-online";
|
|
56252
56609
|
RESULT_TTL_MS = 15e3;
|
|
56253
56610
|
FETCH_TTL_MS = 5 * 6e4;
|
|
@@ -56744,7 +57101,7 @@ var init_runtime_defaults2 = __esm({
|
|
|
56744
57101
|
});
|
|
56745
57102
|
|
|
56746
57103
|
// src/services/agent-token.ts
|
|
56747
|
-
import { randomBytes as randomBytes2, createHash as
|
|
57104
|
+
import { randomBytes as randomBytes2, createHash as createHash12, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
56748
57105
|
function toAgentTokenView(t) {
|
|
56749
57106
|
return {
|
|
56750
57107
|
id: t.id,
|
|
@@ -56831,112 +57188,11 @@ var init_agent_token = __esm({
|
|
|
56831
57188
|
"src/services/agent-token.ts"() {
|
|
56832
57189
|
"use strict";
|
|
56833
57190
|
init_db();
|
|
56834
|
-
hash2 = (token) =>
|
|
57191
|
+
hash2 = (token) => createHash12("sha256").update(token).digest("hex");
|
|
56835
57192
|
AGENT_CREATOR_PREFIX = "agent:";
|
|
56836
57193
|
}
|
|
56837
57194
|
});
|
|
56838
57195
|
|
|
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
57196
|
// src/services/telegram/config.ts
|
|
56941
57197
|
async function setTelegramEngine(next) {
|
|
56942
57198
|
const cur = await getSetting();
|
|
@@ -56972,129 +57228,6 @@ var init_config4 = __esm({
|
|
|
56972
57228
|
}
|
|
56973
57229
|
});
|
|
56974
57230
|
|
|
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
57231
|
// src/services/telegram/runtime.ts
|
|
57099
57232
|
function setTelegramRuntime(input) {
|
|
57100
57233
|
registration = {
|
|
@@ -62748,15 +62881,15 @@ var init_client2 = __esm({
|
|
|
62748
62881
|
|
|
62749
62882
|
// src/services/logs/spool.ts
|
|
62750
62883
|
import { appendFile, mkdir as mkdir7, readFile as readFile7, readdir as readdir7, rm as rm6, stat as stat6 } from "node:fs/promises";
|
|
62751
|
-
import { join as
|
|
62884
|
+
import { join as join33 } from "node:path";
|
|
62752
62885
|
function spoolDir(lane) {
|
|
62753
|
-
return
|
|
62886
|
+
return join33(resolveDataDirs().home, "log-spool", lane);
|
|
62754
62887
|
}
|
|
62755
62888
|
async function currentSegment(lane) {
|
|
62756
62889
|
const dir2 = spoolDir(lane);
|
|
62757
62890
|
await mkdir7(dir2, { recursive: true, mode: 448 });
|
|
62758
62891
|
if (currentFile && currentBytes < LOG_SPOOL_SEGMENT_BYTES) return currentFile;
|
|
62759
|
-
currentFile =
|
|
62892
|
+
currentFile = join33(dir2, `${Date.now()}-${++segCounter}.ndjson`);
|
|
62760
62893
|
currentBytes = 0;
|
|
62761
62894
|
return currentFile;
|
|
62762
62895
|
}
|
|
@@ -62778,7 +62911,7 @@ async function readSpoolSegments(lane) {
|
|
|
62778
62911
|
const files2 = names.filter((n2) => n2.endsWith(".ndjson")).sort();
|
|
62779
62912
|
const out4 = [];
|
|
62780
62913
|
for (const name2 of files2) {
|
|
62781
|
-
const file =
|
|
62914
|
+
const file = join33(dir2, name2);
|
|
62782
62915
|
const raw = await readFile7(file, "utf8").catch(() => "");
|
|
62783
62916
|
const entries4 = raw.split("\n").filter(Boolean).map((l) => JSON.parse(l));
|
|
62784
62917
|
out4.push({ file, entries: entries4 });
|
|
@@ -63465,15 +63598,15 @@ var init_codex_version = __esm({
|
|
|
63465
63598
|
// src/services/agent-tool-catalog.ts
|
|
63466
63599
|
import { readFileSync as readFileSync26 } from "node:fs";
|
|
63467
63600
|
import { homedir as homedir13 } from "node:os";
|
|
63468
|
-
import { join as
|
|
63601
|
+
import { join as join49 } from "node:path";
|
|
63469
63602
|
function mcpServerNames(repoDir) {
|
|
63470
63603
|
const names = [];
|
|
63471
|
-
const claudeJson = readJson2(
|
|
63604
|
+
const claudeJson = readJson2(join49(home(), ".claude.json"));
|
|
63472
63605
|
names.push(...serversOf(claudeJson));
|
|
63473
63606
|
if (repoDir) {
|
|
63474
63607
|
const projects = claudeJson?.projects;
|
|
63475
63608
|
if (projects && typeof projects === "object") names.push(...serversOf(projects[repoDir]));
|
|
63476
|
-
names.push(...serversOf(readJson2(
|
|
63609
|
+
names.push(...serversOf(readJson2(join49(repoDir, ".mcp.json"))));
|
|
63477
63610
|
}
|
|
63478
63611
|
names.push(...codexServers());
|
|
63479
63612
|
return [...new Set(names.filter(Boolean))].sort((a, b) => a.localeCompare(b));
|
|
@@ -63502,7 +63635,7 @@ var init_agent_tool_catalog = __esm({
|
|
|
63502
63635
|
codexServers = () => {
|
|
63503
63636
|
let text2;
|
|
63504
63637
|
try {
|
|
63505
|
-
text2 = readFileSync26(
|
|
63638
|
+
text2 = readFileSync26(join49(home(), ".codex", "config.toml"), "utf8");
|
|
63506
63639
|
} catch {
|
|
63507
63640
|
return [];
|
|
63508
63641
|
}
|
|
@@ -70445,8 +70578,8 @@ init_local_binding();
|
|
|
70445
70578
|
init_session_id();
|
|
70446
70579
|
init_uploads();
|
|
70447
70580
|
import { mkdir as mkdir6, readdir as readdir5, readFile as readFile6, rm as rm4, writeFile as writeFile5 } from "node:fs/promises";
|
|
70448
|
-
import { join as
|
|
70449
|
-
var specAttachmentsDir = (repoDir, sessionId2) =>
|
|
70581
|
+
import { join as join30 } from "node:path";
|
|
70582
|
+
var specAttachmentsDir = (repoDir, sessionId2) => join30(repoDir, ".worktrees", ".attachments", sessionId2);
|
|
70450
70583
|
var INDEX = "INDEX.md";
|
|
70451
70584
|
var humanSize = (n2) => n2 >= 1024 * 1024 ? `${(n2 / 1024 / 1024).toFixed(1)} MB` : n2 >= 1024 ? `${Math.round(n2 / 1024)} KB` : `${n2} B`;
|
|
70452
70585
|
function uniqueName(taken, filename) {
|
|
@@ -70496,19 +70629,19 @@ async function syncSpecAttachmentsDir(specId, projectId) {
|
|
|
70496
70629
|
for (const a of rows) {
|
|
70497
70630
|
let bytes;
|
|
70498
70631
|
try {
|
|
70499
|
-
bytes = await readFile6(
|
|
70632
|
+
bytes = await readFile6(join30(uploadDir(), a.storageKey));
|
|
70500
70633
|
} catch {
|
|
70501
70634
|
continue;
|
|
70502
70635
|
}
|
|
70503
70636
|
const filename = uniqueName(taken, a.filename);
|
|
70504
|
-
const path =
|
|
70637
|
+
const path = join30(dir2, filename);
|
|
70505
70638
|
await writeFile5(path, bytes, { mode: 384 });
|
|
70506
70639
|
items.push({ filename, mimeType: a.mimeType, size: a.size, path });
|
|
70507
70640
|
}
|
|
70508
|
-
await writeFile5(
|
|
70641
|
+
await writeFile5(join30(dir2, INDEX), renderIndex(specId, items), { mode: 384 });
|
|
70509
70642
|
const keep = /* @__PURE__ */ new Set([INDEX, ...items.map((a) => a.filename)]);
|
|
70510
70643
|
for (const name2 of await readdir5(dir2)) {
|
|
70511
|
-
if (!keep.has(name2)) await rm4(
|
|
70644
|
+
if (!keep.has(name2)) await rm4(join30(dir2, name2), { recursive: true, force: true }).catch(() => {
|
|
70512
70645
|
});
|
|
70513
70646
|
}
|
|
70514
70647
|
return items;
|
|
@@ -73147,7 +73280,7 @@ init_session_phases();
|
|
|
73147
73280
|
init_stage_machine();
|
|
73148
73281
|
init_notifications2();
|
|
73149
73282
|
init_sync_notify();
|
|
73150
|
-
import { createHash as
|
|
73283
|
+
import { createHash as createHash11 } from "node:crypto";
|
|
73151
73284
|
var specNum = (id2) => Number.parseInt(id2.match(/\d+/)?.[0] ?? "0", 10);
|
|
73152
73285
|
var selectOf = (shape) => Object.fromEntries(
|
|
73153
73286
|
Object.keys(shape).filter((k) => k !== "blockedBy").map((k) => [k, true])
|
|
@@ -73239,7 +73372,7 @@ async function liveSignature() {
|
|
|
73239
73372
|
async function specsDigest() {
|
|
73240
73373
|
const agg = await prisma.spec.aggregate({ _count: { _all: true }, _max: { updatedAt: true }, _sum: { version: true } });
|
|
73241
73374
|
const db = `${agg._count._all}:${agg._max.updatedAt?.getTime() ?? 0}:${agg._sum.version ?? 0}`;
|
|
73242
|
-
return
|
|
73375
|
+
return createHash11("sha1").update(`${db}|${await liveSignature()}`).digest("hex");
|
|
73243
73376
|
}
|
|
73244
73377
|
async function liveSpecs(filter = {}) {
|
|
73245
73378
|
await liveOverlayTick();
|
|
@@ -73954,7 +74087,7 @@ async function docs_default(app3) {
|
|
|
73954
74087
|
// src/routes/ide.ts
|
|
73955
74088
|
init_src2();
|
|
73956
74089
|
import { basename as basename10 } from "node:path";
|
|
73957
|
-
import { spawn as
|
|
74090
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
73958
74091
|
|
|
73959
74092
|
// src/services/git-remotes.ts
|
|
73960
74093
|
import { execFile as execFile14 } from "node:child_process";
|
|
@@ -74024,7 +74157,7 @@ init_codex_trust();
|
|
|
74024
74157
|
// src/services/repo-fs.ts
|
|
74025
74158
|
import { lstat as lstat2, rename as rename4, rm as rm5 } from "node:fs/promises";
|
|
74026
74159
|
import { createWriteStream } from "node:fs";
|
|
74027
|
-
import { join as
|
|
74160
|
+
import { join as join31 } from "node:path";
|
|
74028
74161
|
import { randomUUID as randomUUID11 } from "node:crypto";
|
|
74029
74162
|
import { pipeline } from "node:stream/promises";
|
|
74030
74163
|
var EntryExistsError = class extends Error {
|
|
@@ -74085,7 +74218,7 @@ async function saveUpload2(repoDir, rel, source, opts2 = {}) {
|
|
|
74085
74218
|
if (current && !opts2.overwrite) return { status: "exists" };
|
|
74086
74219
|
if (current && !current.isFile())
|
|
74087
74220
|
throw new PathContainmentError("repository path ditolak: target bukan file regular");
|
|
74088
|
-
const temp =
|
|
74221
|
+
const temp = join31(entry2.parent, `.hanoman-${randomUUID11()}.tmp`);
|
|
74089
74222
|
try {
|
|
74090
74223
|
await pipeline(source, createWriteStream(temp, { flags: "wx", mode: 384 }));
|
|
74091
74224
|
if (opts2.isTruncated?.()) {
|
|
@@ -74369,7 +74502,7 @@ async function ide_default(app3) {
|
|
|
74369
74502
|
const ref = q2.ref || "HEAD";
|
|
74370
74503
|
if (!/^[\w./@{}-]+$/.test(ref)) return reply.code(400).send({ error: "ref tidak valid" });
|
|
74371
74504
|
const fmt = q2.format === "tar" ? "tar" : "zip";
|
|
74372
|
-
const child =
|
|
74505
|
+
const child = spawn4("git", ["archive", `--format=${fmt}`, "--end-of-options", ref], { cwd: repoDir });
|
|
74373
74506
|
reply.header("content-type", fmt === "zip" ? "application/zip" : "application/x-tar");
|
|
74374
74507
|
reply.header("content-disposition", `attachment; filename="${basename10(repoDir)}-${ref.replace(/[^\w.-]/g, "_")}.${fmt}"`);
|
|
74375
74508
|
return reply.send(child.stdout);
|
|
@@ -74576,14 +74709,14 @@ async function finishGraphOp(reply, id2, repoDir, r, verb) {
|
|
|
74576
74709
|
// src/routes/fs.ts
|
|
74577
74710
|
import { readdir as readdir6 } from "node:fs/promises";
|
|
74578
74711
|
import { homedir as homedir6 } from "node:os";
|
|
74579
|
-
import { resolve as resolve22, dirname as dirname14, join as
|
|
74712
|
+
import { resolve as resolve22, dirname as dirname14, join as join32 } from "node:path";
|
|
74580
74713
|
async function fs_default(app3) {
|
|
74581
74714
|
app3.get("/fs/browse", async (req, reply) => {
|
|
74582
74715
|
const q2 = req.query.path;
|
|
74583
74716
|
const dir2 = q2 && q2.trim() ? resolve22(q2.trim()) : homedir6();
|
|
74584
74717
|
try {
|
|
74585
74718
|
const ents = await readdir6(dir2, { withFileTypes: true });
|
|
74586
|
-
const entries4 = ents.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => ({ name: e.name, path:
|
|
74719
|
+
const entries4 = ents.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => ({ name: e.name, path: join32(dir2, e.name) })).sort((a, b) => a.name.localeCompare(b.name));
|
|
74587
74720
|
const parent = dirname14(dir2);
|
|
74588
74721
|
return { path: dir2, parent: parent === dir2 ? null : parent, entries: entries4 };
|
|
74589
74722
|
} catch {
|
|
@@ -74612,11 +74745,11 @@ init_src2();
|
|
|
74612
74745
|
init_db();
|
|
74613
74746
|
import { execFile as execFile15, spawnSync as spawnSync4 } from "node:child_process";
|
|
74614
74747
|
import { existsSync as existsSync16, readdirSync as readdirSync8, rmSync as rmSync9 } from "node:fs";
|
|
74615
|
-
import { join as
|
|
74748
|
+
import { join as join34 } from "node:path";
|
|
74616
74749
|
import { promisify as promisify14 } from "node:util";
|
|
74617
74750
|
|
|
74618
74751
|
// src/services/git-auth.ts
|
|
74619
|
-
import { chmodSync as chmodSync8, mkdirSync as mkdirSync15, writeFileSync as
|
|
74752
|
+
import { chmodSync as chmodSync8, mkdirSync as mkdirSync15, writeFileSync as writeFileSync16 } from "node:fs";
|
|
74620
74753
|
import { tmpdir as tmpdir6 } from "node:os";
|
|
74621
74754
|
import { dirname as dirname15 } from "node:path";
|
|
74622
74755
|
var gitAskpassPath = () => `${tmpdir6()}/hanoman-askpass/token.sh`;
|
|
@@ -74637,7 +74770,7 @@ var isGithubHttps = (remote) => /^https:\/\/github\.com\//.test(remote);
|
|
|
74637
74770
|
function gitAuthEnv(token) {
|
|
74638
74771
|
const path = gitAskpassPath();
|
|
74639
74772
|
mkdirSync15(dirname15(path), { recursive: true, mode: 448 });
|
|
74640
|
-
|
|
74773
|
+
writeFileSync16(path, ASKPASS_TOKEN, { mode: 448 });
|
|
74641
74774
|
chmodSync8(path, 448);
|
|
74642
74775
|
return { GIT_ASKPASS: path, GIT_TERMINAL_PROMPT: "0", HANOMAN_GIT_TOKEN: token };
|
|
74643
74776
|
}
|
|
@@ -74679,7 +74812,7 @@ var CheckoutError = class extends Error {
|
|
|
74679
74812
|
var CLONE_TIMEOUT_MS = 10 * 6e4;
|
|
74680
74813
|
var FETCH_TIMEOUT_MS = 2 * 6e4;
|
|
74681
74814
|
var AUTH_RE = /Authentication failed|could not read (Username|Password)|Invalid username or password|HTTP 40[13]|returned error: 40[13]|Repository not found|Permission denied \(publickey\)/i;
|
|
74682
|
-
var checkoutRoot = () =>
|
|
74815
|
+
var checkoutRoot = () => join34(resolveHome(), "projects");
|
|
74683
74816
|
var slug = (id2) => id2.toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || "project";
|
|
74684
74817
|
var sameRemote = (a, b) => {
|
|
74685
74818
|
const norm5 = (u) => httpsRemote(u).replace(/\.git$/, "").replace(/\/+$/, "").toLowerCase();
|
|
@@ -74713,7 +74846,7 @@ async function doClone(projectId, opts2) {
|
|
|
74713
74846
|
if (!project) throw new CheckoutError(`project "${projectId}" tidak ada`, "not-found");
|
|
74714
74847
|
if (!project.gitRemote) throw new CheckoutError(`project "${projectId}" tidak punya gitRemote untuk di-clone`, "no-remote");
|
|
74715
74848
|
const remote = project.gitRemote;
|
|
74716
|
-
const dest = opts2.dir ??
|
|
74849
|
+
const dest = opts2.dir ?? join34(checkoutRoot(), slug(project.id));
|
|
74717
74850
|
if (existsSync16(dest) && !isEmptyDir(dest)) {
|
|
74718
74851
|
const origin = originOf2(dest);
|
|
74719
74852
|
if (origin && sameRemote(origin, remote)) {
|
|
@@ -75297,13 +75430,13 @@ init_local_binding();
|
|
|
75297
75430
|
|
|
75298
75431
|
// src/services/terminal-diag.ts
|
|
75299
75432
|
import { appendFileSync as appendFileSync4, mkdirSync as mkdirSync16, statSync as statSync4, rmSync as rmSync10 } from "node:fs";
|
|
75300
|
-
import { join as
|
|
75433
|
+
import { join as join35 } from "node:path";
|
|
75301
75434
|
var DIAG_MAX_BYTES = 2 * 1024 * 1024;
|
|
75302
75435
|
var KINDS = /* @__PURE__ */ new Set(["key", "comp", "data", "ack", "pred"]);
|
|
75303
75436
|
var ID = /^[A-Za-z0-9_-]{1,64}$/;
|
|
75304
75437
|
function diagFile(home3, sessionId2) {
|
|
75305
75438
|
if (!ID.test(sessionId2)) throw new Error(`id sesi tak sah untuk diag: ${sessionId2}`);
|
|
75306
|
-
return
|
|
75439
|
+
return join35(home3, "diag", `${sessionId2}.jsonl`);
|
|
75307
75440
|
}
|
|
75308
75441
|
function usable(ev) {
|
|
75309
75442
|
if (!ev || typeof ev !== "object") return false;
|
|
@@ -75314,7 +75447,7 @@ function appendDiag(home3, sessionId2, events) {
|
|
|
75314
75447
|
const file = diagFile(home3, sessionId2);
|
|
75315
75448
|
const rows = events.filter(usable);
|
|
75316
75449
|
if (!rows.length) return;
|
|
75317
|
-
mkdirSync16(
|
|
75450
|
+
mkdirSync16(join35(home3, "diag"), { recursive: true });
|
|
75318
75451
|
try {
|
|
75319
75452
|
if (statSync4(file).size > DIAG_MAX_BYTES) rmSync10(file);
|
|
75320
75453
|
} catch {
|
|
@@ -75408,7 +75541,7 @@ init_session_event_spool();
|
|
|
75408
75541
|
init_ingress_policy();
|
|
75409
75542
|
import { constants as constants4 } from "node:fs";
|
|
75410
75543
|
import { mkdir as mkdir8, open as open3, readdir as readdir8, rm as rm7 } from "node:fs/promises";
|
|
75411
|
-
import { join as
|
|
75544
|
+
import { join as join36 } from "node:path";
|
|
75412
75545
|
var MAX_EVENT_BYTES = 1e6;
|
|
75413
75546
|
var MAX_FILES_PER_DRAIN = 1e3;
|
|
75414
75547
|
var SESSION_ID_RE = /^[a-z0-9_-]+$/;
|
|
@@ -75459,7 +75592,7 @@ async function drainSpool(app3, root, observation, host2) {
|
|
|
75459
75592
|
let readBuffer;
|
|
75460
75593
|
for (const session of await readdir8(root, { withFileTypes: true })) {
|
|
75461
75594
|
if (!session.isDirectory() || !SESSION_ID_RE.test(session.name)) continue;
|
|
75462
|
-
const dir2 =
|
|
75595
|
+
const dir2 = join36(root, session.name);
|
|
75463
75596
|
let entries4;
|
|
75464
75597
|
try {
|
|
75465
75598
|
entries4 = await readdir8(dir2, { withFileTypes: true });
|
|
@@ -75471,7 +75604,7 @@ async function drainSpool(app3, root, observation, host2) {
|
|
|
75471
75604
|
if (!entry2.isFile() || !entry2.name.endsWith(".json")) continue;
|
|
75472
75605
|
const eventAt = /^(\d{1,15})-/.exec(entry2.name)?.[1];
|
|
75473
75606
|
if (++examined > MAX_FILES_PER_DRAIN) return delivered;
|
|
75474
|
-
const path =
|
|
75607
|
+
const path = join36(dir2, entry2.name);
|
|
75475
75608
|
let payload;
|
|
75476
75609
|
let handle;
|
|
75477
75610
|
try {
|
|
@@ -76498,9 +76631,9 @@ init_session_sandbox();
|
|
|
76498
76631
|
init_session_sandbox();
|
|
76499
76632
|
import { execFile as execFile16 } from "node:child_process";
|
|
76500
76633
|
import { randomUUID as randomUUID13 } from "node:crypto";
|
|
76501
|
-
import { mkdirSync as mkdirSync17, rmSync as rmSync11, writeFileSync as
|
|
76634
|
+
import { mkdirSync as mkdirSync17, rmSync as rmSync11, writeFileSync as writeFileSync17 } from "node:fs";
|
|
76502
76635
|
import { constants as constants5, tmpdir as tmpdir7 } from "node:os";
|
|
76503
|
-
import { join as
|
|
76636
|
+
import { join as join37 } from "node:path";
|
|
76504
76637
|
var binFor = (agent) => agent === "antigravity" ? antigravityBin() : agent === "codex" ? effectiveStr("HANOMAN_CODEX_BIN") ?? "codex" : effectiveStr("HANOMAN_CLAUDE_BIN") ?? "claude";
|
|
76505
76638
|
function leadArgv(o) {
|
|
76506
76639
|
if (o.agent === "antigravity") return [
|
|
@@ -76538,11 +76671,11 @@ function leadProcess(prompt, o, env = process.env) {
|
|
|
76538
76671
|
const mode = env.HANOMAN_SESSION_SANDBOX ?? (resolveHardening(env) ? "required" : "off");
|
|
76539
76672
|
if (mode === "off") return { file, args: directArgs, cwd: o.cwd, cleanup: () => {
|
|
76540
76673
|
} };
|
|
76541
|
-
const promptDir =
|
|
76674
|
+
const promptDir = join37(tmpdir7(), "hanoman-prompts");
|
|
76542
76675
|
mkdirSync17(promptDir, { recursive: true, mode: 448 });
|
|
76543
|
-
const promptFile =
|
|
76544
|
-
|
|
76545
|
-
const workspace = o.cwd ??
|
|
76676
|
+
const promptFile = join37(promptDir, `oneshot-${randomUUID13()}`);
|
|
76677
|
+
writeFileSync17(promptFile, prompt, { flag: "wx", mode: 384 });
|
|
76678
|
+
const workspace = o.cwd ?? join37(tmpdir7(), `hanoman-oneshot-${randomUUID13()}`);
|
|
76546
76679
|
if (!o.cwd) mkdirSync17(workspace, { recursive: false, mode: 448 });
|
|
76547
76680
|
try {
|
|
76548
76681
|
const argsWithoutPrompt = directArgs.slice(0, -1);
|
|
@@ -77401,13 +77534,13 @@ async function run2(sessionId2, deps) {
|
|
|
77401
77534
|
// src/services/decision-guard.ts
|
|
77402
77535
|
init_src2();
|
|
77403
77536
|
init_db();
|
|
77404
|
-
import { mkdirSync as mkdirSync18, readFileSync as readFileSync18, readdirSync as readdirSync9, rmSync as rmSync12, statSync as statSync6, writeFileSync as
|
|
77405
|
-
import { join as
|
|
77537
|
+
import { mkdirSync as mkdirSync18, readFileSync as readFileSync18, readdirSync as readdirSync9, rmSync as rmSync12, statSync as statSync6, writeFileSync as writeFileSync18 } from "node:fs";
|
|
77538
|
+
import { join as join38 } from "node:path";
|
|
77406
77539
|
var SESSION_HEADER = "x-hanoman-session";
|
|
77407
77540
|
var SAFE_ID = /^[A-Za-z0-9._-]{1,200}$/;
|
|
77408
77541
|
var launchers = /* @__PURE__ */ new Map();
|
|
77409
77542
|
var STALE_MS = 30 * 24 * 60 * 6e4;
|
|
77410
|
-
var launcherDir = (env = process.env) =>
|
|
77543
|
+
var launcherDir = (env = process.env) => join38(resolveHome(env), "session-launchers");
|
|
77411
77544
|
function recordLauncher(sessionId2, agentId, env = process.env) {
|
|
77412
77545
|
if (!SAFE_ID.test(sessionId2)) return;
|
|
77413
77546
|
const known2 = launchersFor(sessionId2, env);
|
|
@@ -77415,11 +77548,11 @@ function recordLauncher(sessionId2, agentId, env = process.env) {
|
|
|
77415
77548
|
try {
|
|
77416
77549
|
const dir2 = launcherDir(env);
|
|
77417
77550
|
mkdirSync18(dir2, { recursive: true, mode: 448 });
|
|
77418
|
-
|
|
77551
|
+
writeFileSync18(join38(dir2, sessionId2), [...known2].join("\n"), { mode: 384 });
|
|
77419
77552
|
const now = Date.now();
|
|
77420
77553
|
for (const f of readdirSync9(dir2)) {
|
|
77421
77554
|
try {
|
|
77422
|
-
if (now - statSync6(
|
|
77555
|
+
if (now - statSync6(join38(dir2, f)).mtimeMs > STALE_MS) rmSync12(join38(dir2, f), { force: true });
|
|
77423
77556
|
} catch {
|
|
77424
77557
|
}
|
|
77425
77558
|
}
|
|
@@ -77431,7 +77564,7 @@ function launchersFor(sessionId2, env) {
|
|
|
77431
77564
|
if (hot) return hot;
|
|
77432
77565
|
const ids = /* @__PURE__ */ new Set();
|
|
77433
77566
|
try {
|
|
77434
|
-
for (const l of readFileSync18(
|
|
77567
|
+
for (const l of readFileSync18(join38(launcherDir(env), sessionId2), "utf8").split("\n")) if (l.trim()) ids.add(l.trim());
|
|
77435
77568
|
} catch {
|
|
77436
77569
|
}
|
|
77437
77570
|
launchers.set(sessionId2, ids);
|
|
@@ -78329,10 +78462,10 @@ import { readFileSync as readFileSync20 } from "node:fs";
|
|
|
78329
78462
|
|
|
78330
78463
|
// src/services/vps-ssh.ts
|
|
78331
78464
|
init_config2();
|
|
78332
|
-
import { spawn as
|
|
78333
|
-
import { mkdtempSync as mkdtempSync4, rmSync as rmSync13, writeFileSync as
|
|
78465
|
+
import { spawn as spawn5 } from "node:child_process";
|
|
78466
|
+
import { mkdtempSync as mkdtempSync4, rmSync as rmSync13, writeFileSync as writeFileSync19 } from "node:fs";
|
|
78334
78467
|
import { tmpdir as tmpdir8 } from "node:os";
|
|
78335
|
-
import { dirname as dirname16, join as
|
|
78468
|
+
import { dirname as dirname16, join as join39 } from "node:path";
|
|
78336
78469
|
var sshBin = () => effectiveStr("HANOMAN_SSH_BIN") ?? "ssh";
|
|
78337
78470
|
function consoleArgv(t) {
|
|
78338
78471
|
return [
|
|
@@ -78347,9 +78480,9 @@ function consoleArgv(t) {
|
|
|
78347
78480
|
];
|
|
78348
78481
|
}
|
|
78349
78482
|
function askpassScript() {
|
|
78350
|
-
const dir2 = mkdtempSync4(
|
|
78351
|
-
const path =
|
|
78352
|
-
|
|
78483
|
+
const dir2 = mkdtempSync4(join39(tmpdir8(), "hanoman-askpass-"));
|
|
78484
|
+
const path = join39(dir2, "askpass.sh");
|
|
78485
|
+
writeFileSync19(path, `#!/bin/sh
|
|
78353
78486
|
printf '%s' "$HANOMAN_SSH_PASSWORD"
|
|
78354
78487
|
`, { mode: 448 });
|
|
78355
78488
|
return path;
|
|
@@ -78383,7 +78516,7 @@ function sshExec(t, remoteCmd, opts2 = {}) {
|
|
|
78383
78516
|
HANOMAN_SSH_PASSWORD: opts2.password
|
|
78384
78517
|
} : process.env;
|
|
78385
78518
|
return new Promise((resolve25) => {
|
|
78386
|
-
const p3 =
|
|
78519
|
+
const p3 = spawn5(sshBin(), args, { stdio: ["pipe", "pipe", "pipe"], env });
|
|
78387
78520
|
let out4 = "";
|
|
78388
78521
|
const timer11 = setTimeout(() => p3.kill("SIGKILL"), opts2.timeoutMs ?? 6e4);
|
|
78389
78522
|
const done = (r) => {
|
|
@@ -78409,16 +78542,16 @@ function sshExec(t, remoteCmd, opts2 = {}) {
|
|
|
78409
78542
|
// src/services/vps-audit.ts
|
|
78410
78543
|
init_db();
|
|
78411
78544
|
import { existsSync as existsSync20, readFileSync as readFileSync19 } from "node:fs";
|
|
78412
|
-
import { join as
|
|
78545
|
+
import { join as join41 } from "node:path";
|
|
78413
78546
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
78414
78547
|
|
|
78415
78548
|
// src/runner/deps.ts
|
|
78416
78549
|
import { existsSync as existsSync19 } from "node:fs";
|
|
78417
|
-
import { dirname as dirname17, join as
|
|
78550
|
+
import { dirname as dirname17, join as join40 } from "node:path";
|
|
78418
78551
|
function repoRootFrom(startDir) {
|
|
78419
78552
|
let dir2 = startDir;
|
|
78420
78553
|
for (let i = 0; i < 8; i++) {
|
|
78421
|
-
if (existsSync19(
|
|
78554
|
+
if (existsSync19(join40(dir2, "pnpm-workspace.yaml"))) return dir2;
|
|
78422
78555
|
const parent = dirname17(dir2);
|
|
78423
78556
|
if (parent === dir2) break;
|
|
78424
78557
|
dir2 = parent;
|
|
@@ -78531,7 +78664,7 @@ var packagedScript = (f) => fileURLToPath4(new URL(`../scripts/vps/${f}`, import
|
|
|
78531
78664
|
var moduleDir = () => fileURLToPath4(new URL(".", import.meta.url));
|
|
78532
78665
|
var scriptPath = (f) => {
|
|
78533
78666
|
const packed = packagedScript(f);
|
|
78534
|
-
return existsSync20(packed) ? packed :
|
|
78667
|
+
return existsSync20(packed) ? packed : join41(repoRoot(moduleDir()), "server", "scripts", "vps", f);
|
|
78535
78668
|
};
|
|
78536
78669
|
async function itemStatesOf(vpsId) {
|
|
78537
78670
|
const rows = await prisma.vpsItemState.findMany({ where: { vpsId } });
|
|
@@ -78778,25 +78911,25 @@ init_config2();
|
|
|
78778
78911
|
import { execFileSync as execFileSync7 } from "node:child_process";
|
|
78779
78912
|
import { chmodSync as chmodSync9, copyFileSync, existsSync as existsSync21, mkdirSync as mkdirSync19, readFileSync as readFileSync22, unlinkSync as unlinkSync5 } from "node:fs";
|
|
78780
78913
|
import { homedir as homedir8 } from "node:os";
|
|
78781
|
-
import { join as
|
|
78914
|
+
import { join as join42 } from "node:path";
|
|
78782
78915
|
var keyDir = () => effectiveStr("HANOMAN_SSH_KEY_DIR") ?? resolveDataDirs().sshKeys;
|
|
78783
78916
|
var KEY_FILES = ["id_ed25519", "id_ed25519.pub"];
|
|
78784
78917
|
function adoptLegacyKey(dir2) {
|
|
78785
78918
|
if (effectiveStr("HANOMAN_SSH_KEY_DIR")) return;
|
|
78786
|
-
const legacy =
|
|
78919
|
+
const legacy = join42(homedir8(), ".hanoman");
|
|
78787
78920
|
if (legacy === dir2) return;
|
|
78788
|
-
if (!KEY_FILES.every((f) => existsSync21(
|
|
78921
|
+
if (!KEY_FILES.every((f) => existsSync21(join42(legacy, f)))) return;
|
|
78789
78922
|
mkdirSync19(dir2, { recursive: true, mode: 448 });
|
|
78790
78923
|
for (const f of KEY_FILES) {
|
|
78791
|
-
copyFileSync(
|
|
78792
|
-
chmodSync9(
|
|
78924
|
+
copyFileSync(join42(legacy, f), join42(dir2, f));
|
|
78925
|
+
chmodSync9(join42(dir2, f), 384);
|
|
78793
78926
|
}
|
|
78794
|
-
for (const f of KEY_FILES) unlinkSync5(
|
|
78927
|
+
for (const f of KEY_FILES) unlinkSync5(join42(legacy, f));
|
|
78795
78928
|
console.log(`vps: key SSH dipindah dari ${legacy} ke ${dir2} (SPEC-846 \u2014 satu batas backup)`);
|
|
78796
78929
|
}
|
|
78797
78930
|
function ensureHanomanKey() {
|
|
78798
78931
|
const dir2 = keyDir();
|
|
78799
|
-
const privPath =
|
|
78932
|
+
const privPath = join42(dir2, KEY_FILES[0]);
|
|
78800
78933
|
const pubPath = `${privPath}.pub`;
|
|
78801
78934
|
if (!existsSync21(privPath)) adoptLegacyKey(dir2);
|
|
78802
78935
|
if (!existsSync21(privPath)) {
|
|
@@ -79109,7 +79242,7 @@ init_config2();
|
|
|
79109
79242
|
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
79110
79243
|
import { readFileSync as readFileSync24 } from "node:fs";
|
|
79111
79244
|
import { homedir as homedir10, userInfo } from "node:os";
|
|
79112
|
-
import { join as
|
|
79245
|
+
import { join as join43 } from "node:path";
|
|
79113
79246
|
var USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
|
|
79114
79247
|
var TTL_MS2 = 3e4;
|
|
79115
79248
|
var lastOk = null;
|
|
@@ -79122,7 +79255,7 @@ var LABELS = {
|
|
|
79122
79255
|
var humanize = (k) => k.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
79123
79256
|
var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
79124
79257
|
function credsFile() {
|
|
79125
|
-
return
|
|
79258
|
+
return join43(effectiveStr("CLAUDE_CONFIG_DIR") ?? join43(homedir10(), ".claude"), ".credentials.json");
|
|
79126
79259
|
}
|
|
79127
79260
|
var securityRun = (args) => execFileSync8("security", args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
79128
79261
|
var osAccount = () => {
|
|
@@ -79207,14 +79340,14 @@ async function getLimits() {
|
|
|
79207
79340
|
// src/services/codex-limits.ts
|
|
79208
79341
|
import { open as open4, readdir as readdir9, stat as stat7 } from "node:fs/promises";
|
|
79209
79342
|
import { homedir as homedir11 } from "node:os";
|
|
79210
|
-
import { join as
|
|
79343
|
+
import { join as join44 } from "node:path";
|
|
79211
79344
|
var TTL_MS3 = 3e4;
|
|
79212
79345
|
var STALE_AFTER_MS = 12 * 36e5;
|
|
79213
79346
|
var TAIL_BYTES = 512 * 1024;
|
|
79214
79347
|
var MAX_FILES = 8;
|
|
79215
79348
|
var cache2 = null;
|
|
79216
79349
|
var freshUntil2 = 0;
|
|
79217
|
-
var codexSessionsDir = () =>
|
|
79350
|
+
var codexSessionsDir = () => join44(process.env.CODEX_HOME ?? join44(homedir11(), ".codex"), "sessions");
|
|
79218
79351
|
var UNAVAILABLE = { status: "unavailable", windows: [], fetchedAt: null, plan: null };
|
|
79219
79352
|
function windowLabel(minutes) {
|
|
79220
79353
|
if (minutes === 300) return "Sesi 5 jam";
|
|
@@ -79247,7 +79380,7 @@ async function recentRollouts(dir2) {
|
|
|
79247
79380
|
return [];
|
|
79248
79381
|
}
|
|
79249
79382
|
const stamped = await Promise.all(entries4.map(async (rel) => {
|
|
79250
|
-
const full =
|
|
79383
|
+
const full = join44(dir2, rel);
|
|
79251
79384
|
try {
|
|
79252
79385
|
return { full, mtime: (await stat7(full)).mtimeMs };
|
|
79253
79386
|
} catch {
|
|
@@ -79323,7 +79456,7 @@ init_config2();
|
|
|
79323
79456
|
import { execFileSync as execFileSync9 } from "node:child_process";
|
|
79324
79457
|
import { readFileSync as readFileSync25 } from "node:fs";
|
|
79325
79458
|
import { homedir as homedir12 } from "node:os";
|
|
79326
|
-
import { join as
|
|
79459
|
+
import { join as join45 } from "node:path";
|
|
79327
79460
|
var QUOTA_URL = "https://cloudcode-pa.googleapis.com/v1internal:retrieveUserQuotaSummary";
|
|
79328
79461
|
var USER_AGENT = "antigravity (hanoman)";
|
|
79329
79462
|
var TTL_MS4 = 6e4;
|
|
@@ -79345,10 +79478,10 @@ function readAgyToken(run6 = securityRun2) {
|
|
|
79345
79478
|
return null;
|
|
79346
79479
|
}
|
|
79347
79480
|
}
|
|
79348
|
-
var agyHome = () =>
|
|
79481
|
+
var agyHome = () => join45(homedir12(), ".gemini", "antigravity-cli");
|
|
79349
79482
|
function defaultProject() {
|
|
79350
79483
|
try {
|
|
79351
|
-
return readFileSync25(
|
|
79484
|
+
return readFileSync25(join45(agyHome(), "cache", "default_project_id.txt"), "utf8").trim() || null;
|
|
79352
79485
|
} catch {
|
|
79353
79486
|
return null;
|
|
79354
79487
|
}
|
|
@@ -79536,7 +79669,7 @@ init_src2();
|
|
|
79536
79669
|
init_src();
|
|
79537
79670
|
import { randomUUID as randomUUID14 } from "node:crypto";
|
|
79538
79671
|
import { readFile as readFile8, writeFile as writeFile6, rename as rename5, mkdir as mkdir9, rm as rm9 } from "node:fs/promises";
|
|
79539
|
-
import { dirname as dirname18, join as
|
|
79672
|
+
import { dirname as dirname18, join as join47 } from "node:path";
|
|
79540
79673
|
|
|
79541
79674
|
// src/services/model-catalog-parser.ts
|
|
79542
79675
|
init_zod();
|
|
@@ -79640,10 +79773,10 @@ init_src2();
|
|
|
79640
79773
|
init_antigravity_runtime();
|
|
79641
79774
|
init_config2();
|
|
79642
79775
|
init_session_sandbox();
|
|
79643
|
-
import { spawn as
|
|
79776
|
+
import { spawn as spawn6 } from "node:child_process";
|
|
79644
79777
|
import { mkdtemp as mkdtemp2, rm as rm8 } from "node:fs/promises";
|
|
79645
79778
|
import { tmpdir as tmpdir9 } from "node:os";
|
|
79646
|
-
import { join as
|
|
79779
|
+
import { join as join46 } from "node:path";
|
|
79647
79780
|
var MAX_BYTES = 4 * 1024 * 1024;
|
|
79648
79781
|
var TIMEOUT_MS = 2e4;
|
|
79649
79782
|
var quote3 = (s2) => "'" + s2.replaceAll("'", "'\\''") + "'";
|
|
@@ -79680,7 +79813,7 @@ function catalogCommand(agent, cwd, env) {
|
|
|
79680
79813
|
function runCatalogCommand(command, agent, cwd, env, timeoutMs = TIMEOUT_MS) {
|
|
79681
79814
|
return new Promise((resolve25, reject2) => {
|
|
79682
79815
|
assertAntigravityLaunch(agent, void 0, env);
|
|
79683
|
-
const child =
|
|
79816
|
+
const child = spawn6(command.bin, command.args, { cwd, env: agentEnvironment(agent, env), stdio: ["pipe", "pipe", "pipe"] });
|
|
79684
79817
|
let buffer = "", bytes = 0, settled = false;
|
|
79685
79818
|
let killTimer;
|
|
79686
79819
|
const finish = (error, result) => {
|
|
@@ -79742,7 +79875,7 @@ function runCatalogCommand(command, agent, cwd, env, timeoutMs = TIMEOUT_MS) {
|
|
|
79742
79875
|
}
|
|
79743
79876
|
async function probeModelCatalog(agent) {
|
|
79744
79877
|
assertAntigravityLaunch(agent);
|
|
79745
|
-
const cwd = await mkdtemp2(
|
|
79878
|
+
const cwd = await mkdtemp2(join46(tmpdir9(), "hanoman-models-"));
|
|
79746
79879
|
const env = agentEnvironment(agent, process.env);
|
|
79747
79880
|
for (const key of ["HANOMAN_CLAUDE_BIN", "HANOMAN_CODEX_BIN", "HANOMAN_PODMAN_BIN", "HANOMAN_ANTIGRAVITY_BIN"]) {
|
|
79748
79881
|
const value = effectiveStr(key);
|
|
@@ -79853,7 +79986,7 @@ async function writeModelCatalogCache(file, catalog, current = () => true) {
|
|
|
79853
79986
|
await rm9(temp, { force: true });
|
|
79854
79987
|
}
|
|
79855
79988
|
}
|
|
79856
|
-
var cacheFile = () =>
|
|
79989
|
+
var cacheFile = () => join47(resolveHome(), "model-catalog.json");
|
|
79857
79990
|
var modelCatalogService = createModelCatalogService({
|
|
79858
79991
|
probe: probeModelCatalog,
|
|
79859
79992
|
key: catalogProbeKey,
|
|
@@ -79892,8 +80025,9 @@ init_pty();
|
|
|
79892
80025
|
|
|
79893
80026
|
// src/services/restart.ts
|
|
79894
80027
|
init_src();
|
|
80028
|
+
init_exit_guard();
|
|
79895
80029
|
function requestConfigRestart(delayMs = 50) {
|
|
79896
|
-
setTimeout(() =>
|
|
80030
|
+
setTimeout(() => exitGuarded(CONFIG_RESTART_EXIT), delayMs);
|
|
79897
80031
|
}
|
|
79898
80032
|
function afterReplyFlushed(raw, fn, maxWaitMs = 5e3) {
|
|
79899
80033
|
let done = false;
|
|
@@ -79911,7 +80045,7 @@ function afterReplyFlushed(raw, fn, maxWaitMs = 5e3) {
|
|
|
79911
80045
|
timer11 = setTimeout(run6, maxWaitMs);
|
|
79912
80046
|
}
|
|
79913
80047
|
function requestManualRestart(delayMs = 250) {
|
|
79914
|
-
setTimeout(() =>
|
|
80048
|
+
setTimeout(() => exitGuarded(MANUAL_RESTART_EXIT), delayMs);
|
|
79915
80049
|
}
|
|
79916
80050
|
|
|
79917
80051
|
// src/routes/update.ts
|
|
@@ -81780,7 +81914,7 @@ init_src();
|
|
|
81780
81914
|
init_db();
|
|
81781
81915
|
import { gunzipSync, gzipSync } from "node:zlib";
|
|
81782
81916
|
import { mkdir as mkdir10, rename as rename6, writeFile as writeFile7 } from "node:fs/promises";
|
|
81783
|
-
import { join as
|
|
81917
|
+
import { join as join48 } from "node:path";
|
|
81784
81918
|
|
|
81785
81919
|
// src/services/device-auth.ts
|
|
81786
81920
|
function bearerToken2(req) {
|
|
@@ -82036,9 +82170,9 @@ async function sync_default(app3) {
|
|
|
82036
82170
|
const deviceId = req.device.id;
|
|
82037
82171
|
const withTranscriptKey = await Promise.all(parsed.data.entries.map(async (e) => {
|
|
82038
82172
|
if (!e.transcript) return e;
|
|
82039
|
-
const dir2 =
|
|
82173
|
+
const dir2 = join48(resolveDataDirs().home, "remote-transcripts", deviceId);
|
|
82040
82174
|
await mkdir10(dir2, { recursive: true, mode: 448 });
|
|
82041
|
-
const final =
|
|
82175
|
+
const final = join48(dir2, `${e.seq}.txt`);
|
|
82042
82176
|
const tmp = `${final}.tmp`;
|
|
82043
82177
|
await writeFile7(tmp, e.transcript, { encoding: "utf8", mode: 384 });
|
|
82044
82178
|
await rename6(tmp, final);
|
|
@@ -85325,7 +85459,7 @@ init_qa_attachment_transfer();
|
|
|
85325
85459
|
init_uploads();
|
|
85326
85460
|
import { createHash as createHash21 } from "node:crypto";
|
|
85327
85461
|
import { execFileSync as execFileSync10 } from "node:child_process";
|
|
85328
|
-
import { join as
|
|
85462
|
+
import { join as join50 } from "node:path";
|
|
85329
85463
|
var QaSessionError = class extends Error {
|
|
85330
85464
|
constructor(code, message, needsBind = false) {
|
|
85331
85465
|
super(message);
|
|
@@ -85352,7 +85486,7 @@ async function startQaFindingSession(d, fid) {
|
|
|
85352
85486
|
const saved = await saveSessionUpload(id2, bytes, a.mimeType);
|
|
85353
85487
|
files2.push(`${JSON.stringify(a.filename)} (${a.mimeType}): ${saved.path}`);
|
|
85354
85488
|
}
|
|
85355
|
-
const wt =
|
|
85489
|
+
const wt = join50(repoDir, ".worktrees", id2);
|
|
85356
85490
|
const branch = `qa/${id2.slice(3)}`;
|
|
85357
85491
|
const reused = realGit.worktreeAlive(wt);
|
|
85358
85492
|
try {
|
|
@@ -86891,9 +87025,9 @@ ${baru}`;
|
|
|
86891
87025
|
// src/services/portal-chat/workspace.ts
|
|
86892
87026
|
init_src();
|
|
86893
87027
|
init_db();
|
|
86894
|
-
import { mkdirSync as mkdirSync20, mkdtempSync as mkdtempSync5, rmSync as rmSync14, writeFileSync as
|
|
87028
|
+
import { mkdirSync as mkdirSync20, mkdtempSync as mkdtempSync5, rmSync as rmSync14, writeFileSync as writeFileSync20 } from "node:fs";
|
|
86895
87029
|
import { tmpdir as tmpdir10 } from "node:os";
|
|
86896
|
-
import { join as
|
|
87030
|
+
import { join as join51 } from "node:path";
|
|
86897
87031
|
var STAGE_LABEL = {
|
|
86898
87032
|
brainstorming: "Dirumuskan",
|
|
86899
87033
|
objective: "Dirumuskan",
|
|
@@ -86936,7 +87070,7 @@ function renderTicketDoc(tickets2) {
|
|
|
86936
87070
|
${baris.join("\n")}`;
|
|
86937
87071
|
}
|
|
86938
87072
|
async function buildChatWorkspace(projectId) {
|
|
86939
|
-
const dir2 = mkdtempSync5(
|
|
87073
|
+
const dir2 = mkdtempSync5(join51(tmpdir10(), "hanoman-portal-chat-"));
|
|
86940
87074
|
try {
|
|
86941
87075
|
const project = await prisma.project.findUnique({
|
|
86942
87076
|
where: { id: projectId },
|
|
@@ -86951,7 +87085,7 @@ async function buildChatWorkspace(projectId) {
|
|
|
86951
87085
|
});
|
|
86952
87086
|
const files2 = [];
|
|
86953
87087
|
const tulis = (rel, isi) => {
|
|
86954
|
-
|
|
87088
|
+
writeFileSync20(join51(dir2, rel), isi, { mode: 384 });
|
|
86955
87089
|
files2.push(rel);
|
|
86956
87090
|
};
|
|
86957
87091
|
tulis("project.md", renderProjectDoc(project));
|
|
@@ -86959,11 +87093,11 @@ async function buildChatWorkspace(projectId) {
|
|
|
86959
87093
|
tulis("laporan.md", renderTicketDoc(tickets2.map((t) => toPortalTicket(t, null))));
|
|
86960
87094
|
const prds = await listPrds(projectId);
|
|
86961
87095
|
if (prds.length) {
|
|
86962
|
-
mkdirSync20(
|
|
87096
|
+
mkdirSync20(join51(dir2, "dokumen"), { mode: 448 });
|
|
86963
87097
|
for (const prd of prds) {
|
|
86964
87098
|
const isi = await readPrd(projectId, prd.path);
|
|
86965
87099
|
if (!isi) continue;
|
|
86966
|
-
tulis(
|
|
87100
|
+
tulis(join51("dokumen", `${prd.slug.replaceAll("/", "-")}.md`), isi);
|
|
86967
87101
|
}
|
|
86968
87102
|
}
|
|
86969
87103
|
return { dir: dir2, files: files2, cleanup: () => rmSync14(dir2, { recursive: true, force: true }) };
|
|
@@ -87490,13 +87624,13 @@ init_db();
|
|
|
87490
87624
|
// src/services/bootstrap.ts
|
|
87491
87625
|
import { createHash as createHash22, randomBytes as randomBytes11, timingSafeEqual as timingSafeEqual5 } from "node:crypto";
|
|
87492
87626
|
import { chmod as chmod2, lstat as lstat3, mkdir as mkdir11, readFile as readFile9, unlink as unlink5, writeFile as writeFile8 } from "node:fs/promises";
|
|
87493
|
-
import { join as
|
|
87627
|
+
import { join as join52 } from "node:path";
|
|
87494
87628
|
var TTL_MS7 = 15 * 6e4;
|
|
87495
87629
|
var SETUP_TOKEN_FILE = "setup.token";
|
|
87496
87630
|
var BootstrapError = class extends Error {
|
|
87497
87631
|
code = "BOOTSTRAP_PROOF";
|
|
87498
87632
|
};
|
|
87499
|
-
var tokenPath = (home3) =>
|
|
87633
|
+
var tokenPath = (home3) => join52(home3, SETUP_TOKEN_FILE);
|
|
87500
87634
|
async function readStored(home3) {
|
|
87501
87635
|
try {
|
|
87502
87636
|
const info = await lstat3(tokenPath(home3));
|
|
@@ -88334,7 +88468,7 @@ init_event_log();
|
|
|
88334
88468
|
init_transcript_store();
|
|
88335
88469
|
init_src2();
|
|
88336
88470
|
import { readFile as readFile10 } from "node:fs/promises";
|
|
88337
|
-
import { join as
|
|
88471
|
+
import { join as join53 } from "node:path";
|
|
88338
88472
|
function decodeCursor(c) {
|
|
88339
88473
|
if (!c) return null;
|
|
88340
88474
|
try {
|
|
@@ -88372,7 +88506,7 @@ async function readRemoteTranscript(id2) {
|
|
|
88372
88506
|
const row = await prisma.logEntry.findUnique({ where: { id: id2 } });
|
|
88373
88507
|
if (!row || row.lane !== "transcript" || !row.transcriptKey) return null;
|
|
88374
88508
|
if (row.deviceId === "local") return readTranscript(row.transcriptKey);
|
|
88375
|
-
const path =
|
|
88509
|
+
const path = join53(resolveDataDirs().home, "remote-transcripts", row.transcriptKey);
|
|
88376
88510
|
try {
|
|
88377
88511
|
return await readFile10(path, "utf8");
|
|
88378
88512
|
} catch {
|
|
@@ -89639,9 +89773,9 @@ async function pruneLogs(now, retention, opts2 = {}) {
|
|
|
89639
89773
|
}
|
|
89640
89774
|
async function reconcileRemoteTranscripts() {
|
|
89641
89775
|
const { readdir: readdir10, unlink: unlink6, stat: stat8 } = await import("node:fs/promises");
|
|
89642
|
-
const { join:
|
|
89776
|
+
const { join: join54 } = await import("node:path");
|
|
89643
89777
|
const { resolveDataDirs: resolveDataDirs2 } = await Promise.resolve().then(() => (init_src2(), src_exports));
|
|
89644
|
-
const root =
|
|
89778
|
+
const root = join54(resolveDataDirs2().home, "remote-transcripts");
|
|
89645
89779
|
let deviceDirs;
|
|
89646
89780
|
try {
|
|
89647
89781
|
deviceDirs = await readdir10(root);
|
|
@@ -89655,16 +89789,16 @@ async function reconcileRemoteTranscripts() {
|
|
|
89655
89789
|
for (const dev of deviceDirs) {
|
|
89656
89790
|
let files2;
|
|
89657
89791
|
try {
|
|
89658
|
-
files2 = await readdir10(
|
|
89792
|
+
files2 = await readdir10(join54(root, dev));
|
|
89659
89793
|
} catch {
|
|
89660
89794
|
continue;
|
|
89661
89795
|
}
|
|
89662
89796
|
for (const f of files2) {
|
|
89663
89797
|
const key = `${dev}/${f}`;
|
|
89664
89798
|
if (referenced.has(key)) continue;
|
|
89665
|
-
const st = await stat8(
|
|
89799
|
+
const st = await stat8(join54(root, dev, f)).catch(() => null);
|
|
89666
89800
|
if (!st || Date.now() - st.mtimeMs < gracePeriodMs) continue;
|
|
89667
|
-
await unlink6(
|
|
89801
|
+
await unlink6(join54(root, dev, f)).catch(() => {
|
|
89668
89802
|
});
|
|
89669
89803
|
orphans++;
|
|
89670
89804
|
}
|
|
@@ -89851,6 +89985,7 @@ init_transcript_store();
|
|
|
89851
89985
|
init_client2();
|
|
89852
89986
|
init_dispatcher();
|
|
89853
89987
|
init_safe_outbound_request();
|
|
89988
|
+
init_exit_guard();
|
|
89854
89989
|
installSafeGlobalFetch();
|
|
89855
89990
|
var app2 = buildApp();
|
|
89856
89991
|
var port = Number(process.env.PORT ?? 8787);
|
|
@@ -89869,7 +90004,7 @@ var shutdown = createShutdown({
|
|
|
89869
90004
|
await app2.close();
|
|
89870
90005
|
await prisma.$disconnect();
|
|
89871
90006
|
},
|
|
89872
|
-
exit: (code) =>
|
|
90007
|
+
exit: (code) => exitGuarded(code)
|
|
89873
90008
|
});
|
|
89874
90009
|
process.on("SIGTERM", () => void shutdown("SIGTERM"));
|
|
89875
90010
|
process.on("SIGINT", () => void shutdown("SIGINT"));
|
|
@@ -89945,7 +90080,7 @@ bootstrapReady.then(async () => {
|
|
|
89945
90080
|
startOverviewRecorder();
|
|
89946
90081
|
}).catch((err) => {
|
|
89947
90082
|
console.error("listen gagal:", err);
|
|
89948
|
-
|
|
90083
|
+
exitGuarded(1);
|
|
89949
90084
|
});
|
|
89950
90085
|
/*! Bundled license information:
|
|
89951
90086
|
|