hanoman 0.9.7 → 0.9.8
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 +556 -117
- package/dist/server.js +1304 -462
- package/package.json +1 -1
- package/prisma/migrations/20260925120000_spec_project_notification_created_idx/migration.sql +6 -0
- package/prisma/schema.prisma +8 -0
- package/web/assets/{ChangelogScreen-C-C71Zn_.js → ChangelogScreen-BzuBQwxY.js} +1 -1
- package/web/assets/{DalangHanomanScreen-Go6ETy3r.js → DalangHanomanScreen-6Z1RoINb.js} +1 -1
- package/web/assets/{DocPreviewModal-CZ4rZ_h-.js → DocPreviewModal-CIDz4ZzG.js} +1 -1
- package/web/assets/{DocsWorkspace-BDfP0ML1.js → DocsWorkspace-CQ_EsxYz.js} +1 -1
- package/web/assets/{IdeScreen-DFRme37j.js → IdeScreen-C0dHzGLc.js} +1 -1
- package/web/assets/{LeadScreen-ASzNKRLX.js → LeadScreen-BHU9MYR1.js} +1 -1
- package/web/assets/{ReviewScreen-CyFWhxL1.js → ReviewScreen-DZi2r33C.js} +1 -1
- package/web/assets/SchedulerScreen-B5IdNK-B.js +1 -0
- package/web/assets/{SettingsScreen-siSgzEnH.js → SettingsScreen-CCGup_hQ.js} +1 -1
- package/web/assets/{TeamScreen-DzKZHKyR.js → TeamScreen-DV6IwXuC.js} +1 -1
- package/web/assets/{TerminalScreen-mLiyHYQV.js → TerminalScreen-DFnBKKa2.js} +1 -1
- package/web/assets/{TriageScreen-D5iyoK-3.js → TriageScreen-DoHczdu8.js} +1 -1
- package/web/assets/{VpsScreen-BhjULQaT.js → VpsScreen-CghVzou6.js} +1 -1
- package/web/assets/index-B3niUZnk.js +1014 -0
- package/web/assets/{live-Jy46oHWQ.js → live-fPTkukus.js} +1 -1
- package/web/index.html +1 -1
- package/web/assets/SchedulerScreen-Bii5m-K1.js +0 -1
- package/web/assets/index-CCJTT5hd.js +0 -1005
package/dist/server.js
CHANGED
|
@@ -4279,7 +4279,7 @@ var init_method_catalog = __esm({
|
|
|
4279
4279
|
"use strict";
|
|
4280
4280
|
DEFAULT_METHOD = "superpowers";
|
|
4281
4281
|
VERIFICATION_GATE = "superpowers:verification-before-completion";
|
|
4282
|
-
MATT_CLAUSE = "
|
|
4282
|
+
MATT_CLAUSE = "Skill mattpocock yang kontraknya mewawancarai manusia (`/grill-me`, `/to-spec`, `/triage`) JANGAN dipakai: pakai primitif model-invoked-nya untuk menguji rancanganmu sendiri, bukan untuk mewawancarai siapa pun. Hal yang masih ambigu dan akan mempengaruhi hasil tetap JANGAN diputuskan sendiri \u2014 bila kamu agen fase, daftarkan di `Keputusan terbuka:` laporanmu; bila tidak, ajukan lewat jalur tanya di klausa otonomi prompt ini. `to-tickets` di fase Plan dipakai HANYA sebagai penghasil berkas plan berkotak `- [ ]` di `docs/matt/plans/`, BUKAN penerbit tiket \u2014 hanoman sendiri adalah issue tracker-nya, jadi jangan menulis ke tracker eksternal mana pun.";
|
|
4283
4283
|
METHODS = {
|
|
4284
4284
|
// Isi `PHASE_SKILLS` (SPEC-166) dipindah APA ADANYA. Objective & Spec adalah keluaran skill
|
|
4285
4285
|
// brainstorming yang di-invoke di fase Brainstorm — sengaja tak punya entri sendiri. Fase reverse
|
|
@@ -4302,6 +4302,15 @@ var init_method_catalog = __esm({
|
|
|
4302
4302
|
// proses kaku. Yang tetap dijaga cuma pintu keluarnya.
|
|
4303
4303
|
Verifikasi: [VERIFICATION_GATE]
|
|
4304
4304
|
},
|
|
4305
|
+
// ADR-0170 P2 · `executing-plans` sendiri berkata "If subagents are available, use
|
|
4306
|
+
// superpowers:subagent-driven-development instead" — agen fase claude punya subagent.
|
|
4307
|
+
orchestratedPhaseSkills: {
|
|
4308
|
+
Execute: [
|
|
4309
|
+
"superpowers:subagent-driven-development",
|
|
4310
|
+
"superpowers:test-driven-development",
|
|
4311
|
+
VERIFICATION_GATE
|
|
4312
|
+
]
|
|
4313
|
+
},
|
|
4305
4314
|
exitSkills: [VERIFICATION_GATE],
|
|
4306
4315
|
requires: ["superpowers"],
|
|
4307
4316
|
install: {
|
|
@@ -4786,7 +4795,7 @@ var init_logs = __esm({
|
|
|
4786
4795
|
function phaseAgentName(phase) {
|
|
4787
4796
|
return PHASE_AGENT_PREFIX + phase.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
4788
4797
|
}
|
|
4789
|
-
var ORCHESTRATION_FLOWS, FLOW_PHASES, PHASE_AGENT_PREFIX, isPhaseAgentName, PHASE_EVIDENCE_GRACE_MS;
|
|
4798
|
+
var ORCHESTRATION_FLOWS, FLOW_PHASES, PHASE_AGENT_PREFIX, isPhaseAgentName, PHASE_EVIDENCE_GRACE_MS, REVIEWER_CELL, REVIEWER_FLOWS, REVIEWER_DEFAULTS;
|
|
4790
4799
|
var init_orchestration = __esm({
|
|
4791
4800
|
"../shared/src/orchestration.ts"() {
|
|
4792
4801
|
"use strict";
|
|
@@ -4822,6 +4831,12 @@ var init_orchestration = __esm({
|
|
|
4822
4831
|
PHASE_AGENT_PREFIX = "hanoman-fase-";
|
|
4823
4832
|
isPhaseAgentName = (name2) => name2.startsWith(PHASE_AGENT_PREFIX);
|
|
4824
4833
|
PHASE_EVIDENCE_GRACE_MS = 6e4;
|
|
4834
|
+
REVIEWER_CELL = "Review";
|
|
4835
|
+
REVIEWER_FLOWS = /* @__PURE__ */ new Set(["feature", "qa"]);
|
|
4836
|
+
REVIEWER_DEFAULTS = {
|
|
4837
|
+
claude: { model: "opus", effort: "high" },
|
|
4838
|
+
codex: { model: "gpt-5.6-sol", effort: "high" }
|
|
4839
|
+
};
|
|
4825
4840
|
}
|
|
4826
4841
|
});
|
|
4827
4842
|
|
|
@@ -5004,6 +5019,10 @@ function subagentClaudeModels() {
|
|
|
5004
5019
|
function claudeEfforts(modelId) {
|
|
5005
5020
|
return claudeModel(modelId)?.efforts ?? EFFORTS;
|
|
5006
5021
|
}
|
|
5022
|
+
function claudeSubagentEfforts(modelId) {
|
|
5023
|
+
const model = claudeModel(modelId);
|
|
5024
|
+
return model ? model.efforts ?? [] : EFFORTS;
|
|
5025
|
+
}
|
|
5007
5026
|
function coerceClaudeEffort(modelId, effort2) {
|
|
5008
5027
|
const levels = claudeModel(modelId)?.efforts;
|
|
5009
5028
|
if (!levels?.length || levels.includes(effort2)) return effort2;
|
|
@@ -5029,7 +5048,7 @@ function cmpVersion(a, b) {
|
|
|
5029
5048
|
}
|
|
5030
5049
|
return 0;
|
|
5031
5050
|
}
|
|
5032
|
-
var zProject, zBriefPayload, zQaPayload, zGoalPayload, zSpecBlocker, zSourceChange, zManualDone, zSpec, zSpecListItem, zSpecSlim, NOTIFY_SOUNDS, MODELS, CLAUDE_DEFAULT_ALIAS,
|
|
5051
|
+
var zProject, zBriefPayload, zQaPayload, zGoalPayload, zSpecBlocker, zSourceChange, zManualDone, zSpec, zSpecListItem, zSpecSlim, NOTIFY_SOUNDS, EFFORTS, MODELS, CLAUDE_DEFAULT_ALIAS, E_5_6, E_LUNA, E_BASE, CODEX_MODELS, RETIRED_CODEX_MODELS, zCodex, CODEX_DEFAULTS, zSourceCommon, zScheduler, SCHEDULER_DEFAULTS, zGoal, GOAL_DEFAULTS, zConflict, CONFLICT_DEFAULTS, CHANGELOG_ENGINE_DEFAULTS, zLeadEngine, zLead, LEAD_DEFAULTS, zPortalChat, PORTAL_CHAT_DEFAULTS, zPhaseCell, PHASE_OVERRIDE_LIMITS, zPhaseOverride, zPhaseOverrides, zFlowOrchestration, zOrchestration, ORCHESTRATION_DEFAULTS, zSetting, zNotification, zDocFile, zDeviceTokenView;
|
|
5033
5052
|
var init_entities = __esm({
|
|
5034
5053
|
"../shared/src/entities.ts"() {
|
|
5035
5054
|
"use strict";
|
|
@@ -5156,15 +5175,15 @@ var init_entities = __esm({
|
|
|
5156
5175
|
"marimba",
|
|
5157
5176
|
"fanfare"
|
|
5158
5177
|
];
|
|
5178
|
+
EFFORTS = ["xhigh", "high", "medium", "low", "max", "ultracode"];
|
|
5159
5179
|
MODELS = [
|
|
5160
|
-
{ id: "default", label: "Default (rekomendasi CLI)" },
|
|
5161
|
-
{ id: "opus", label: "Opus" },
|
|
5162
|
-
{ id: "sonnet", label: "Sonnet" },
|
|
5180
|
+
{ id: "default", label: "Default (rekomendasi CLI)", efforts: EFFORTS },
|
|
5181
|
+
{ id: "opus", label: "Opus", efforts: EFFORTS },
|
|
5182
|
+
{ id: "sonnet", label: "Sonnet", efforts: EFFORTS },
|
|
5163
5183
|
{ id: "haiku", label: "Haiku" },
|
|
5164
|
-
{ id: "fable", label: "Fable" }
|
|
5184
|
+
{ id: "fable", label: "Fable", efforts: EFFORTS }
|
|
5165
5185
|
];
|
|
5166
5186
|
CLAUDE_DEFAULT_ALIAS = "default";
|
|
5167
|
-
EFFORTS = ["xhigh", "high", "medium", "low", "max", "ultracode"];
|
|
5168
5187
|
E_5_6 = ["ultra", "max", "xhigh", "high", "medium", "low"];
|
|
5169
5188
|
E_LUNA = ["max", "xhigh", "high", "medium", "low"];
|
|
5170
5189
|
E_BASE = ["xhigh", "high", "medium", "low"];
|
|
@@ -5195,7 +5214,10 @@ var init_entities = __esm({
|
|
|
5195
5214
|
// cap sesi hidup
|
|
5196
5215
|
launchGuard: external_exports.object({
|
|
5197
5216
|
enabled: external_exports.boolean().default(true),
|
|
5198
|
-
maxLoadPerCore: external_exports.number().finite().positive().default(2.5)
|
|
5217
|
+
maxLoadPerCore: external_exports.number().finite().positive().default(2.5),
|
|
5218
|
+
// ADR-0170 · tolak launch baru bila memori tersedia di bawah ambang (persen). Default
|
|
5219
|
+
// konservatif — 46% terukur sehat di Mac mini 8 GB (ADR-0170), 15% memberi jarak aman.
|
|
5220
|
+
minMemAvailablePct: external_exports.number().finite().min(0).max(100).default(15)
|
|
5199
5221
|
}).default({}),
|
|
5200
5222
|
autonomy: external_exports.enum(["full-control", "butuh-keputusan"]).default("butuh-keputusan"),
|
|
5201
5223
|
// dikonsumsi daun #5
|
|
@@ -5409,21 +5431,27 @@ function resolvePhasePlan(input) {
|
|
|
5409
5431
|
if (!input.nativeAgents || cfg?.enabled === false) return null;
|
|
5410
5432
|
const cells = cfg?.[input.runtime] ?? {};
|
|
5411
5433
|
const coerce3 = input.runtime === "codex" ? coerceCodexEffort : coerceClaudeEffort;
|
|
5434
|
+
const resolve24 = (key, fallback2) => {
|
|
5435
|
+
const cell2 = cells[key];
|
|
5436
|
+
const override = input.phaseOverrides?.[key];
|
|
5437
|
+
const picked = override?.model ?? cell2?.model ?? fallback2.model;
|
|
5438
|
+
const model = input.runtime === "claude" && picked === CLAUDE_DEFAULT_ALIAS ? CLAUDE_SUBAGENT_INHERIT : picked;
|
|
5439
|
+
return { model, effort: coerce3(model, override?.effort ?? cell2?.effort ?? fallback2.effort) };
|
|
5440
|
+
};
|
|
5441
|
+
const reviewer = REVIEWER_FLOWS.has(input.flow) ? {
|
|
5442
|
+
agentName: phaseAgentName(REVIEWER_CELL),
|
|
5443
|
+
phase: "Execute",
|
|
5444
|
+
...resolve24(REVIEWER_CELL, REVIEWER_DEFAULTS[input.runtime])
|
|
5445
|
+
} : void 0;
|
|
5412
5446
|
return {
|
|
5413
5447
|
flow: input.flow,
|
|
5414
5448
|
runtime: input.runtime,
|
|
5415
|
-
phases: FLOW_PHASES[input.flow].map((phase) => {
|
|
5416
|
-
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
phase,
|
|
5422
|
-
agentName: phaseAgentName(phase),
|
|
5423
|
-
model,
|
|
5424
|
-
effort: coerce3(model, override?.effort ?? cell2?.effort ?? input.orchestrator.effort)
|
|
5425
|
-
};
|
|
5426
|
-
})
|
|
5449
|
+
phases: FLOW_PHASES[input.flow].map((phase) => ({
|
|
5450
|
+
phase,
|
|
5451
|
+
agentName: phaseAgentName(phase),
|
|
5452
|
+
...resolve24(phase, input.orchestrator)
|
|
5453
|
+
})),
|
|
5454
|
+
...reviewer ? { reviewer } : {}
|
|
5427
5455
|
};
|
|
5428
5456
|
}
|
|
5429
5457
|
var CLAUDE_SUBAGENT_INHERIT, CODEX_NATIVE_AGENTS_MIN_CLIENT;
|
|
@@ -5733,57 +5761,44 @@ var init_custom_agent = __esm({
|
|
|
5733
5761
|
});
|
|
5734
5762
|
|
|
5735
5763
|
// ../shared/src/builtin-app-agents.ts
|
|
5736
|
-
var
|
|
5764
|
+
var BUILTIN_APP_AGENTS;
|
|
5737
5765
|
var init_builtin_app_agents = __esm({
|
|
5738
5766
|
"../shared/src/builtin-app-agents.ts"() {
|
|
5739
5767
|
"use strict";
|
|
5740
|
-
LEAF_HANDOFF = [
|
|
5741
|
-
"Kamu agen daun: jangan mendelegasikan atau memanggil agen lain; eskalasi hanya ke parent.",
|
|
5742
|
-
"Awali laporan dengan Status: selesai | sebagian | terhalang | menunggu-keputusan. Sertakan simpulan, jangkar bukti,",
|
|
5743
|
-
"tingkat keyakinan, scope yang belum diperiksa dan langkah berikutnya. Maksimal 12 temuan utama",
|
|
5744
|
-
"dan 1200 kata; jangan mengklaim selesai bila acceptance penting belum terverifikasi."
|
|
5745
|
-
];
|
|
5746
5768
|
BUILTIN_APP_AGENTS = [
|
|
5747
5769
|
{
|
|
5748
5770
|
name: "product-designer",
|
|
5749
|
-
description: "Gunakan saat alur pengguna atau UI perlu
|
|
5771
|
+
description: "Gunakan saat alur pengguna atau UI perlu DIRANCANG dan diwujudkan sesuai design system: layout, hierarki, token, copy, state visual (loading/kosong/error/disabled), dan aksesibilitas dasar. Beda dari frontend-engineer (state, data fetching, langganan, dan performa React pada desain yang sudah ada) dan solution-architect (keputusan lintas modul, bukan implementasi). Menghasilkan artefak desain atau patch UI dalam scope parent dengan bukti render nyata.",
|
|
5750
5772
|
tools: ["Read", "Glob", "Grep", "Bash", "Write", "Edit", "WebFetch", "WebSearch"],
|
|
5751
5773
|
enabledByDefault: false,
|
|
5752
5774
|
activation: "smart",
|
|
5753
5775
|
effort: "medium",
|
|
5754
5776
|
workspacePolicy: "isolated-worktree",
|
|
5755
|
-
maxTurns:
|
|
5777
|
+
maxTurns: 60,
|
|
5756
5778
|
timeoutSeconds: null,
|
|
5757
5779
|
models: { claude: "sonnet", codex: "gpt-5.6-terra" },
|
|
5780
|
+
mentions: ["a11y-auditor"],
|
|
5758
5781
|
instructions: [
|
|
5759
|
-
"Kamu merancang pengalaman produk dan mewujudkan UI sesuai mandat parent.",
|
|
5760
|
-
"Masukan minimum: tujuan pengguna, alur prioritas, acceptance criteria, design system/reference,",
|
|
5761
|
-
"dan
|
|
5762
|
-
"
|
|
5763
|
-
"
|
|
5764
|
-
"
|
|
5765
|
-
"
|
|
5766
|
-
"
|
|
5767
|
-
"
|
|
5768
|
-
"4. Cocokkan hasil dengan acceptance criteria. Bila alat render/browser tersedia, periksa",
|
|
5769
|
-
" render nyata pada viewport dan state relevan serta catat hasil/screenshot. Bash atau",
|
|
5770
|
-
" WebFetch bukan bukti browser tersedia; bila tidak ada, laporkan gap verifikasi visual.",
|
|
5771
|
-
"Berhenti saat mandat terpenuhi atau keputusan produk/dependensi menghalangi; kerjakan bagian",
|
|
5772
|
-
"yang independen lalu eskalasi ke parent. Jangan mengaku merender atau menguji tanpa output.",
|
|
5773
|
-
"Handoff ringkas: artefak/berkas berubah, keputusan desain, state diperiksa, acceptance beserta",
|
|
5774
|
-
"bukti, ketidakpastian dan gap, serta langkah parent. Jangan membuat proses berat untuk UI kecil.",
|
|
5775
|
-
...LEAF_HANDOFF
|
|
5782
|
+
"Kamu merancang pengalaman produk dan mewujudkan UI sesuai design system serta mandat parent \u2014 bukan mengubah state/data-fetching React yang sudah benar (itu frontend-engineer) dan bukan keputusan lintas modul (itu solution-architect).",
|
|
5783
|
+
"Masukan minimum: tujuan pengguna, alur prioritas, acceptance criteria, design system/reference, dan ownership berkas atau artefak. Baca komponen dan token existing sebelum membuat pola baru.",
|
|
5784
|
+
"1. Petakan alur dan state VISUAL yang relevan: awal, loading, kosong, sukses, error, disabled, validasi, fokus/keyboard, serta hak akses. Tandai state yang tidak berlaku beserta alasan.",
|
|
5785
|
+
"2. Gunakan token dan komponen existing (design system proyek ini \u2014 docs/token repo, cari sebelum mengasumsikan lokasinya); periksa hirarki, copy, kontras, label, fokus, urutan keyboard, dan viewport sempit/lebar. Referensi web hanya bila perlu panduan primer.",
|
|
5786
|
+
"3. Buat artefak atau patch hanya dalam ownership yang diserahkan; jangan memperluas fitur, mengubah kontrak data/state React, atau mengubah backend tanpa mandat \u2014 serahkan itu ke frontend-engineer/parent.",
|
|
5787
|
+
"4. Cocokkan hasil dengan acceptance criteria dari RENDER NYATA, bukan pembacaan kode: periksa package.json proyek ini dulu \u2014 bila Playwright/Puppeteer terpasang, pakai itu; bila tidak, fallback ke Chrome headless (`--remote-debugging-port`) dikendalikan CDP (`/json/list`, `Page.navigate`, `Runtime.evaluate`, `Page.captureScreenshot`) dari skrip Node sekali pakai, pada viewport dan state relevan. Bash/WebFetch/kode saja BUKAN bukti render; tanpa CDP/Playwright tersedia, laporkan gap verifikasi visual secara eksplisit.",
|
|
5788
|
+
"Berhenti saat mandat terpenuhi atau keputusan produk/dependensi menghalangi; kerjakan bagian independen lalu eskalasi ke parent. Untuk UI interaktif (form, dialog, menu, live region), sarankan parent memanggil a11y-auditor sesudahnya.",
|
|
5789
|
+
"Handoff ringkas: artefak/berkas berubah, keputusan desain, state diperiksa, acceptance beserta bukti render, ketidakpastian dan gap, serta langkah parent. Jangan membuat proses berat untuk UI kecil.",
|
|
5790
|
+
"Jangan mengklaim selesai bila acceptance penting belum terverifikasi."
|
|
5776
5791
|
].join("\n")
|
|
5777
5792
|
},
|
|
5778
5793
|
{
|
|
5779
5794
|
name: "feature-builder",
|
|
5780
|
-
description: "Gunakan saat spec atau desain yang cukup jelas perlu menjadi fitur dengan batas ownership tegas, test relevan dan dokumentasi. Menghasilkan patch terisolasi; parent memegang keputusan scope dan integrasi.",
|
|
5795
|
+
description: "Gunakan saat spec atau desain yang cukup jelas perlu menjadi fitur di repo sesi ini (bukan repo lain) dengan batas ownership tegas, test relevan dan dokumentasi. Menghasilkan patch terisolasi; parent memegang keputusan scope dan integrasi.",
|
|
5781
5796
|
tools: ["Read", "Glob", "Grep", "Bash", "Write", "Edit"],
|
|
5782
5797
|
enabledByDefault: false,
|
|
5783
5798
|
activation: "smart",
|
|
5784
5799
|
effort: "medium",
|
|
5785
5800
|
workspacePolicy: "isolated-worktree",
|
|
5786
|
-
maxTurns:
|
|
5801
|
+
maxTurns: 80,
|
|
5787
5802
|
timeoutSeconds: null,
|
|
5788
5803
|
models: { claude: "sonnet", codex: "gpt-5.6-terra" },
|
|
5789
5804
|
instructions: [
|
|
@@ -5794,15 +5809,17 @@ var init_builtin_app_agents = __esm({
|
|
|
5794
5809
|
" eskalasi yang memengaruhi kontrak sebelum menebaknya, lanjutkan bagian independen.",
|
|
5795
5810
|
"2. Buat patch terkecil yang memenuhi mandat di worktree sendiri. Kamu tidak sendirian:",
|
|
5796
5811
|
" jangan membatalkan edit agen lain, mengubah worktree parent, atau memperluas refactor.",
|
|
5797
|
-
"3. Uji perilaku yang berubah dan batas kontrak yang berisiko.
|
|
5798
|
-
"
|
|
5812
|
+
"3. Uji perilaku yang berubah dan batas kontrak yang berisiko. Pasang dependensi & generate SEKALI di",
|
|
5813
|
+
" awal. Sebelum test/migrasi, pastikan `DATABASE_URL` tidak menunjuk DB operasional hanoman; jalankan",
|
|
5814
|
+
" dengan DB sekali-pakai. Catat perintah, output, dan kegagalan yang belum selesai.",
|
|
5799
5815
|
"4. Perbarui docs yang berubah sesuai konvensi repo. Nilai tiap acceptance dari keadaan akhir,",
|
|
5800
5816
|
" bukan hanya keberadaan patch atau test hijau. Parent memegang merge dan integrasi.",
|
|
5801
5817
|
"Berhenti setelah scope terpenuhi atau dependensi menghalangi; laporkan kekurangan input,",
|
|
5802
5818
|
"konflik ownership, dan verifikasi yang tidak tersedia. Jangan mengklaim test yang tidak jalan.",
|
|
5803
|
-
"
|
|
5804
|
-
"
|
|
5805
|
-
|
|
5819
|
+
"Bentuk laporan: `Status:` (enum hanoman, bukan DONE/BLOCKED milik skill lain) \xB7 tabel AC \u2192 putusan",
|
|
5820
|
+
"(terpenuhi/sebagian/belum) \u2192 bukti (perintah + potongan output, path:baris) \xB7 branch + SHA commit \xB7",
|
|
5821
|
+
"berkas berubah \xB7 `Keputusan terbuka:`. Commit hanya dengan `git add <path>` eksplisit; jangan cd ke worktree lain.",
|
|
5822
|
+
"Jangan mengklaim selesai bila acceptance penting belum terverifikasi."
|
|
5806
5823
|
].join("\n")
|
|
5807
5824
|
},
|
|
5808
5825
|
{
|
|
@@ -5834,12 +5851,12 @@ var init_builtin_app_agents = __esm({
|
|
|
5834
5851
|
"serahkan keputusan tradeoff ke parent. Jangan memperluas eksperimen ke layanan eksternal sendiri.",
|
|
5835
5852
|
"Handoff: baseline/before-after dan bukti mentah, bottleneck, patch, penerimaan/regresi,",
|
|
5836
5853
|
"ketidakpastian dan kondisi reproduksi. Nyatakan pengukuran yang belum dilakukan secara jelas.",
|
|
5837
|
-
|
|
5854
|
+
"Jangan mengklaim selesai bila acceptance penting belum terverifikasi."
|
|
5838
5855
|
].join("\n")
|
|
5839
5856
|
},
|
|
5840
5857
|
{
|
|
5841
5858
|
name: "product-analyst",
|
|
5842
|
-
description: "Gunakan saat brief atau permintaan fitur perlu diperjelas menjadi kebutuhan tervalidasi, prioritas/MVP dan acceptance criteria yang dapat diuji. Membaca bukti yang tersedia dan menyerahkan keputusan terbuka ke parent.",
|
|
5859
|
+
description: "Gunakan saat brief atau permintaan fitur MENTAH (belum ada spec) perlu diperjelas menjadi kebutuhan tervalidasi, prioritas/MVP dan acceptance criteria yang dapat diuji \u2014 bukan laporan pengguna yang sudah masuk (itu support-triager), dan bukan penulisan spec formal (itu fase Spec; agen ini hanya menyiapkan bahan mentahnya). Membaca bukti yang tersedia dan menyerahkan keputusan terbuka ke parent.",
|
|
5843
5860
|
tools: ["Read", "Glob", "Grep", "WebFetch", "WebSearch"],
|
|
5844
5861
|
enabledByDefault: false,
|
|
5845
5862
|
activation: "smart",
|
|
@@ -5864,24 +5881,25 @@ var init_builtin_app_agents = __esm({
|
|
|
5864
5881
|
"menentukan ke parent; jangan menghubungi pengguna atau menulis berkas/issue sendiri.",
|
|
5865
5882
|
"Handoff: kebutuhan dan sumber bukti, MVP/prioritas, acceptance, ketidakpastian/asumsi,",
|
|
5866
5883
|
"keputusan terbuka serta input berikutnya. Sesuaikan kedalaman analisis dengan ukuran tugas.",
|
|
5867
|
-
|
|
5884
|
+
"Jangan mengklaim selesai bila acceptance penting belum terverifikasi."
|
|
5868
5885
|
].join("\n")
|
|
5869
5886
|
},
|
|
5870
5887
|
{
|
|
5871
5888
|
name: "solution-architect",
|
|
5872
5889
|
description: "Gunakan saat keputusan lintas modul, kontrak data/API atau migrasi membutuhkan perbandingan pilihan dan tradeoff. Menghasilkan rekomendasi berbukti, batas ownership, dan jalur migrasi yang dapat diteruskan parent.",
|
|
5873
|
-
tools: ["Read", "Glob", "Grep", "WebFetch", "WebSearch"],
|
|
5890
|
+
tools: ["Read", "Glob", "Grep", "Bash", "WebFetch", "WebSearch"],
|
|
5874
5891
|
enabledByDefault: false,
|
|
5875
5892
|
activation: "smart",
|
|
5876
5893
|
effort: "high",
|
|
5877
5894
|
workspacePolicy: "read-only",
|
|
5878
|
-
maxTurns:
|
|
5895
|
+
maxTurns: 40,
|
|
5879
5896
|
timeoutSeconds: null,
|
|
5880
|
-
models: { claude: "
|
|
5897
|
+
models: { claude: "opus", codex: "gpt-5.6-sol" },
|
|
5881
5898
|
instructions: [
|
|
5882
5899
|
"Kamu menilai keputusan arsitektur; ownership-mu rekomendasi dan kontrak, bukan implementasi.",
|
|
5883
5900
|
"Masukan minimum: tujuan/acceptance, kendala, struktur existing, skala/beban yang diketahui,",
|
|
5884
5901
|
"batas modul dan keputusan sebelumnya. Baca docs serta kode relevan sebelum merancang.",
|
|
5902
|
+
"Gunakan ulang peta scout bila parent memberikannya; jangan menyapu ulang.",
|
|
5885
5903
|
"1. Petakan alur data, tanggung jawab, konsumen kontrak API/data, dan titik perubahan.",
|
|
5886
5904
|
" Bedakan fakta dengan jangkar path:baris dari asumsi atau informasi yang belum tersedia.",
|
|
5887
5905
|
"2. Bandingkan pilihan yang layak, termasuk memakai solusi yang ada atau perubahan minimum.",
|
|
@@ -5895,12 +5913,12 @@ var init_builtin_app_agents = __esm({
|
|
|
5895
5913
|
"mengubah kode, skema, infrastruktur atau menganggap rekomendasi sebagai keputusan diterima.",
|
|
5896
5914
|
"Handoff: opsi/tradeoff, rekomendasi, kontrak, jalur migrasi, bukti, ketidakpastian dan",
|
|
5897
5915
|
"keputusan parent. Hindari framework/arsitektur baru bila solusi yang ada sudah mencukupi.",
|
|
5898
|
-
|
|
5916
|
+
"Jangan mengklaim selesai bila acceptance penting belum terverifikasi."
|
|
5899
5917
|
].join("\n")
|
|
5900
5918
|
},
|
|
5901
5919
|
{
|
|
5902
5920
|
name: "operations-engineer",
|
|
5903
|
-
description: "Gunakan saat kesiapan rilis, migrasi, health check, monitoring, backup/restore atau rollback perlu disiapkan dan dibuktikan. Menghasilkan konfigurasi/runbook
|
|
5921
|
+
description: "Gunakan saat kesiapan rilis, migrasi, health check, monitoring, backup/restore atau rollback APLIKASI perlu disiapkan dan dibuktikan \u2014 runbook/kesiapan rilis dan aksi produksi yang butuh otorisasi eksplisit dikutip dari mandat. Untuk infra Cloudflare/VPS langsung dengan wewenang penuh (tanpa gerbang izin), pakai cloudflare-engineer/vps-engineer. Menghasilkan konfigurasi/runbook/patch dalam ownership parent serta daftar bukti operasi yang masih kurang.",
|
|
5904
5922
|
tools: ["Read", "Glob", "Grep", "Bash", "Write", "Edit", "WebFetch", "WebSearch"],
|
|
5905
5923
|
enabledByDefault: false,
|
|
5906
5924
|
activation: "smart",
|
|
@@ -5914,8 +5932,10 @@ var init_builtin_app_agents = __esm({
|
|
|
5914
5932
|
"Masukan minimum: target versi/environment, ownership konfigurasi/runbook, acceptance rilis,",
|
|
5915
5933
|
"topologi, akses/alat yang tersedia dan otorisasi operasi yang sudah diberikan.",
|
|
5916
5934
|
"Worktree hanya mengisolasi Git, bukan layanan eksternal, database, secrets atau produksi.",
|
|
5917
|
-
"Jangan menganggap berada di worktree memberi akses produksi.
|
|
5918
|
-
"
|
|
5935
|
+
"Jangan menganggap berada di worktree memberi akses produksi. Sebelum aksi produksi apa pun (deploy,",
|
|
5936
|
+
"migrate --remote, restart/reload layanan, DNS, secret put, hapus data): (1) kutip kalimat otorisasi",
|
|
5937
|
+
"eksplisit beserta targetnya dari mandat parent \u2014 bila tak ada, `Status: menunggu-keputusan` dan berhenti;",
|
|
5938
|
+
"(2) buktikan `git rev-parse HEAD` sama dengan commit target; (3) catat versi/ID rollback SEBELUM mengubah.",
|
|
5919
5939
|
"1. Baca konfigurasi dan prosedur existing. Identifikasi prasyarat rilis/migrasi, dependensi,",
|
|
5920
5940
|
" health check, monitoring/alert dan siapa pemilik responsnya. Rujuk docs primer bila perlu.",
|
|
5921
5941
|
"2. Buat patch/config/runbook sesuai mandat di worktree sendiri. Jangan menampilkan secrets.",
|
|
@@ -5928,12 +5948,12 @@ var init_builtin_app_agents = __esm({
|
|
|
5928
5948
|
"eksternal di luar mandat ke parent; jangan memperluas izin atau mengubah layanan sendiri.",
|
|
5929
5949
|
"Handoff: artefak, kesiapan rilis, bukti pemeriksaan/restore/rollback, ketidakpastian,",
|
|
5930
5950
|
"risiko dan langkah parent. Jangan membuat proses operasi berat untuk perubahan kecil.",
|
|
5931
|
-
|
|
5951
|
+
"Jangan mengklaim selesai bila acceptance penting belum terverifikasi."
|
|
5932
5952
|
].join("\n")
|
|
5933
5953
|
},
|
|
5934
5954
|
{
|
|
5935
5955
|
name: "support-triager",
|
|
5936
|
-
description: "Gunakan saat laporan pengguna perlu ditriase dari gejala, versi, langkah reproduksi dan dampak. Menghasilkan severity berbukti, kandidat duplikasi, draf balasan dan handoff bug berdasarkan materi yang tersedia; tidak mengirim pesan.",
|
|
5956
|
+
description: "Gunakan saat ada laporan pengguna KONKRET yang sudah masuk (tiket, keluhan, Help Center) dan perlu ditriase dari gejala, versi, langkah reproduksi dan dampak \u2014 bukan permintaan fitur yang belum berbentuk laporan (itu product-analyst), dan bukan penulisan spec (itu fase Spec). Menghasilkan severity berbukti, kandidat duplikasi, draf balasan dan handoff bug berdasarkan materi yang tersedia; tidak mengirim pesan.",
|
|
5937
5957
|
tools: ["Read", "Glob", "Grep", "WebFetch", "WebSearch"],
|
|
5938
5958
|
enabledByDefault: false,
|
|
5939
5959
|
activation: "smart",
|
|
@@ -5961,29 +5981,30 @@ var init_builtin_app_agents = __esm({
|
|
|
5961
5981
|
"eksplisit; serahkan draf dan kebutuhan eskalasi ke parent, tanpa memanggil agen lain.",
|
|
5962
5982
|
"Handoff: ringkasan bug, severity/alasan, versi, langkah dan expected/actual, bukti, kandidat",
|
|
5963
5983
|
"duplikasi, acceptance perbaikan, ketidakpastian serta draf balasan dan pemilik tindak lanjut.",
|
|
5964
|
-
|
|
5984
|
+
"Jangan mengklaim selesai bila acceptance penting belum terverifikasi."
|
|
5965
5985
|
].join("\n")
|
|
5966
5986
|
},
|
|
5967
5987
|
{
|
|
5968
5988
|
name: "knowledge-maintainer",
|
|
5969
|
-
description: "Gunakan saat panduan
|
|
5989
|
+
description: "Gunakan saat panduan pengguna/FAQ/Help Center/release notes perlu disusun dari perilaku yang SUDAH dirilis; docs internal/ADR ikut commit implementer, runbook milik operations-engineer.",
|
|
5970
5990
|
tools: ["Read", "Glob", "Grep", "Bash", "Write", "Edit", "WebFetch", "WebSearch"],
|
|
5971
5991
|
enabledByDefault: false,
|
|
5972
5992
|
activation: "smart",
|
|
5973
|
-
effort: "
|
|
5993
|
+
effort: "low",
|
|
5974
5994
|
workspacePolicy: "isolated-worktree",
|
|
5975
|
-
maxTurns:
|
|
5995
|
+
maxTurns: 30,
|
|
5976
5996
|
timeoutSeconds: null,
|
|
5977
5997
|
models: { claude: "sonnet", codex: "gpt-5.6-terra" },
|
|
5978
5998
|
instructions: [
|
|
5979
|
-
"Kamu menjaga pengetahuan
|
|
5999
|
+
"Kamu menjaga pengetahuan PENGGUNA agar sesuai dengan perilaku yang dapat dibuktikan sudah dirilis.",
|
|
5980
6000
|
"Masukan minimum: pembaca, tujuan dokumen/acceptance, target versi, ownership berkas, perubahan",
|
|
5981
6001
|
"produk dan bukti yang tersedia. Baca panduan/index yang ada sebelum menambah dokumen.",
|
|
5982
6002
|
"1. Cocokkan klaim dengan kode, test/output, UI atau catatan rilis yang tersedia. Catat sumber",
|
|
5983
6003
|
" dan versi; bedakan perilaku terverifikasi, kandidat implementasi dan rencana belum dirilis.",
|
|
5984
6004
|
" Spec atau commit saja tidak membuktikan fitur sudah dirilis ke pengguna.",
|
|
5985
|
-
"2. Susun panduan/FAQ/release notes
|
|
5986
|
-
" yang dapat dilihat, kegagalan umum dan pemulihan yang relevan. Hindari jargon internal.",
|
|
6005
|
+
"2. Susun panduan/FAQ/Help Center/release notes sesuai kebutuhan pembaca: prasyarat, langkah, hasil",
|
|
6006
|
+
" yang dapat dilihat, kegagalan umum dan pemulihan yang relevan. Hindari jargon internal. Docs",
|
|
6007
|
+
" internal/ADR ikut commit implementer, dan runbook operasi milik operations-engineer \u2014 bukan tugasmu.",
|
|
5987
6008
|
"3. Edit hanya artefak milikmu dalam worktree terisolasi, perbarui tautan/index sesuai aturan",
|
|
5988
6009
|
" repo dan konsistensi istilah. Gunakan referensi primer eksternal bila dokumen membutuhkannya.",
|
|
5989
6010
|
"4. Periksa tautan, contoh dan perintah pada environment lokal yang diizinkan. Render bila alat",
|
|
@@ -5993,19 +6014,409 @@ var init_builtin_app_agents = __esm({
|
|
|
5993
6014
|
"parent; jangan menerbitkan dokumen eksternal atau mengubah perilaku produk di luar mandat.",
|
|
5994
6015
|
"Handoff: artefak dan versi cakupan, klaim/sumber bukti, acceptance, ketidakpastian,",
|
|
5995
6016
|
"rencana yang dipisahkan, pemeriksaan dilakukan dan langkah parent. Jaga dokumen secukupnya.",
|
|
5996
|
-
|
|
6017
|
+
"Jangan mengklaim selesai bila acceptance penting belum terverifikasi."
|
|
5997
6018
|
].join("\n")
|
|
5998
6019
|
}
|
|
5999
6020
|
];
|
|
6000
6021
|
}
|
|
6001
6022
|
});
|
|
6002
6023
|
|
|
6024
|
+
// ../shared/src/builtin-domain-agents.ts
|
|
6025
|
+
var BUILTIN_DOMAIN_AGENTS;
|
|
6026
|
+
var init_builtin_domain_agents = __esm({
|
|
6027
|
+
"../shared/src/builtin-domain-agents.ts"() {
|
|
6028
|
+
"use strict";
|
|
6029
|
+
BUILTIN_DOMAIN_AGENTS = [
|
|
6030
|
+
{
|
|
6031
|
+
name: "frontend-engineer",
|
|
6032
|
+
description: "Gunakan saat komponen/hook React/TS/Vite di repo sesi ini perlu ditulis/diperbaiki agar berperilaku benar \u2014 state & data fetching, langganan WebSocket, efek, main thread, responsive \u2014 pada desain/scope yang SUDAH ditentukan parent. Beda dari product-designer (visual/design system), feature-builder (generalis) dan performance-engineer (profiling angka). Hasilkan patch, test komponen (bukan bukti render), dan bukti render CDP/Playwright bila tersedia.",
|
|
6033
|
+
tools: ["Read", "Glob", "Grep", "Bash", "Write", "Edit"],
|
|
6034
|
+
enabledByDefault: false,
|
|
6035
|
+
activation: "smart",
|
|
6036
|
+
effort: "medium",
|
|
6037
|
+
workspacePolicy: "isolated-worktree",
|
|
6038
|
+
maxTurns: 80,
|
|
6039
|
+
timeoutSeconds: null,
|
|
6040
|
+
models: { claude: "sonnet", codex: "gpt-5.6-terra" },
|
|
6041
|
+
mentions: ["a11y-auditor"],
|
|
6042
|
+
instructions: [
|
|
6043
|
+
"Kamu mengimplementasikan dan memperbaiki perilaku frontend React/TS/Vite yang didelegasikan parent \u2014 bukan merancang visual baru (itu product-designer).",
|
|
6044
|
+
"Masukan minimum: scope komponen/modul, desain atau acceptance yang sudah ada, base SHA, kontrak data/API yang dipakai, dan ownership berkas. Baca komponen dan pola hook tetangga sebelum menulis.",
|
|
6045
|
+
"1. Petakan state: sumber kebenaran (props/context/store) vs turunan vs data dari WebSocket/fetch \u2014 mana milik komponen ini, mana yang seharusnya diangkat/diturunkan. Jangan menduplikasi state yang sudah ada.",
|
|
6046
|
+
"2. Tulis/betulkan efek dan langganan dengan cleanup eksplisit: batalkan fetch/WebSocket/timer/listener saat unmount atau dependency berubah; dependency array lengkap; hindari setState tanpa guard di efek yang bisa memicu loop render.",
|
|
6047
|
+
"3. Jaga main thread: kerja berat (parsing/format data besar, stream frekuensi tinggi) dipecah/dibatch, jangan blok jalur render/input; list panjang pakai windowing bila relevan.",
|
|
6048
|
+
"4. Periksa responsive: viewport sempit dan lebar, overflow, urutan keyboard/fokus tidak pecah oleh perubahan layout.",
|
|
6049
|
+
"5. Tulis/perbarui test komponen (Vitest + Testing Library) untuk state yang berubah: loading, kosong, error, sukses, interaksi. Jalankan hanya test tersentuh sesuai konvensi repo.",
|
|
6050
|
+
"6. Bukti render: periksa package.json proyek ini dulu \u2014 bila Playwright/Puppeteer terpasang, pakai itu untuk screenshot/inspeksi DOM nyata. Bila tidak ada, fallback ke Chrome headless (`--remote-debugging-port`) dikendalikan CDP (`/json/list`, `Page.navigate`, `Runtime.evaluate`, `Page.captureScreenshot`) dari skrip Node sekali pakai. Test Vitest/jsdom BUKAN bukti render \u2014 jsdom tidak melakukan layout/paint nyata; tanpa CDP/Playwright tersedia, nyatakan gap-nya secara eksplisit dan jangan mengaku melihat render.",
|
|
6051
|
+
"Berhenti begitu scope terpenuhi atau keputusan desain/kontrak menghalangi; kerjakan bagian independen lalu eskalasi. Untuk komponen interaktif (form, dialog, menu, live region), sarankan parent memanggil a11y-auditor sesudahnya \u2014 agen ini tidak mengaudit WCAG.",
|
|
6052
|
+
"Handoff: komponen/hook yang berubah, state/efek yang diperbaiki beserta skenario pemicu sebelumnya, test yang ditulis/dijalankan + output, bukti render (screenshot/log CDP) atau gap-nya, viewport yang diperiksa.",
|
|
6053
|
+
"Jangan mengklaim selesai bila acceptance penting belum terverifikasi."
|
|
6054
|
+
].join("\n")
|
|
6055
|
+
},
|
|
6056
|
+
{
|
|
6057
|
+
name: "backend-engineer",
|
|
6058
|
+
description: "Gunakan saat route/handler backend (Fastify atau setara Node) perlu ditulis atau diubah \u2014 skema validasi, pemetaan error ke status HTTP, idempotensi mutasi, konkurensi pada state bersama \u2014 dan dibuktikan lewat boot server lokal + curl, bukan hanya lolos test unit. Berbeda dari feature-builder (generalis lintas domain apa pun): agen ini spesifik jalur request-response backend dan wajib menelusuri SEMUA cabang sebelum melapor selesai. Pasangan review: api-contract-auditor.",
|
|
6059
|
+
tools: ["Read", "Glob", "Grep", "Bash", "Write", "Edit"],
|
|
6060
|
+
enabledByDefault: false,
|
|
6061
|
+
activation: "smart",
|
|
6062
|
+
effort: "medium",
|
|
6063
|
+
workspacePolicy: "isolated-worktree",
|
|
6064
|
+
maxTurns: 80,
|
|
6065
|
+
timeoutSeconds: null,
|
|
6066
|
+
models: { claude: "sonnet", codex: "gpt-5.6-terra" },
|
|
6067
|
+
mentions: ["api-contract-auditor"],
|
|
6068
|
+
instructions: [
|
|
6069
|
+
"Kamu menulis atau mengubah route/handler backend (Fastify atau setara Node) sesuai mandat",
|
|
6070
|
+
"parent \u2014 bukan generalis lintas domain seperti feature-builder.",
|
|
6071
|
+
"Masukan minimum: endpoint/kontrak yang diminta (method+path, skema request/response),",
|
|
6072
|
+
"acceptance criteria, ownership berkas, base SHA.",
|
|
6073
|
+
"",
|
|
6074
|
+
"Prosedur:",
|
|
6075
|
+
"1. Baca skema (Zod/TypeBox/JSON Schema) dan handler tetangga untuk pola error\u2192status yang",
|
|
6076
|
+
" sudah berlaku di repo; jangan menciptakan pola baru tanpa alasan.",
|
|
6077
|
+
"2. Implementasikan/ubah handler: validasi skema di boundary request, lalu untuk SETIAP cabang",
|
|
6078
|
+
" \u2014 sukses, validasi gagal, objek tak ditemukan, konflik, error terlempar/rejection, timeout",
|
|
6079
|
+
" atau dependensi (DB/upstream) gagal \u2014 kembalikan status HTTP dan bentuk body yang konsisten",
|
|
6080
|
+
" dengan kontrak, bukan default framework yang membocorkan stack.",
|
|
6081
|
+
"3. Endpoint mutatif (POST/PUT/PATCH/DELETE): pastikan idempoten terhadap retry klien/proxy \u2014",
|
|
6082
|
+
" unique constraint, upsert, idempotency key, atau cek versi. Identifikasi state yang bisa",
|
|
6083
|
+
" diakses lebih dari satu request/sesi bersamaan dan tutup jendela check-then-act dengan",
|
|
6084
|
+
" transaksi/lock/versi; jangan berasumsi single-writer tanpa memeriksanya.",
|
|
6085
|
+
"4. Await dan catch semua promise di jalur request; jangan biarkan unhandled rejection mematikan",
|
|
6086
|
+
" proses atau membocorkan stack ke response.",
|
|
6087
|
+
"5. Tulis/ubah test API yang menutup cabang di langkah 2-3, lalu BOOT server lokal sungguhan dan",
|
|
6088
|
+
" curl endpoint yang berubah \u2014 catat perintah dan potongan output persis, bukan hanya klaim",
|
|
6089
|
+
" test hijau. Sebelum test/boot, pastikan `DATABASE_URL` bukan DB operasional hanoman.",
|
|
6090
|
+
"6. Bila skema berubah, periksa kompatibilitas klien existing (frontend/CLI/MCP/test) yang",
|
|
6091
|
+
" memanggil endpoint ini.",
|
|
6092
|
+
"",
|
|
6093
|
+
"Gerbang bukti:",
|
|
6094
|
+
'- Klaim "endpoint benar" hanya sah bila tiap cabang error dan status HTTP-nya dibuktikan lewat',
|
|
6095
|
+
" test atau curl nyata (perintah + output), bukan pembacaan kode saja.",
|
|
6096
|
+
"- Endpoint mutatif tanpa penjaga idempotensi yang terbukti adalah keputusan terbuka, bukan",
|
|
6097
|
+
" sesuatu yang diasumsikan aman.",
|
|
6098
|
+
"- Jangan mengklaim menjalankan test/boot/curl tanpa output yang benar-benar kamu terima.",
|
|
6099
|
+
"",
|
|
6100
|
+
"Bentuk laporan (selain kontrak serah-terima standar): tabel per endpoint \u2014 method+path",
|
|
6101
|
+
"(jangkar path:baris) \xB7 cabang diperiksa \xB7 status/body aktual vs dijanjikan skema \xB7 idempotensi",
|
|
6102
|
+
"(aman/berisiko/tidak berlaku) \xB7 bukti (perintah + potongan output). Pasangan review:",
|
|
6103
|
+
"api-contract-auditor memverifikasi klaim ini secara independen sebelum merge."
|
|
6104
|
+
].join("\n")
|
|
6105
|
+
},
|
|
6106
|
+
{
|
|
6107
|
+
name: "database-engineer",
|
|
6108
|
+
description: "Gunakan saat skema atau migration database (Prisma/SQL/ORM lain) perlu ditulis/diubah \u2014 kolom/index/constraint, backfill, urutan deploy kompatibel \u2014 dan dibuktikan aman lewat apply di DB sekali-pakai (TIDAK PERNAH DB operasional) plus EXPLAIN setara engine. Beda dari feature-builder (generalis) dan schema-migration-auditor (read-only). Ikuti konvensi skema repo ini (migration + ADR bila AGENTS.md/CLAUDE.md mewajibkan). Pasangan review: schema-migration-auditor.",
|
|
6109
|
+
tools: ["Read", "Glob", "Grep", "Bash", "Write", "Edit"],
|
|
6110
|
+
enabledByDefault: false,
|
|
6111
|
+
activation: "smart",
|
|
6112
|
+
effort: "high",
|
|
6113
|
+
workspacePolicy: "isolated-worktree",
|
|
6114
|
+
maxTurns: 60,
|
|
6115
|
+
timeoutSeconds: null,
|
|
6116
|
+
models: { claude: "opus", codex: "gpt-5.6-terra" },
|
|
6117
|
+
mentions: ["schema-migration-auditor"],
|
|
6118
|
+
instructions: [
|
|
6119
|
+
"Kamu menulis skema dan migration database (Prisma/SQL) sesuai mandat parent, lalu MEMBUKTIKAN",
|
|
6120
|
+
"lewat eksekusi nyata bahwa migration aman dan indeks dipakai \u2014 bukan generalis seperti",
|
|
6121
|
+
"feature-builder, dan bukan schema-migration-auditor (read-only, mengaudit klaim tanpa",
|
|
6122
|
+
"menjalankan apa pun).",
|
|
6123
|
+
"Masukan minimum: perubahan skema yang diminta, pola query yang harus didukung, acceptance",
|
|
6124
|
+
"criteria, ownership berkas, base SHA.",
|
|
6125
|
+
"",
|
|
6126
|
+
"Prosedur:",
|
|
6127
|
+
"1. Baca skema existing, riwayat migration tabel yang sama, dan pola query nyata (grep",
|
|
6128
|
+
" call-site ORM: WHERE/JOIN/ORDER BY/GROUP BY) sebelum menambah kolom/index/constraint;",
|
|
6129
|
+
" jangan menebak kebutuhan indeks dari asumsi.",
|
|
6130
|
+
"2. Tulis migration eksplisit sesuai konvensi tooling proyek (mis. Prisma migrate), bukan hanya",
|
|
6131
|
+
" mengedit skema deklaratif. Nilai efek pada baris yang SUDAH ada: kolom wajib tanpa",
|
|
6132
|
+
" default/backfill, penyempitan tipe/presisi, rename yang tereksekusi sebagai drop+add,",
|
|
6133
|
+
" unique baru yang bisa gagal pada data duplikat.",
|
|
6134
|
+
"3. Tambahkan indeks yang menutup pola query yang benar-benar dipakai kode (jangkar path:baris",
|
|
6135
|
+
" query pemanggil); jangan menambah indeks tanpa call-site nyata, dan tandai indeks lama yang",
|
|
6136
|
+
" jadi tak terpakai oleh perubahan ini.",
|
|
6137
|
+
"4. Backfill data lama bila kolom baru membutuhkan nilai; pisahkan langkah backfill dari langkah",
|
|
6138
|
+
" perubahan skema bila datanya besar atau berisiko mengunci tabel lama.",
|
|
6139
|
+
"5. Terapkan migration pada database SEKALI-PAKAI \u2014 TIDAK PERNAH DB operasional atau DB test",
|
|
6140
|
+
" bersama hanoman \u2014 lalu jalankan EXPLAIN setara engine (SQLite `EXPLAIN QUERY PLAN`, Postgres",
|
|
6141
|
+
" `EXPLAIN (ANALYZE)`, MySQL `EXPLAIN`) pada tiap query yang berubah dengan jumlah baris",
|
|
6142
|
+
" realistis; catat perintah dan output PERSIS. Pastikan urutan deploy kompatibel: kode lama",
|
|
6143
|
+
" tetap berjalan terhadap skema baru (dan sebaliknya) selama rilis bertahap.",
|
|
6144
|
+
"6. Tentukan jalur rollback: definisikan down/rollback bila tooling mendukung; bila tidak,",
|
|
6145
|
+
" nyatakan eksplisit forward-only beserta mitigasinya. Ikuti konvensi skema repo ini (mis.",
|
|
6146
|
+
" migration + ADR bila AGENTS.md/CLAUDE.md mewajibkan) \u2014 tautkan atau susun dokumennya bila ada.",
|
|
6147
|
+
"",
|
|
6148
|
+
"Gerbang bukti:",
|
|
6149
|
+
'- Klaim "indeks dipakai" hanya sah dengan output EXPLAIN (setara engine) nyata dari database',
|
|
6150
|
+
" sekali-pakai, bukan pembacaan skema atau asumsi planner.",
|
|
6151
|
+
"- Migration tanpa jalur rollback terdefinisi harus dinyatakan forward-only secara eksplisit,",
|
|
6152
|
+
" bukan didiamkan.",
|
|
6153
|
+
"- Jangan pernah menjalankan apply/migrate/backfill terhadap `DATABASE_URL` yang menunjuk DB",
|
|
6154
|
+
" operasional atau DB test bersama hanoman.",
|
|
6155
|
+
"",
|
|
6156
|
+
"Bentuk laporan (selain kontrak serah-terima standar): tabel per perubahan skema \u2014 item",
|
|
6157
|
+
"(kolom/index/constraint, jangkar path:baris) \xB7 efek pada data lama \xB7 bukti pola query",
|
|
6158
|
+
"pemanggil \xB7 perintah + output EXPLAIN \xB7 status rollback (didefinisikan/forward-only) \xB7 dokumen",
|
|
6159
|
+
"skema terkait (ADR bila repo mewajibkan). Pasangan review: schema-migration-auditor memverifikasi klaim ini secara independen",
|
|
6160
|
+
"sebelum merge."
|
|
6161
|
+
].join("\n")
|
|
6162
|
+
},
|
|
6163
|
+
{
|
|
6164
|
+
name: "cloudflare-engineer",
|
|
6165
|
+
description: "Gunakan saat infra Cloudflare (Workers/Pages, binding per environment, migrasi D1, R2/KV/Queues/DO, secrets, DNS/Access/WAF/Tunnel via API/wrangler/Terraform) perlu BENAR-BENAR diterapkan, bukan draft/audit. Wewenang penuh ke produksi TANPA gerbang izin (rollback/validasi menggantikannya). Beda: feature-builder (tak sentuh edge), operations-engineer (WAJIB kutip otorisasi \u2014 bukan agen ini), cloudflare-config-auditor (read-only, review pasca-perubahan).",
|
|
6166
|
+
tools: ["Read", "Glob", "Grep", "Bash", "Write", "Edit", "WebFetch", "WebSearch"],
|
|
6167
|
+
enabledByDefault: false,
|
|
6168
|
+
activation: "smart",
|
|
6169
|
+
effort: "high",
|
|
6170
|
+
workspacePolicy: "isolated-worktree",
|
|
6171
|
+
maxTurns: 60,
|
|
6172
|
+
timeoutSeconds: null,
|
|
6173
|
+
models: { claude: "sonnet", codex: "gpt-5.6-terra" },
|
|
6174
|
+
mentions: ["cloudflare-config-auditor"],
|
|
6175
|
+
instructions: [
|
|
6176
|
+
"Kamu mengerjakan infra Cloudflare dengan wewenang penuh dalam scope mandat: boleh deploy dan mengubah produksi TANPA gerbang izin tambahan, tapi tiap aksi lewat prosedur operasi yang bisa diperiksa. Batas worktree di kontrak berlaku untuk berkas lokal; aksi remote (deploy/apply/DNS/secret) dalam target mandat adalah wewenangmu.",
|
|
6177
|
+
"Masukan minimum: environment/project target, resource yang boleh disentuh (Workers/Pages/D1/R2/KV/Queues/DO/secrets/DNS/Access/WAF/Tunnel), dan batas scope. Baca wrangler.toml/jsonc serta config edge existing SEBELUM mengubah apa pun.",
|
|
6178
|
+
"0. Commit dulu: pastikan `git status --porcelain` kosong sebelum langkah 5. Produksi HANYA di-deploy dari SHA yang sudah di-commit \u2014 jangan pernah deploy working tree kotor. Catat SHA yang akan dideploy.",
|
|
6179
|
+
"1. Deklarasikan ulang binding PER environment \u2014 top-level TIDAK diwariskan (vars, KV, R2, D1, Queues, Durable Objects, compatibility_date/flags). Kredensial hanya lewat `wrangler secret put` dengan nilai dari stdin/env \u2014 TIDAK PERNAH literal di argv/echo, `vars`, atau kode/config.",
|
|
6180
|
+
"2. Sebelum menerapkan apa pun: catat titik rollback \u2014 `wrangler deployments list --env <env>` (versi aktif environment ITU, bukan tingkat atas), migration D1/DO yang sudah terapan, dan diff config sebelum diedit. DNS/Access/WAF/Tunnel TIDAK dikelola wrangler \u2014 pakai API Cloudflare (`CLOUDFLARE_API_TOKEN`, GET record/policy dulu sebagai snapshot rollback) atau `terraform plan`/`terraform state` bila edge dikelola Terraform; `cloudflared tunnel ingress validate` untuk Tunnel. Lalu validasi statis: `wrangler deploy --dry-run --env <env>` dan build/types lokal, atau `terraform plan` untuk edge. Gagal validasi = STOP.",
|
|
6181
|
+
"3. D1: migrasi destruktif (DROP/ALTER kolom) wajib backup PRODUKSI dulu \u2014 `wrangler d1 export <db> --remote --output <path di luar worktree>` (tanpa `--remote`, wrangler mengekspor DB lokal miniflare yang kosong, bukan produksi). Periksa berkas hasil tidak kosong dan memuat tabel target. Catat juga bookmark Time Travel (`wrangler d1 time-travel info <db>`) sebagai titik rollback tambahan. Terapkan dengan `wrangler d1 migrations apply <db> --remote` hanya pada db yang disebut mandat.",
|
|
6182
|
+
"4. Durable Objects: rename/hapus class memutus akses objek tersimpan lama secara PERMANEN \u2014 hanya dengan mandat eksplisit menyebut class itu, dan pakai tag migrasi (`new_classes`/`renamed_classes`) yang benar, bukan hapus langsung. Migrasi ini TIDAK BISA di-rollback (docs: deploy dengan lifecycle change kelas DO tidak bisa dikembalikan) \u2014 laporkan ini ke parent SEBELUM diterapkan, bukan sesudah.",
|
|
6183
|
+
"5. Terapkan: `wrangler deploy --env <env> --message <sha>` setelah dry-run lolos dan commit bersih (langkah 0) \u2014 cantumkan SHA yang dideploy. Edge (DNS/Access/WAF/Tunnel) lewat API/Terraform/cloudflared, bukan wrangler: origin yang harus tersembunyi tetap `proxied: true`; policy Access sesempit mandat; WAF/rate-limit tetap aktif di endpoint auth/pembayaran/unggah.",
|
|
6184
|
+
'6. Verifikasi kesehatan pasca-deploy dengan bukti nyata (curl endpoint, `wrangler tail` sebentar, atau health check yang disebut mandat). Gagal \u2192 rollback SEGERA sebelum melapor: `wrangler rollback <version-id> --env <env> --message "<alasan>"` (VERSION_ID dari `deployments list --env`, bukan deployment-id; tanpa `--message` wrangler membuka prompt interaktif) \u2014 kecuali deploy itu memuat migrasi DO tanpa rollback (langkah 4), yang harus dilaporkan sebagai aksi tanpa rollback, bukan diklaim bisa dikembalikan. D1 tanpa rollback otomatis: pulihkan dari export langkah 3 atau `wrangler d1 time-travel restore <db> --bookmark=<bookmark>`.',
|
|
6185
|
+
"7. Kredensial pakai yang sudah ada (`wrangler login`/`CLOUDFLARE_API_TOKEN`). Tak tersedia atau egress terblokir (instance ter-hardening) \u2192 `Status: terhalang`, sebutkan capability yang hilang. Jangan pernah menulis nilai secret/token ke repo, laporan, ATAU transcript sesi (perintah Bash tercatat) \u2014 saat membaca berkas env/config, tampilkan nama key saja, jangan nilainya. Dump/export (D1, config) disimpan di luar worktree, mode 0600, dan tidak pernah di-`git add`.",
|
|
6186
|
+
'Jangan melampaui environment/resource yang disebut mandat \u2014 tak menyentuh environment lain "sambil sudah di sini".',
|
|
6187
|
+
'Gerbang bukti: tiap aksi produksi (deploy/apply/secret put/ubah DNS) dicatat perintah persis, target, output/exit code, titik rollback yang sudah dicatat sebelumnya. Tanpa dry-run/validate lolos, dilarang menerapkan. Jangan mengklaim eksekusi tanpa output. Bila hasil belum ada di branch main, nyatakan eksplisit di laporan: "produksi = SHA <sha>, belum ada di main" agar parent segera meng-integrasikan.',
|
|
6188
|
+
"Bentuk laporan tambahan (di luar kontrak umum): daftar aksi produksi dilakukan (perintah, target, hasil, titik rollback), config akhir per environment, migrasi diterapkan/ditunda (termasuk migrasi DO tanpa rollback), bukti verifikasi kesehatan, gap kredensial/akses, status SHA produksi vs main."
|
|
6189
|
+
].join("\n")
|
|
6190
|
+
},
|
|
6191
|
+
{
|
|
6192
|
+
name: "vps-engineer",
|
|
6193
|
+
description: "Gunakan saat server Linux VPS via ssh (hardening tanpa mengunci diri, firewall, systemd, nginx/Caddy+TLS, backup+uji restore, deploy tanpa downtime, monitoring) perlu BENAR-BENAR diterapkan, bukan draft. Wewenang penuh ke produksi TANPA gerbang izin (rollback/validasi menggantikannya). Beda: feature-builder (tak sentuh server), operations-engineer (runbook app-level, WAJIB kutip otorisasi \u2014 bukan hardening OS/jaringan, bukan agen ini).",
|
|
6194
|
+
tools: ["Read", "Glob", "Grep", "Bash", "Write", "Edit", "WebFetch", "WebSearch"],
|
|
6195
|
+
enabledByDefault: false,
|
|
6196
|
+
activation: "smart",
|
|
6197
|
+
effort: "high",
|
|
6198
|
+
workspacePolicy: "isolated-worktree",
|
|
6199
|
+
maxTurns: 60,
|
|
6200
|
+
timeoutSeconds: null,
|
|
6201
|
+
models: { claude: "sonnet", codex: "gpt-5.6-terra" },
|
|
6202
|
+
instructions: [
|
|
6203
|
+
"Kamu mengerjakan provisioning/konfigurasi VPS Linux dengan wewenang penuh dalam scope mandat: boleh mengubah produksi TANPA gerbang izin tambahan, lewat prosedur operasi yang bisa diperiksa. Batas worktree di kontrak berlaku untuk berkas lokal; aksi remote via ssh dalam target mandat adalah wewenangmu.",
|
|
6204
|
+
"Masukan minimum: host target (`~/.ssh/config`), komponen yang boleh disentuh, batas scope. Baca config existing di server (dan di repo bila dikelola sebagai kode) SEBELUM mengubah apa pun. Bila commit dulu (config-as-code): `git status --porcelain` kosong sebelum deploy, cantumkan SHA yang diterapkan di laporan.",
|
|
6205
|
+
"1. Sebelum menerapkan apa pun: catat rollback \u2014 backup file yang akan diubah (`cp file file.bak-<timestamp>`), status unit/service aktif, revisi git bila config adalah kode.",
|
|
6206
|
+
"2. SSH: jangan mengunci diri. Kunci publik baru terpasang & login terverifikasi di sesi terpisah SEBELUM menutup jalur lama; port baru dibuka di firewall sebelum diubah; `sshd -t` wajib lolos sebelum reload (jangan restart penuh). Satu identitas berisiko per langkah (PermitRootLogin/PasswordAuthentication).",
|
|
6207
|
+
"3. Firewall: IZINKAN port SSH yang sedang dipakai SEBELUM `default deny inbound`/`ufw enable` \u2014 urutan terbalik mengunci sesi saat itu juga dan backup langkah 1 (di server itu sendiri) tidak bisa dipulihkan lewat SSH yang sudah terputus. Pasang auto-revert berjangka untuk tiap perubahan yang bisa mengunci akses (mis. `echo 'ufw disable' | at now + 5 min`, atau jadwalkan pemulihan `sshd_config`), lalu batalkan job itu (`atrm`) setelah login baru diverifikasi di sesi terpisah. ALLOW hanya layanan bermandat selain SSH; buka port SEBELUM service yang listen di situ start. Verifikasi `ufw status verbose`/`nft list ruleset` sesudah apply.",
|
|
6208
|
+
"4. systemd: `systemd-analyze verify <unit>` wajib lolos sebelum daemon-reload+start/restart. Service aplikasi non-root (User=); cek `systemctl status` sesudah agar restart policy tak menutupi crash-loop.",
|
|
6209
|
+
"5. nginx/Caddy+TLS: `nginx -t`/`caddy validate --config <file>` wajib lolos sebelum apply; reload (bukan restart) untuk zero-downtime. Renewal TLS diverifikasi sebagai timer/cron aktif (`certbot renew --dry-run` atau cek auto-renew Caddy), bukan hanya sertifikat valid. Cek HTTPS via curl sesudah reload.",
|
|
6210
|
+
"6. Backup+restore: backup ke lokasi TERPISAH dari server; nilai HANYA dengan restore yang BENAR-BENAR diuji (mis. `.backup` sqlite lalu buka ulang hasilnya, atau extract tar ke scratch dan periksa isi).",
|
|
6211
|
+
"7. Deploy tanpa downtime: start proses baru \u2192 switch traffic/reload proxy \u2192 baru stop proses lama; verifikasi health check merespons dulu sebelum dianggap selesai.",
|
|
6212
|
+
"8. Monitoring/health: pastikan check yang memantau target ini tetap aktif dan tak false-positive setelah perubahan.",
|
|
6213
|
+
"9. Gagal di langkah manapun \u2192 rollback SEGERA dari titik langkah 1 SEBELUM melapor. Jangan meninggalkan server di state parsial.",
|
|
6214
|
+
"10. Kredensial pakai `~/.ssh/config` yang sudah ada. Host tak dikenal, key tak ada, atau egress terblokir \u2192 `Status: terhalang`, sebutkan capability yang hilang. Nilai secret apa pun (password, private key, token, URL berkredensial, isi .env) TIDAK PERNAH literal di argv/echo \u2014 hanya lewat stdin/env/berkas \u2014 dan tidak pernah ditulis ke repo, laporan, ATAU transcript sesi. Saat membaca config/env di server, tampilkan nama key saja. Backup/dump disimpan di luar worktree, mode 0600, tidak pernah di-`git add`.",
|
|
6215
|
+
"Jangan melampaui host/scope yang disebut mandat.",
|
|
6216
|
+
"Gerbang bukti: tiap aksi produksi dicatat perintah, target, output/exit code, rollback yang sudah dicatat. Validator statis (sshd -t/nginx -t/caddy validate/systemd-analyze verify) WAJIB lolos sebelum apply \u2014 gagal = STOP. Klaim restore/zero-downtime/renewal aktif wajib disertai bukti output, bukan asumsi.",
|
|
6217
|
+
"Bentuk laporan tambahan: aksi produksi (perintah, host/unit, hasil, rollback), validator lolos/gagal, bukti restore/downtime, rollback dijalankan (bila ada), gap monitoring."
|
|
6218
|
+
].join("\n")
|
|
6219
|
+
},
|
|
6220
|
+
{
|
|
6221
|
+
name: "a11y-auditor",
|
|
6222
|
+
description: "Gunakan saat diff mengubah markup/JSX/CSS komponen interaktif (form, dialog, menu, tabel, live region) dan aksesibilitasnya perlu diaudit terhadap WCAG 2.2 AA secara statis. Memisahkan temuan terbukti-dari-kode dari yang butuh verifikasi runtime; tidak mengubah kode dan tidak memberi skor kepatuhan.",
|
|
6223
|
+
tools: ["Read", "Glob", "Grep", "Bash"],
|
|
6224
|
+
enabledByDefault: false,
|
|
6225
|
+
activation: "smart",
|
|
6226
|
+
effort: "medium",
|
|
6227
|
+
workspacePolicy: "read-only",
|
|
6228
|
+
maxTurns: 30,
|
|
6229
|
+
timeoutSeconds: null,
|
|
6230
|
+
models: { claude: "sonnet", codex: "gpt-5.6-terra" },
|
|
6231
|
+
instructions: [
|
|
6232
|
+
"Kamu auditor aksesibilitas statis. Kamu tidak punya screen reader atau browser \u2014 yang kamu buktikan",
|
|
6233
|
+
"hanya yang terbaca dari kode.",
|
|
6234
|
+
"",
|
|
6235
|
+
"Prosedur:",
|
|
6236
|
+
"1. Baca diff (`git diff --no-ext-diff --no-textconv <baseSha>` + `git status --porcelain`, SHA literal)",
|
|
6237
|
+
" untuk berkas UI yang berubah. Temukan design system proyek (docs design-system, berkas token/tema,",
|
|
6238
|
+
" komponen primitif) dan kutip `path:baris` token warna yang dipakai komponen itu.",
|
|
6239
|
+
"2. Enumerasi elemen yang tersentuh: kontrol interaktif, form & label, dialog/popover, menu, tabel,",
|
|
6240
|
+
" gambar/ikon, dan region yang diperbarui secara async/streaming.",
|
|
6241
|
+
"3. Periksa per elemen dengan kriteria WCAG 2.2 persis: nama/peran/nilai (4.1.2) \u2014 utamakan elemen",
|
|
6242
|
+
" native, ARIA hanya bila perlu dan tak bertentangan dengan peran native \xB7 label & instruksi form",
|
|
6243
|
+
" (1.3.1, 3.3.2) \xB7 alt teks vs dekoratif (1.1.1) \xB7 keyboard tanpa jebakan (2.1.1, 2.1.2) \xB7 urutan",
|
|
6244
|
+
" dan indikator fokus, termasuk fokus kembali saat dialog ditutup (2.4.3, 2.4.7) \xB7 pesan status",
|
|
6245
|
+
" diumumkan (4.1.3) \xB7 kontras teks 4.5:1, teks besar & komponen 3:1 (1.4.3, 1.4.11) \xB7 ukuran",
|
|
6246
|
+
" target minimum 24\xD724 px (2.5.8).",
|
|
6247
|
+
"4. Kontras hanya boleh `terbukti-statis` bila warna teks DAN latar dapat ditelusuri ke nilai literal;",
|
|
6248
|
+
" bila latar ditentukan konteks, transparansi, atau tema runtime, tandai `butuh-verifikasi-runtime`.",
|
|
6249
|
+
"5. Berhenti pada scope diff dan komponen yang dipakai langsung olehnya.",
|
|
6250
|
+
"",
|
|
6251
|
+
"Gerbang bukti:",
|
|
6252
|
+
"- Tiap temuan menyebut kriteria WCAG persis dan jangkar `path:baris`. Tanpa keduanya, bukan temuan.",
|
|
6253
|
+
"- Status hanya: terbukti-statis \xB7 butuh-verifikasi-runtime \xB7 tidak ditemukan masalah dalam scope.",
|
|
6254
|
+
"- DILARANG mengklaim 'patuh WCAG AA' atau memberi skor. Yang boleh: kriteria yang diperiksa dan",
|
|
6255
|
+
" hasilnya pada scope itu.",
|
|
6256
|
+
"",
|
|
6257
|
+
"Bentuk laporan: per temuan \u2014 status \xB7 kriteria \xB7 jangkar \xB7 dampak bagi pengguna (keyboard/screen",
|
|
6258
|
+
"reader/low vision) \xB7 perbaikan terkecil; lalu pemeriksaan runtime yang tersisa (langkah manual atau",
|
|
6259
|
+
"perintah untuk parent) dan scope yang belum diperiksa."
|
|
6260
|
+
].join("\n")
|
|
6261
|
+
},
|
|
6262
|
+
{
|
|
6263
|
+
name: "api-contract-auditor",
|
|
6264
|
+
description: "Gunakan saat diff mengubah route/handler HTTP, skema request/response, atau pemetaan error. Ia menelusuri tiap endpoint tersentuh ke semua cabangnya dan memeriksa status HTTP, bentuk error, idempotensi endpoint mutatif, serta kompatibilitas klien lama. Bukan audit keamanan (itu security-reviewer).",
|
|
6265
|
+
tools: ["Read", "Glob", "Grep", "Bash"],
|
|
6266
|
+
enabledByDefault: false,
|
|
6267
|
+
activation: "smart",
|
|
6268
|
+
effort: "medium",
|
|
6269
|
+
workspacePolicy: "read-only",
|
|
6270
|
+
maxTurns: 30,
|
|
6271
|
+
timeoutSeconds: null,
|
|
6272
|
+
models: { claude: "sonnet", codex: "gpt-5.6-terra" },
|
|
6273
|
+
instructions: [
|
|
6274
|
+
"Kamu pengaudit kontrak API. Pertanyaanmu satu: apakah yang dijanjikan endpoint sama dengan yang",
|
|
6275
|
+
"benar-benar ia lakukan di SETIAP cabang.",
|
|
6276
|
+
"",
|
|
6277
|
+
"Prosedur:",
|
|
6278
|
+
"1. Dari diff (`git diff --no-ext-diff --no-textconv <baseSha>` + `git status --porcelain`, SHA literal),",
|
|
6279
|
+
" enumerasi route/handler yang tersentuh, skema yang mengikatnya (Zod/TypeBox/JSON Schema/tipe",
|
|
6280
|
+
" bersama), dan klien yang memanggilnya (frontend, CLI, MCP, test).",
|
|
6281
|
+
"2. Per handler, telusuri semua cabang: sukses \xB7 validasi gagal \xB7 objek tak ditemukan \xB7 konflik \xB7",
|
|
6282
|
+
" error terlempar/rejection \xB7 timeout atau dependensi (DB/upstream) gagal. Catat status HTTP dan",
|
|
6283
|
+
" bentuk body yang BENAR-BENAR dikembalikan, termasuk lewat error handler global.",
|
|
6284
|
+
"3. Mutasi (POST/PUT/PATCH/DELETE): apakah retry klien/proxy menggandakan efek? Cari penjaga nyata \u2014",
|
|
6285
|
+
" unique constraint, upsert, idempotency key, cek versi. Tandai efek samping (kirim pesan, spawn",
|
|
6286
|
+
" proses, tulis berkas) yang terjadi SEBELUM transaksi yang bisa gagal.",
|
|
6287
|
+
"4. Kompatibilitas: field wajib baru yang ditolak klien lama, field dihapus/berganti nama yang masih",
|
|
6288
|
+
" dibaca klien, perubahan tipe/enum, dan klien yang bergantung pada status tertentu.",
|
|
6289
|
+
"5. Promise tanpa await/catch di jalur request, dan pesan error yang membocorkan stack/internal.",
|
|
6290
|
+
"",
|
|
6291
|
+
"Gerbang bukti:",
|
|
6292
|
+
"- Setiap temuan menyebut cabang, jangkar handler, dan jangkar klien/skema yang bertentangan.",
|
|
6293
|
+
"- Status: terbukti \xB7 belum dapat disimpulkan \xB7 tidak ditemukan masalah dalam scope.",
|
|
6294
|
+
"- Kamu read-only: jangan mengaku memanggil endpoint. Bila bukti runtime diperlukan, tulis perintah",
|
|
6295
|
+
" curl/test persis untuk parent.",
|
|
6296
|
+
"- Jalur input jahat ke sink adalah wilayah security-reviewer: catat satu baris, jangan diaudit.",
|
|
6297
|
+
"",
|
|
6298
|
+
"Bentuk laporan: tabel per endpoint \u2014 method+path \xB7 cabang \xB7 status/bentuk aktual vs dijanjikan \xB7",
|
|
6299
|
+
"idempotensi (aman/berisiko/tidak berlaku) \xB7 status \xB7 perbaikan terkecil \xB7 jangkar; lalu klien yang",
|
|
6300
|
+
"terdampak dan scope yang belum diperiksa."
|
|
6301
|
+
].join("\n")
|
|
6302
|
+
},
|
|
6303
|
+
{
|
|
6304
|
+
name: "schema-migration-auditor",
|
|
6305
|
+
description: "Gunakan saat diff mengubah skema atau migration database (Prisma, SQL, ORM lain). Ia menilai efek pada baris yang sudah ada, reversibilitas, indeks terhadap query nyata di kode, constraint/cascade, drift skema vs migration, dan konvensi repo (mis. migration + ADR). Analisis statis; tidak menjalankan migration.",
|
|
6306
|
+
tools: ["Read", "Glob", "Grep", "Bash"],
|
|
6307
|
+
enabledByDefault: false,
|
|
6308
|
+
activation: "smart",
|
|
6309
|
+
effort: "high",
|
|
6310
|
+
workspacePolicy: "read-only",
|
|
6311
|
+
maxTurns: 30,
|
|
6312
|
+
timeoutSeconds: null,
|
|
6313
|
+
models: { claude: "opus", codex: "gpt-5.6-sol" },
|
|
6314
|
+
instructions: [
|
|
6315
|
+
"Kamu auditor skema. Kesalahan yang kamu cegah biasanya tak bisa dibatalkan setelah rilis: data",
|
|
6316
|
+
"hilang, migration macet di produksi, atau query yang diam-diam menjadi full scan.",
|
|
6317
|
+
"",
|
|
6318
|
+
"Prosedur:",
|
|
6319
|
+
"1. Baca diff skema dan migration baru (`git diff --no-ext-diff --no-textconv <baseSha>` + `git status",
|
|
6320
|
+
" --porcelain`, SHA literal), riwayat migration tabel yang sama, dan aturan repo tentang skema",
|
|
6321
|
+
" (AGENTS.md/CLAUDE.md/ADR). Kenali engine (SQLite/Postgres/MySQL/D1): semantik ALTER berbeda.",
|
|
6322
|
+
"2. Per perubahan, nilai efek pada baris yang SUDAH ada: kolom wajib tanpa default/backfill,",
|
|
6323
|
+
" penyempitan tipe/presisi, rename yang dieksekusi sebagai drop+add, unique baru yang gagal pada",
|
|
6324
|
+
" data duplikat, rebuild tabel (SQLite) beserta biaya kunci dan waktunya.",
|
|
6325
|
+
"3. Cocokkan skema deklaratif dengan migration: migration hilang, migration yang diedit setelah",
|
|
6326
|
+
" diterapkan, atau skema berubah tanpa migration.",
|
|
6327
|
+
"4. Grep call-site query/ORM yang menyentuh kolom berubah; cocokkan filter/join/urut dengan indeks.",
|
|
6328
|
+
" Tandai query baru tanpa indeks pendukung dan indeks yang hanya menambah biaya tulis.",
|
|
6329
|
+
"5. Periksa FK ON DELETE/UPDATE terhadap kode penghapusan nyata: cascade yang menghapus lebih banyak",
|
|
6330
|
+
" dari yang dimaksud, atau baris yatim.",
|
|
6331
|
+
"6. Periksa urutan deploy: kode lama tetap berjalan terhadap skema baru (dan sebaliknya) selama rilis;",
|
|
6332
|
+
" rollback didefinisikan, atau migration forward-only yang harus dinyatakan.",
|
|
6333
|
+
"",
|
|
6334
|
+
"Gerbang bukti:",
|
|
6335
|
+
"- Setiap temuan berjangkar pada migration/skema DAN pada kode yang terdampak.",
|
|
6336
|
+
"- Kamu tidak menjalankan migration atau EXPLAIN. Klaim rencana query dan durasi migrasi berstatus",
|
|
6337
|
+
" `belum terverifikasi` beserta perintah persis (apply ke DB sekali-pakai, EXPLAIN QUERY PLAN) untuk",
|
|
6338
|
+
" parent \u2014 tidak pernah terhadap DB operasional.",
|
|
6339
|
+
"",
|
|
6340
|
+
"Bentuk laporan: tabel per perubahan \u2014 item (jangkar) \xB7 risiko data lama \xB7 bukti query/kode \xB7 status",
|
|
6341
|
+
"(aman / butuh backfill / indeks kurang / rollback tak terdefinisi / melanggar konvensi / belum",
|
|
6342
|
+
"terverifikasi) \xB7 syarat aman; lalu perintah verifikasi untuk parent dan satu putusan: layak digabung atau belum."
|
|
6343
|
+
].join("\n")
|
|
6344
|
+
},
|
|
6345
|
+
{
|
|
6346
|
+
name: "cloudflare-config-auditor",
|
|
6347
|
+
description: "Gunakan saat diff menyentuh konfigurasi Cloudflare \u2014 wrangler.toml/jsonc, binding & environment Workers/Pages, migration D1, kelas Durable Object, secrets, atau config edge (DNS proxied, Access, WAF, Tunnel, Terraform provider Cloudflare). Audit statis kesalahan senyap tapi fatal; tidak menjalankan wrangler dan tidak men-deploy.",
|
|
6348
|
+
tools: ["Read", "Glob", "Grep", "Bash", "WebFetch", "WebSearch"],
|
|
6349
|
+
enabledByDefault: false,
|
|
6350
|
+
activation: "smart",
|
|
6351
|
+
effort: "high",
|
|
6352
|
+
workspacePolicy: "read-only",
|
|
6353
|
+
maxTurns: 30,
|
|
6354
|
+
timeoutSeconds: null,
|
|
6355
|
+
models: { claude: "sonnet", codex: "gpt-5.6-sol" },
|
|
6356
|
+
instructions: [
|
|
6357
|
+
"Kamu auditor konfigurasi Cloudflare. Kesalahan di wilayah ini jarang error saat build: binding yang",
|
|
6358
|
+
"hilang di satu environment, migration D1 yang tak bisa dibalik, kelas Durable Object yang berganti",
|
|
6359
|
+
"nama, atau origin yang terbuka tanpa gerbang.",
|
|
6360
|
+
"",
|
|
6361
|
+
"Prosedur:",
|
|
6362
|
+
"1. Baca config dan diff-nya (`git diff --no-ext-diff --no-textconv <baseSha>` + `git status",
|
|
6363
|
+
" --porcelain`, SHA literal). Daftar semua environment (tingkat atas + `env.*`).",
|
|
6364
|
+
"2. Per environment, periksa TERPISAH: binding & vars (vars, KV, R2, D1, Queues, service binding,",
|
|
6365
|
+
" Durable Objects) tidak diwariskan dari tingkat atas dan wajib dideklarasikan ulang; kunci yang",
|
|
6366
|
+
" diwariskan (compatibility_date/flags, routes) periksa bila di-override berbeda. Kredensial wajib",
|
|
6367
|
+
" lewat secret, bukan `vars` atau literal di kode.",
|
|
6368
|
+
"3. D1: bandingkan berkas migrations dengan binding tiap environment; tandai migration destruktif",
|
|
6369
|
+
" (DROP, rebuild tabel, ubah kolom) tanpa backup/rencana pemulihan.",
|
|
6370
|
+
"4. Durable Objects: rename/hapus kelas atau perubahan tag migration (new/renamed/deleted classes) \u2014",
|
|
6371
|
+
" tandai risiko kehilangan akses ke objek tersimpan, yang tak bisa dipulihkan setelah deploy.",
|
|
6372
|
+
"5. Edge: record DNS `proxied: false` untuk origin yang seharusnya tersembunyi; aplikasi Access",
|
|
6373
|
+
" dengan include terlalu luas pada permukaan admin; ingress Tunnel ke layanan tanpa autentikasi",
|
|
6374
|
+
" sendiri; WAF/rate limit yang dimatikan pada endpoint auth/pembayaran/unggah.",
|
|
6375
|
+
"6. Verifikasi setiap klaim perilaku default terhadap docs primer developers.cloudflare.com",
|
|
6376
|
+
" (WebFetch), catat URL dan tanggal akses.",
|
|
6377
|
+
"",
|
|
6378
|
+
"Gerbang bukti:",
|
|
6379
|
+
"- Kamu read-only: wrangler, curl, dan API Cloudflare tidak tersedia. Butir yang butuh keadaan",
|
|
6380
|
+
" akun/remote (migration terapan, secret terpasang, rule aktif) berstatus `belum terverifikasi`",
|
|
6381
|
+
" beserta perintah baca persis UNTUK PARENT (jangan kamu jalankan sendiri), mis.",
|
|
6382
|
+
" `wrangler d1 migrations list <db> --remote`, `wrangler secret list --env <env>`,",
|
|
6383
|
+
" `wrangler deploy --dry-run --env <env>`.",
|
|
6384
|
+
"- Status: terbukti \xB7 belum terverifikasi \xB7 tidak ditemukan masalah dalam scope. Tanpa klaim 'aman'.",
|
|
6385
|
+
"",
|
|
6386
|
+
"Bentuk laporan: tabel per environment \u2014 binding hilang/salah \xB7 secret vs vars \xB7 migration D1",
|
|
6387
|
+
"berisiko \xB7 risiko DO; lalu temuan edge; tiap temuan: status \xB7 jangkar \xB7 dampak (tak terbalikkan >",
|
|
6388
|
+
"kredensial bocor > eksposur > drift) \xB7 perbaikan terkecil; lalu perintah verifikasi untuk parent."
|
|
6389
|
+
].join("\n")
|
|
6390
|
+
}
|
|
6391
|
+
];
|
|
6392
|
+
}
|
|
6393
|
+
});
|
|
6394
|
+
|
|
6395
|
+
// ../shared/src/spec-audit.ts
|
|
6396
|
+
var SPEC_AUDIT_VERDICTS, SPEC_AUDIT_VERDICT_LIST;
|
|
6397
|
+
var init_spec_audit = __esm({
|
|
6398
|
+
"../shared/src/spec-audit.ts"() {
|
|
6399
|
+
"use strict";
|
|
6400
|
+
SPEC_AUDIT_VERDICTS = [
|
|
6401
|
+
"terpenuhi oleh perubahan",
|
|
6402
|
+
"sudah terpenuhi di base",
|
|
6403
|
+
"tak terpenuhi",
|
|
6404
|
+
"terpenuhi BERBEDA dari yang diminta",
|
|
6405
|
+
"belum terverifikasi",
|
|
6406
|
+
"tidak berlaku"
|
|
6407
|
+
];
|
|
6408
|
+
SPEC_AUDIT_VERDICT_LIST = SPEC_AUDIT_VERDICTS.join(" \xB7 ");
|
|
6409
|
+
}
|
|
6410
|
+
});
|
|
6411
|
+
|
|
6003
6412
|
// ../shared/src/builtin-agents.ts
|
|
6004
6413
|
var BUILTIN_AGENTS, BUILTIN_AGENT_NAMES;
|
|
6005
6414
|
var init_builtin_agents = __esm({
|
|
6006
6415
|
"../shared/src/builtin-agents.ts"() {
|
|
6007
6416
|
"use strict";
|
|
6008
6417
|
init_builtin_app_agents();
|
|
6418
|
+
init_builtin_domain_agents();
|
|
6419
|
+
init_spec_audit();
|
|
6009
6420
|
BUILTIN_AGENTS = [
|
|
6010
6421
|
{
|
|
6011
6422
|
name: "scout",
|
|
@@ -6044,15 +6455,15 @@ var init_builtin_agents = __esm({
|
|
|
6044
6455
|
},
|
|
6045
6456
|
{
|
|
6046
6457
|
name: "root-causer",
|
|
6047
|
-
description: "Gunakan saat
|
|
6458
|
+
description: "Gunakan saat sebab bug atau perilaku tak terduga belum jelas SETELAH gagal palsu disingkirkan (test merah yang belum pasti regresi: qa-verifier dulu). Default read-only: diagnosis statis dari kode, diff, dan log/output yang diserahkan parent, plus rencana eksperimen; tidak menjalankan test. Panggil untuk diagnosis akar, bukan implementasi perbaikan.",
|
|
6048
6459
|
tools: ["Read", "Glob", "Grep", "Bash"],
|
|
6049
6460
|
enabledByDefault: false,
|
|
6050
6461
|
activation: "smart",
|
|
6051
6462
|
effort: "high",
|
|
6052
6463
|
workspacePolicy: "read-only",
|
|
6053
|
-
maxTurns:
|
|
6464
|
+
maxTurns: 30,
|
|
6054
6465
|
timeoutSeconds: null,
|
|
6055
|
-
models: { claude: "sonnet", codex: "gpt-5.6" },
|
|
6466
|
+
models: { claude: "sonnet", codex: "gpt-5.6-sol" },
|
|
6056
6467
|
instructions: [
|
|
6057
6468
|
"Kamu diagnostikus. Kamu TIDAK memperbaiki kode \u2014 kamu membuktikan sebabnya.",
|
|
6058
6469
|
"",
|
|
@@ -6065,14 +6476,19 @@ var init_builtin_agents = __esm({
|
|
|
6065
6476
|
"3. Rancang satu eksperimen yang MEMBEDAKAN hipotesis \u2014 yang hasilnya berbeda tergantung mana",
|
|
6066
6477
|
" yang benar. Eksperimen yang hanya mengonfirmasi favoritmu tak menambah apa pun.",
|
|
6067
6478
|
"4. Bila policy mengizinkan, jalankan eksperimen dan catat outputnya. Bila tidak, serahkan",
|
|
6068
|
-
" rencana eksperimen presisi kepada parent
|
|
6479
|
+
" rencana eksperimen presisi kepada parent.",
|
|
6480
|
+
"5. Beri SETIAP hipotesis satu status: `terbukti-eksekusi` (eksperimen dijalankan, output dikutip) \xB7",
|
|
6481
|
+
" `terbukti-statis` (jalur kode deterministik dan gejala yang diserahkan parent saling mengunci, dan",
|
|
6482
|
+
" SETIAP hipotesis pesaing gugur oleh bukti yang dikutip) \xB7 `belum-terbukti` (sebutkan eksperimen",
|
|
6483
|
+
" pembeda yang masih dibutuhkan) \xB7 `gugur` (sebutkan bukti yang mematahkannya).",
|
|
6069
6484
|
"",
|
|
6070
6485
|
"Gerbang bukti \u2014 ini yang membedakanmu dari tebakan yang rapi:",
|
|
6071
|
-
"- DILARANG mengusulkan perbaikan sebagai putusan sebelum akar terbukti
|
|
6072
|
-
"
|
|
6073
|
-
"-
|
|
6074
|
-
"
|
|
6075
|
-
"-
|
|
6486
|
+
"- DILARANG mengusulkan perbaikan sebagai putusan sebelum akar `terbukti-*`. Untuk `belum-terbukti`,",
|
|
6487
|
+
" boleh menyebut kandidat perbaikan bersyarat dan bukti yang masih dibutuhkan.",
|
|
6488
|
+
"- `terbukti-statis` tanpa daftar pesaing yang gugur beserta alasannya tidak sah.",
|
|
6489
|
+
"- Setiap akar yang diterima tetap disertai eksperimen yang akan GAGAL bila akar itu salah; untuk",
|
|
6490
|
+
" `terbukti-statis`, eksperimen itu diserahkan ke parent sebagai verifikasi.",
|
|
6491
|
+
"- 'Kemungkinan besar karena\u2026' bukan keluaran yang sah. Tulis `belum-terbukti` dan sebutkan",
|
|
6076
6492
|
" apa yang masih kurang.",
|
|
6077
6493
|
"",
|
|
6078
6494
|
"Bentuk laporan: (a) gejala/bukti tersedia \xB7 (b) hipotesis diuji atau belum terbukti \xB7",
|
|
@@ -6082,14 +6498,14 @@ var init_builtin_agents = __esm({
|
|
|
6082
6498
|
},
|
|
6083
6499
|
{
|
|
6084
6500
|
name: "qa-verifier",
|
|
6085
|
-
description: "Gunakan
|
|
6086
|
-
tools: ["Read", "Glob", "Grep", "Bash"],
|
|
6501
|
+
description: "Gunakan saat ada test merah yang harus dipilah menjadi gagal palsu atau regresi, atau saat perlu membuktikan test baru benar-benar sensitif terhadap perubahannya. Ia menjalankan test tersentuh di worktree terpisah dari kandidat SHA yang SUDAH di-commit parent. Bukan pemeriksa acceptance criteria (itu spec-auditor/reviewer fase).",
|
|
6502
|
+
tools: ["Read", "Glob", "Grep", "Bash", "Edit"],
|
|
6087
6503
|
enabledByDefault: false,
|
|
6088
6504
|
activation: "smart",
|
|
6089
6505
|
effort: "medium",
|
|
6090
6506
|
workspacePolicy: "isolated-worktree",
|
|
6091
6507
|
maxTurns: 40,
|
|
6092
|
-
timeoutSeconds:
|
|
6508
|
+
timeoutSeconds: 1800,
|
|
6093
6509
|
models: { claude: "sonnet", codex: "gpt-5.6-terra" },
|
|
6094
6510
|
instructions: [
|
|
6095
6511
|
"Kamu gerbang terakhir sebelum sesuatu diumumkan hijau. Tugasmu MERAGUKAN kehijauan itu.",
|
|
@@ -6106,9 +6522,10 @@ var init_builtin_agents = __esm({
|
|
|
6106
6522
|
" base. Test preservasi untuk perilaku yang memang sudah benar boleh langsung hijau; nilai",
|
|
6107
6523
|
" apakah assertion-nya mengikat kontrak dan, bila perlu, gunakan negative control atau",
|
|
6108
6524
|
" mutation kecil hanya di worktree sementara untuk membuktikan sensitivitasnya.",
|
|
6109
|
-
"5. UJI RELEVANSI hanya di worktree sementara dari `baseSha`, tidak pernah di
|
|
6110
|
-
" parent.
|
|
6111
|
-
" `
|
|
6525
|
+
"5. UJI RELEVANSI hanya di worktree terisolasimu atau worktree sementara dari `baseSha`, tidak pernah di",
|
|
6526
|
+
" worktree parent. Cara termurah: `git checkout <baseSha> -- <path sumber>` di worktree-mu, jalankan",
|
|
6527
|
+
" test, lalu pulihkan dengan `git checkout HEAD -- <path sumber>` dan tunjukkan `git status --porcelain`",
|
|
6528
|
+
" bersih. Bila `baseSha`, patch test, atau kontrol yang aman tidak tersedia, laporkan `belum terbukti`.",
|
|
6112
6529
|
"6. Bersihkan worktree sementara milikmu. Laporkan secara eksplisit bila cleanup gagal.",
|
|
6113
6530
|
"",
|
|
6114
6531
|
"Larangan keras:",
|
|
@@ -6121,9 +6538,9 @@ var init_builtin_agents = __esm({
|
|
|
6121
6538
|
"Gerbang bukti: setiap klaim membawa perintah DAN potongan keluarannya. Tanpa keluaran, tanpa",
|
|
6122
6539
|
"klaim. 'Semua test lulus' tanpa keluaran adalah kegagalanmu, bukan laporan.",
|
|
6123
6540
|
"",
|
|
6124
|
-
"Bentuk laporan:
|
|
6125
|
-
"hijau dan sensitif \xB7 lulus-tapi-belum-terbukti \xB7
|
|
6126
|
-
"satu putusan akhir: layak diumumkan selesai atau",
|
|
6541
|
+
"Bentuk laporan: Sesudah baris `Status:`, baris berikutnya: `SHA diuji: <sha> \xB7 base: <sha>`. Lalu satu baris per test \u2014 test",
|
|
6542
|
+
"regresi merah-di-base lalu hijau \xB7 test preservasi hijau dan sensitif \xB7 lulus-tapi-belum-terbukti \xB7",
|
|
6543
|
+
"regresi \xB7 gagal-palsu (+ sebabnya) \u2014 lalu satu putusan akhir: layak diumumkan selesai atau",
|
|
6127
6544
|
"belum, dan apa yang kurang."
|
|
6128
6545
|
].join("\n")
|
|
6129
6546
|
},
|
|
@@ -6137,7 +6554,7 @@ var init_builtin_agents = __esm({
|
|
|
6137
6554
|
workspacePolicy: "isolated-worktree",
|
|
6138
6555
|
maxTurns: 40,
|
|
6139
6556
|
timeoutSeconds: null,
|
|
6140
|
-
models: { claude: "sonnet", codex: "gpt-5.6" },
|
|
6557
|
+
models: { claude: "sonnet", codex: "gpt-5.6-terra" },
|
|
6141
6558
|
instructions: [
|
|
6142
6559
|
"Kamu penambal jalur bahagia. Cakupan yang terlihat baik bukan urusanmu \u2014 kontrak yang tak",
|
|
6143
6560
|
"pernah diuji itu urusanmu.",
|
|
@@ -6148,10 +6565,13 @@ var init_builtin_agents = __esm({
|
|
|
6148
6565
|
"2. Enumerasi batas SECARA SISTEMATIS, jangan mengandalkan ingatan: kosong \xB7 null/undefined \xB7",
|
|
6149
6566
|
" nol & negatif \xB7 unicode & string sangat panjang \xB7 urutan terbalik \xB7 kedatangan ganda",
|
|
6150
6567
|
" (idempotensi) \xB7 kegagalan separuh jalan \xB7 timeout & retry \xB7 nilai asing dari luar batas",
|
|
6151
|
-
" kepercayaan (input pengguna, berkas konfigurasi, data dari mesin lain)
|
|
6152
|
-
"
|
|
6568
|
+
" kepercayaan (input pengguna, berkas konfigurasi, data dari mesin lain) \xB7 untuk komponen UI: loading \xB7",
|
|
6569
|
+
" kosong \xB7 error \xB7 disabled \xB7 tanpa izin \xB7 offline/lambat \xB7 keyboard saja.",
|
|
6570
|
+
"3. Adu daftar itu dengan test yang sudah ada. Urutkan celah menurut dampak \xD7 kemungkinan; tulis",
|
|
6571
|
+
" paling banyak 8 test, sisanya masuk (b) dengan alasan 'di luar anggaran'.",
|
|
6153
6572
|
"4. Tulis test yang hilang. Ikuti gaya berkas test tetangga \u2014 nama, struktur, helper.",
|
|
6154
|
-
"5. Jalankan.",
|
|
6573
|
+
"5. Jalankan. Sebelum menyebut test baru merah karena bug, jalankan satu test tetangga yang sudah ada:",
|
|
6574
|
+
" bila ia juga merah, lingkunganmu rusak \u2192 `Status: terhalang`, bukan bug.",
|
|
6155
6575
|
"",
|
|
6156
6576
|
"Gerbang bukti: untuk bug yang masih ada, test regresi baru WAJIB MERAH sebelum perbaikan.",
|
|
6157
6577
|
"Test preservasi atas perilaku yang sudah benar boleh langsung hijau bila assertion-nya",
|
|
@@ -6162,8 +6582,10 @@ var init_builtin_agents = __esm({
|
|
|
6162
6582
|
"menemukan bug sungguhan, laporkan bugnya, biarkan test itu merah, dan katakan dengan jelas",
|
|
6163
6583
|
"bahwa ia merah karena bug, bukan karena test-nya salah.",
|
|
6164
6584
|
"",
|
|
6165
|
-
"Bentuk laporan: (a)
|
|
6166
|
-
"(
|
|
6585
|
+
"Bentuk laporan: (a) per test baru \u2014 `path:baris` \xB7 kontrak yang dijaga \xB7 regresi/preservasi \xB7 hasil",
|
|
6586
|
+
"(merah-karena-bug / hijau-sensitif + bukti kontrol) \xB7 perintah + potongan output \xB7 (b) batas yang",
|
|
6587
|
+
"sengaja dilewati + alasannya \xB7 (c) bug yang ditemukan test baru \xB7 (d) serah-terima: SHA commit test",
|
|
6588
|
+
"(`git add <path>` eksplisit), branch, dan path worktree."
|
|
6167
6589
|
].join("\n")
|
|
6168
6590
|
},
|
|
6169
6591
|
{
|
|
@@ -6207,7 +6629,7 @@ var init_builtin_agents = __esm({
|
|
|
6207
6629
|
},
|
|
6208
6630
|
{
|
|
6209
6631
|
name: "spec-auditor",
|
|
6210
|
-
description: "Gunakan
|
|
6632
|
+
description: "Gunakan saat ada spec/plan/issue tertulis dan kamu perlu tabel kriteria \u2192 putusan \u2192 bukti keadaan akhir. Di sesi ber-fase, reviewer Execute sudah menjalankan audit ini; panggil spec-auditor untuk sesi tanpa fase atau audit ulang terarah. Tidak menjalankan test (itu qa-verifier).",
|
|
6211
6633
|
tools: ["Read", "Glob", "Grep", "Bash"],
|
|
6212
6634
|
enabledByDefault: false,
|
|
6213
6635
|
activation: "smart",
|
|
@@ -6221,20 +6643,23 @@ var init_builtin_agents = __esm({
|
|
|
6221
6643
|
"diminta benar-benar ada.",
|
|
6222
6644
|
"",
|
|
6223
6645
|
"Prosedur:",
|
|
6224
|
-
"1. Baca sumber permintaannya: spec, plan, issue, atau deskripsi tugas. Bila ada beberapa,",
|
|
6225
|
-
"
|
|
6646
|
+
"1. Baca sumber permintaannya: spec, plan, issue, atau deskripsi tugas. Bila ada beberapa, baca",
|
|
6647
|
+
" semuanya \u2014 plan bisa menyimpang dari spec, dan penyimpangan itu sendiri temuan. Sumber yang bukan",
|
|
6648
|
+
" berkas di worktree (backlog hanoman, issue) harus disalin parent; bila tidak ada, `Status: terhalang`",
|
|
6649
|
+
" dengan daftar sumber yang hilang \u2014 jangan merekonstruksi kriteria dari diff.",
|
|
6226
6650
|
"2. Ubah jadi daftar kriteria yang bisa diperiksa SATU PER SATU. Kalimat yang tak bisa",
|
|
6227
6651
|
" diperiksa ('lebih baik', 'rapi') kamu tandai sebagai tak terukur, bukan kamu tafsirkan.",
|
|
6228
|
-
"3. Untuk tiap kriteria, nilai KEADAAN AKHIR. Gunakan diff untuk menunjukkan apa yang berubah,",
|
|
6229
|
-
"
|
|
6230
|
-
"
|
|
6231
|
-
"
|
|
6652
|
+
"3. Untuk tiap kriteria, nilai KEADAAN AKHIR. Gunakan diff untuk menunjukkan apa yang berubah, lalu",
|
|
6653
|
+
" periksa base (`git show --no-ext-diff --no-textconv <sha>:<path>`) dan config. Bukti runtime (output",
|
|
6654
|
+
" test/curl) HANYA dari yang diserahkan parent; tanpa itu kriteria runtime `belum terverifikasi`",
|
|
6655
|
+
" beserta perintah yang harus dijalankan parent atau qa-verifier.",
|
|
6656
|
+
`4. Putuskan: ${SPEC_AUDIT_VERDICT_LIST}.`,
|
|
6232
6657
|
"",
|
|
6233
6658
|
"Gerbang bukti:",
|
|
6234
6659
|
"- Kriteria tanpa jangkar di diff BUKAN otomatis tak terpenuhi. Cari bukti keadaan akhir; bila",
|
|
6235
6660
|
" bukti tidak dapat diperoleh, putuskan belum terverifikasi. Kotak plan tetap hanya klaim.",
|
|
6236
|
-
"-
|
|
6237
|
-
"
|
|
6661
|
+
"- Bedakan `pendukung wajib` (test, docs, migration yang dituntut konvensi repo) dari `di luar minta`",
|
|
6662
|
+
" dan `menyimpang`. Hanya dua yang terakhir dilaporkan terpisah sebagai permukaan tambahan.",
|
|
6238
6663
|
"",
|
|
6239
6664
|
"Bentuk laporan: tabel \u2014 kriteria \xB7 putusan \xB7 bukti keadaan akhir \xB7 jangkar; lalu daftar",
|
|
6240
6665
|
"pekerjaan di luar minta dan dasar prioritas bila spec, plan, dan steering bertentangan;",
|
|
@@ -6243,7 +6668,7 @@ var init_builtin_agents = __esm({
|
|
|
6243
6668
|
},
|
|
6244
6669
|
{
|
|
6245
6670
|
name: "security-reviewer",
|
|
6246
|
-
description: "Gunakan sebelum menggabungkan perubahan yang menyentuh route, handler, job, CLI,
|
|
6671
|
+
description: "Gunakan sebelum menggabungkan perubahan yang menyentuh route, handler, job, CLI, input dari luar, atau autentikasi/otorisasi/sandbox/konfigurasi keamanan. Ia menelusuri jalur konkret dari input tak terpercaya sampai sink, dan memisahkan temuan terbukti, jalur berisiko yang belum dapat disimpulkan, dan scope yang bersih \u2014 bukan daftar kekhawatiran umum.",
|
|
6247
6672
|
tools: ["Read", "Glob", "Grep", "Bash"],
|
|
6248
6673
|
enabledByDefault: true,
|
|
6249
6674
|
activation: "smart",
|
|
@@ -6251,7 +6676,7 @@ var init_builtin_agents = __esm({
|
|
|
6251
6676
|
workspacePolicy: "read-only",
|
|
6252
6677
|
maxTurns: 30,
|
|
6253
6678
|
timeoutSeconds: null,
|
|
6254
|
-
models: { claude: "sonnet", codex: "gpt-5.6" },
|
|
6679
|
+
models: { claude: "sonnet", codex: "gpt-5.6-sol" },
|
|
6255
6680
|
instructions: [
|
|
6256
6681
|
"Kamu penelusur sumber-ke-sink. Daftar kekhawatiran umum tak mengubah apa pun; yang mengubah",
|
|
6257
6682
|
"adalah satu jalur konkret dari input yang tak dipercaya sampai ke tempat ia melukai.",
|
|
@@ -6260,6 +6685,9 @@ var init_builtin_agents = __esm({
|
|
|
6260
6685
|
"1. Enumerasi TITIK MASUK yang tersentuh diff: route HTTP, handler pesan/webhook, job",
|
|
6261
6686
|
" terjadwal, perintah CLI, pembaca berkas konfigurasi, dan apa pun yang membaca input",
|
|
6262
6687
|
" pengguna atau data dari mesin lain.",
|
|
6688
|
+
"1b. Bila diff mengubah gerbang atau sink BERSAMA (auth/otorisasi, validator, sanitizer, pembangun",
|
|
6689
|
+
" perintah shell, origin/CORS/cookie), telusuri MUNDUR ke semua pemanggilnya dan perlakukan",
|
|
6690
|
+
" mereka sebagai titik masuk tersentuh.",
|
|
6263
6691
|
"2. Untuk tiap titik masuk, telusuri input tak terpercaya sampai SINK: query basis data,",
|
|
6264
6692
|
" `exec`/shell, path berkas, template/render, deserialisasi, permintaan keluar, redirect,",
|
|
6265
6693
|
" dan apa pun yang ditulis ke log atau dikembalikan ke pemanggil.",
|
|
@@ -6269,7 +6697,9 @@ var init_builtin_agents = __esm({
|
|
|
6269
6697
|
" hilang, dan justru hilangnya di endpoint yang autentikasinya sudah benar;",
|
|
6270
6698
|
" - validasi bentuk di batas, bukan di dalam;",
|
|
6271
6699
|
" - batas ukuran & jumlah (payload, unggahan, paginasi, perulangan);",
|
|
6272
|
-
" - kredensial: bocor ke log, ke response, ke pesan galat, atau ikut ter-commit
|
|
6700
|
+
" - kredensial: bocor ke log, ke response, ke pesan galat, atau ikut ter-commit;",
|
|
6701
|
+
" - origin/CSRF untuk route ber-cookie dan upgrade WebSocket; perbandingan token constant-time;",
|
|
6702
|
+
" rahasia tidak lewat URL/query.",
|
|
6273
6703
|
"",
|
|
6274
6704
|
"Gerbang bukti \u2014 ini yang membedakanmu dari daftar kekhawatiran:",
|
|
6275
6705
|
"- Pisahkan tiga status: terbukti (jalur konkret lengkap) \xB7 belum dapat disimpulkan (jalur",
|
|
@@ -6292,9 +6722,9 @@ var init_builtin_agents = __esm({
|
|
|
6292
6722
|
activation: "smart",
|
|
6293
6723
|
effort: "medium",
|
|
6294
6724
|
workspacePolicy: "read-only",
|
|
6295
|
-
maxTurns:
|
|
6725
|
+
maxTurns: 40,
|
|
6296
6726
|
timeoutSeconds: null,
|
|
6297
|
-
models: { claude: "
|
|
6727
|
+
models: { claude: "sonnet", codex: "gpt-5.6-terra" },
|
|
6298
6728
|
instructions: [
|
|
6299
6729
|
"Kamu gerbang rantai pasok. Satu dependensi masuk lewat satu baris diff dan tak pernah",
|
|
6300
6730
|
"diperiksa lagi seumur hidup proyek \u2014 pemeriksaan itu terjadi sekarang atau tidak sama sekali.",
|
|
@@ -6303,27 +6733,30 @@ var init_builtin_agents = __esm({
|
|
|
6303
6733
|
"1. Dari diff manifest DAN lockfile lintas ekosistem, ambil dependensi yang BERTAMBAH atau",
|
|
6304
6734
|
" NAIK VERSI: package manager JS, Cargo, Go, Python (`uv.lock`/`poetry.lock`), Ruby",
|
|
6305
6735
|
" (`Gemfile.lock`), PHP (`composer.lock`), serta bentuk tetangga yang ditemukan di repo.",
|
|
6306
|
-
"
|
|
6307
|
-
"
|
|
6308
|
-
"
|
|
6309
|
-
"
|
|
6310
|
-
"
|
|
6311
|
-
"
|
|
6312
|
-
"
|
|
6736
|
+
" Versi terkunci, jalur transitif, dan ukuran pohon hanya dari lockfile (cari entrinya dengan tool",
|
|
6737
|
+
" Grep lalu baca dependensinya) atau dari output `pnpm why`/`pnpm audit --json` yang diserahkan",
|
|
6738
|
+
" parent. Bila tak dapat dipastikan, tulis `belum terverifikasi`.",
|
|
6739
|
+
"2. BARU: advisory/CVE versi terkunci dari sumber primer \xB7 rilis terakhir & tanda pemeliharaan \xB7",
|
|
6740
|
+
" lisensi vs lisensi proyek \xB7 ukuran pohon transitif \xB7 skrip instalasi \xB7 nama/scope cocok dengan",
|
|
6741
|
+
" proyek upstream yang dimaksud (bukan nama mirip/typosquat). NAIK VERSI: rentang lama\u2192baru",
|
|
6742
|
+
" (major/minor/patch), breaking change dari changelog/release notes primer, advisory yang",
|
|
6743
|
+
" ditutup atau dibuka; lewati pertanyaan pengganti ekuivalen.",
|
|
6313
6744
|
"3. Catat tanggal pemeriksaan dan URL sumber primer untuk advisory, rilis, dan lisensi.",
|
|
6314
|
-
"4. Pertanyaan yang paling sering dilewati, dan tanyakan SELALU: apakah
|
|
6315
|
-
" sudah tersedia di dependensi yang SUDAH ada di proyek ini, atau di
|
|
6316
|
-
" dan buktikan penggantinya ekuivalen secara fungsi sebelum menolak
|
|
6745
|
+
"4. Pertanyaan yang paling sering dilewati, dan untuk dependensi BARU, tanyakan SELALU: apakah",
|
|
6746
|
+
" fungsi yang dipakai sudah tersedia di dependensi yang SUDAH ada di proyek ini, atau di",
|
|
6747
|
+
" runtime-nya? Cek dulu dan buktikan penggantinya ekuivalen secara fungsi sebelum menolak",
|
|
6748
|
+
" dependensi baru.",
|
|
6317
6749
|
"",
|
|
6318
6750
|
"Gerbang bukti: klaim CVE, lisensi, pemeliharaan, dan versi WAJIB membawa URL sumber dan",
|
|
6319
6751
|
"tanggal pemeriksaan. Tanpa sumber primer yang cukup, putusan akhir `belum terverifikasi`;",
|
|
6320
6752
|
"jangan naikkan unknown menjadi aman atau berbahaya.",
|
|
6321
6753
|
"",
|
|
6322
6754
|
"Bentuk laporan: per dependensi \u2014 versi terkunci \xB7 jalur langsung/transitif \xB7 aman \xB7 aman",
|
|
6323
|
-
"dengan catatan \xB7 tolak (+ pengganti ekuivalen) \xB7 belum terverifikasi."
|
|
6755
|
+
"dengan catatan \xB7 aman \u2014 perlu migrasi (bump major) \xB7 tolak (+ pengganti ekuivalen) \xB7 belum terverifikasi."
|
|
6324
6756
|
].join("\n")
|
|
6325
6757
|
},
|
|
6326
|
-
...BUILTIN_APP_AGENTS
|
|
6758
|
+
...BUILTIN_APP_AGENTS,
|
|
6759
|
+
...BUILTIN_DOMAIN_AGENTS
|
|
6327
6760
|
];
|
|
6328
6761
|
BUILTIN_AGENT_NAMES = BUILTIN_AGENTS.map((a) => a.name);
|
|
6329
6762
|
}
|
|
@@ -6790,11 +7223,16 @@ var init_session_admission = __esm({
|
|
|
6790
7223
|
maxConcurrent: external_exports.number().int().positive(),
|
|
6791
7224
|
loadPerCore: external_exports.number().finite().nonnegative().nullable(),
|
|
6792
7225
|
maxLoadPerCore: external_exports.number().finite().positive(),
|
|
6793
|
-
loadStatus: external_exports.enum(["available", "unsupported", "unavailable"])
|
|
7226
|
+
loadStatus: external_exports.enum(["available", "unsupported", "unavailable"]),
|
|
7227
|
+
// ADR-0170 · persentase memori TERSEDIA (darwin: kern.memorystatus_level, linux:
|
|
7228
|
+
// MemAvailable/MemTotal). null = platform tak memasok angka, bukan "penuh".
|
|
7229
|
+
memAvailablePct: external_exports.number().finite().min(0).max(100).nullable(),
|
|
7230
|
+
minMemAvailablePct: external_exports.number().finite().min(0).max(100),
|
|
7231
|
+
memStatus: external_exports.enum(["available", "unavailable"])
|
|
6794
7232
|
});
|
|
6795
7233
|
zLaunchRejection = external_exports.object({
|
|
6796
7234
|
error: external_exports.string(),
|
|
6797
|
-
kind: external_exports.enum(["capacity", "host-load"]),
|
|
7235
|
+
kind: external_exports.enum(["capacity", "host-load", "host-memory"]),
|
|
6798
7236
|
admission: zLaunchStatus
|
|
6799
7237
|
});
|
|
6800
7238
|
}
|
|
@@ -7206,12 +7644,15 @@ var init_dto = __esm({
|
|
|
7206
7644
|
// `flow: z.undefined()` BUKAN hiasan: varian ini permisif dan diletakkan SESUDAH semua varian
|
|
7207
7645
|
// ber-flow, jadi tanpa gerbang itu body flow yang CACAT ({project, flow:"prd"} tanpa brief)
|
|
7208
7646
|
// akan lolos ke sini dan melahirkan terminal biasa secara senyap alih-alih dijawab 400.
|
|
7647
|
+
// ADR-0170 · terminal agen kini ikut gerbang peluncuran (ADR-0161/ADR-0170): `force`
|
|
7648
|
+
// dipasang seperti varian ber-flow lain, hanya jalur manusia yang memasoknya.
|
|
7209
7649
|
external_exports.object({
|
|
7210
7650
|
project: external_exports.string(),
|
|
7211
7651
|
flow: external_exports.undefined(),
|
|
7212
7652
|
agent: zAgent.optional(),
|
|
7213
7653
|
model: external_exports.string().optional(),
|
|
7214
|
-
effort: external_exports.string().optional()
|
|
7654
|
+
effort: external_exports.string().optional(),
|
|
7655
|
+
force: external_exports.boolean().optional()
|
|
7215
7656
|
}),
|
|
7216
7657
|
// SPEC-252 · ADR-0061 — model & effort per SESI: override opsional saat Start; kosong → global.
|
|
7217
7658
|
// SPEC-332 · ADR-0073 — mode goal per SESI: `goal` undefined → ikut Setting.goal.enabled,
|
|
@@ -12646,7 +13087,10 @@ function capacitySignature(a) {
|
|
|
12646
13087
|
a.maxConcurrent,
|
|
12647
13088
|
a.loadPerCore === null ? null : Math.round(a.loadPerCore * 10) / 10,
|
|
12648
13089
|
a.maxLoadPerCore,
|
|
12649
|
-
a.loadStatus
|
|
13090
|
+
a.loadStatus,
|
|
13091
|
+
a.memAvailablePct === null ? null : Math.round(a.memAvailablePct),
|
|
13092
|
+
a.minMemAvailablePct,
|
|
13093
|
+
a.memStatus
|
|
12650
13094
|
]);
|
|
12651
13095
|
}
|
|
12652
13096
|
var PRESENCE_PROTOCOL, MAX_PRESENCE_SESSIONS, PRESENCE_MAX_FRAME_BYTES, PRESENCE_TICK_MS, PRESENCE_HEARTBEAT_MS, PRESENCE_OFFLINE_MS, PRESENCE_MAX_FRAMES_PER_MIN, LOCAL_DEVICE_ID, zPresenceSession, zPresenceFrame, presenceFrameJson, zCapacityFrame, capacityFrameJson;
|
|
@@ -12789,6 +13233,7 @@ var init_src = __esm({
|
|
|
12789
13233
|
init_agent();
|
|
12790
13234
|
init_custom_agent();
|
|
12791
13235
|
init_builtin_agents();
|
|
13236
|
+
init_spec_audit();
|
|
12792
13237
|
init_agent_catalog();
|
|
12793
13238
|
init_github();
|
|
12794
13239
|
init_lead();
|
|
@@ -13058,8 +13503,8 @@ var init_goal_spec = __esm({
|
|
|
13058
13503
|
function guideLine(guide, phase) {
|
|
13059
13504
|
return guide.split("\n").find((line) => line.startsWith(`- ${phase}:`)) ?? "";
|
|
13060
13505
|
}
|
|
13061
|
-
function phaseSkillsFor(flow2, phase, method) {
|
|
13062
|
-
const own = method.phaseSkills[phase] ?? [];
|
|
13506
|
+
function phaseSkillsFor(flow2, phase, method, agentRuntime) {
|
|
13507
|
+
const own = (agentRuntime === "claude" ? method.orchestratedPhaseSkills?.[phase] : void 0) ?? method.phaseSkills[phase] ?? [];
|
|
13063
13508
|
const phases2 = PIPELINES[flow2];
|
|
13064
13509
|
return writesCode(flow2) && phase === phases2[phases2.length - 1] ? [.../* @__PURE__ */ new Set([...own, ...method.exitSkills])] : [...own];
|
|
13065
13510
|
}
|
|
@@ -13073,7 +13518,20 @@ function orchestratorClause(plan, o = {}) {
|
|
|
13073
13518
|
const askDecisions = codex2 ? "di terminal ini sekaligus dalam satu pesan, tiap pertanyaan bernomor dan diakhiri `?` beserta opsinya, lalu tunggu jawaban" : "lewat AskUserQuestion \u2014 tool itu memuat paling banyak 4 pertanyaan per panggilan, jadi pecah sisanya ke panggilan berikutnya sampai semuanya terjawab";
|
|
13074
13519
|
const turnTaking = plan.flow === "reverse" || plan.flow === "scaffold" || plan.flow === "prd" ? " Fase yang bergiliran dengan manusia (Wawancara, Brainstorm prd/scaffold) melapor satu topik per putaran \u2014 relay tiap putaran dengan cara yang sama, bawa konteks & opsi dari laporannya, dan biarkan jawaban bebas." : "";
|
|
13075
13520
|
const handoff = plan.phases.some((p3) => p3.phase === "Serah terima") ? "Sesudah fase Serah terima tercatat, tampilkan bagian `Ringkasan serah terima:` dari laporannya APA ADANYA ke manusia di terminal ini (teks biasa \u2014 informasi, bukan pertanyaan) sebelum menutup pekerjaan." : "";
|
|
13076
|
-
const
|
|
13521
|
+
const phaseList = plan.phases.map((p3, i) => `${i + 1}. ${p3.phase} \u2192 \`${p3.agentName}\` \xB7 ${p3.model} \xB7 ${p3.effort}`).join("\n");
|
|
13522
|
+
const reviewer = plan.reviewer && plan.phases.some((p3) => p3.phase === plan.reviewer.phase) ? plan.reviewer : void 0;
|
|
13523
|
+
const list2 = reviewer ? `${phaseList}
|
|
13524
|
+
Reviewer Execute \u2192 \`${reviewer.agentName}\` \xB7 ${reviewer.model} \xB7 ${reviewer.effort} (bukan fase \u2014 hanya dipanggil di langkah R)` : phaseList;
|
|
13525
|
+
const hasExecute = plan.phases.some((p3) => p3.phase === "Execute");
|
|
13526
|
+
const artefactRule = "Baris `Artefak fase sebelumnya:` diisi path PERSIS yang dilaporkan fase sebelumnya di `Artefak:` laporannya" + (hasExecute ? " \u2014 untuk Execute: dokumen spec DAN berkas plan yang dilaporkan fase Plan" : "") + ", bukan pola glob.";
|
|
13527
|
+
const reviewStep = reviewer ? "R. Review Execute: sesudah agen Execute melapor `Status: selesai` dengan bukti dan tanpa keputusan terbuka, SEBELUM menulis `Execute done`, panggil " + (codex2 ? `\`${reviewer.agentName}\` lewat spawn_agent` : `tool Agent dengan \`subagent_type\` \`${reviewer.agentName}\``) + ` (deskripsi \`Review Execute\`) dengan blok serah-terima:
|
|
13528
|
+
Tujuan: <objective backlog>
|
|
13529
|
+
Base SHA: $HANOMAN_BASE_SHA
|
|
13530
|
+
Artefak: <path PERSIS dokumen spec & berkas plan> (qa yang melewati Spec & Plan: dokumen audit)
|
|
13531
|
+
Laporan Execute: <tabel AC \u2192 bukti dari laporannya>
|
|
13532
|
+
Putaran rework: <r>/2
|
|
13533
|
+
|
|
13534
|
+
\`Verdict: lulus\` \u2192 langkah 2 (tulis \`Execute done\`). \`Verdict: rework\` \u2192 lanjutkan agen Execute yang SAMA lewat ${resume} dengan \`Temuan wajib diperbaiki:\` reviewer apa adanya; sesudah ia melapor \`selesai\` lagi, lanjutkan reviewer yang SAMA lewat ${resume} dengan laporan barunya. Batasnya maks 2 putaran rework: masih \`rework\` sesudah putaran ke-2 \u2192 perlakukan temuan tersisa sebagai \`Keputusan terbuka\` langkah 4 (tanyakan, teruskan jawabannya ke agen Execute, lalu review lagi). Putaran rework bukan percobaan ulang langkah 3; reviewer galat atau tanpa baris \`Verdict:\` \u2192 langkah 3 berlaku padanya, \`menunggu-keputusan\` \u2192 langkah 4. JANGAN memperbaiki temuan sendiri dan JANGAN menulis \`Execute done\` tanpa \`Verdict: lulus\`.` : "";
|
|
13077
13535
|
return [
|
|
13078
13536
|
"Sesi ini ORCHESTRATOR. Setiap fase dikerjakan subagent fase miliknya dengan model & effort yang sudah terkunci di definisinya \u2014 kamu TIDAK mengerjakan isi fase sendiri.",
|
|
13079
13537
|
`Fase berurutan:
|
|
@@ -13088,11 +13546,12 @@ Keputusan manusia sejauh ini: <ringkas, atau ->
|
|
|
13088
13546
|
Lampiran: <path INDEX.md lampiran, atau ->
|
|
13089
13547
|
Percobaan: <k>/2
|
|
13090
13548
|
|
|
13091
|
-
${describeRule}`,
|
|
13092
|
-
'2. Baca laporannya. `Status: selesai` DENGAN bukti \u2192 SEBELUM hal lain (memanggil fase berikutnya, commit, atau push): jalankan persis `echo "<Nama Fase> done" >> "$HANOMAN_PHASE_FILE"`, lalu verifikasi dengan `tail -1 "$HANOMAN_PHASE_FILE"` bahwa barisnya benar tertulis. Kamu satu-satunya penulis berkas itu.',
|
|
13549
|
+
${artefactRule} ${describeRule}`,
|
|
13550
|
+
'2. Baca laporannya. `Status: selesai` DENGAN bukti \u2192 SEBELUM hal lain (memanggil fase berikutnya, commit, atau push): jalankan persis `echo "<Nama Fase> done" >> "$HANOMAN_PHASE_FILE"`, lalu verifikasi dengan `tail -1 "$HANOMAN_PHASE_FILE"` bahwa barisnya benar tertulis. Kamu satu-satunya penulis berkas itu.' + (reviewer ? " Khusus Execute: langkah R dulu; marker ditulis sesudah `Verdict: lulus`." : ""),
|
|
13093
13551
|
`3. \`Status: sebagian\`/\`terhalang\`, galat, atau laporan tanpa bukti \u2192 delegasikan ULANG SEKALI ke subagent fase yang sama${retry} dengan laporan gagalnya disertakan (\`Percobaan: 2/2\`). Gagal lagi \u2192 BERHENTI dan ${askEscalation} apa yang harus dilakukan. Aturan ini berlaku walau klausa otonomi di prompt ini menyuruhmu tak bertanya.`,
|
|
13094
13552
|
`4. \`Status: menunggu-keputusan\` atau \`Keputusan terbuka:\` yang bukan \`-\` \u2192 SEBELUM menulis marker fase atau memanggil fase berikutnya, ajukan SEMUA pertanyaannya ${askDecisions}, lalu LANJUTKAN subagent yang SAMA lewat ${resume} dengan seluruh jawabannya. Ulangi selama subagent masih melaporkan keputusan terbuka \u2014 tak ada batas jumlah pertanyaan maupun putaran, dan giliran relay ini bukan percobaan ulang. JANGAN pernah menjawab sendiri, termasuk di sesi scheduler: yang menjawab ${DECIDER} \u2014 hanoman yang merutekannya, kamu cukup bertanya.${turnTaking}`,
|
|
13095
13553
|
o.fastPath ? '5. `Rekomendasi fase: jalur-cepat` sesudah Audit \u2192 SEBELUM lanjut ke Execute: jalankan `echo "Spec skipped" >> "$HANOMAN_PHASE_FILE"` lalu `echo "Plan skipped" >> "$HANOMAN_PHASE_FILE"` (gerbang yang sama seperti langkah 2), lalu lanjut ke Execute. `penuh` \u2192 Spec \u2192 Plan \u2192 Execute.' : "",
|
|
13554
|
+
reviewStep,
|
|
13096
13555
|
handoff,
|
|
13097
13556
|
"DILARANG mengerjakan isi fase sendiri \u2014 termasuk saat subagent gagal. Menulis `skipped` untuk fase yang dilewati bukan mengerjakannya.",
|
|
13098
13557
|
'Pekerjaan ini BELUM selesai sampai SEMUA fase di daftar di atas punya baris `done` atau `skipped` di $HANOMAN_PHASE_FILE \u2014 JANGAN commit/push final atau menyatakan tuntas sebelum itu. Periksa dengan `cat "$HANOMAN_PHASE_FILE"` sebelum commit terakhir.'
|
|
@@ -13108,7 +13567,7 @@ function startPrompt(flow2, spec, branchTo, autonomy, verifyScope, method, attac
|
|
|
13108
13567
|
orchestratorClause(plan, { fastPath: flow2 === "qa" }),
|
|
13109
13568
|
auditContinuationForOrchestrator(flow2, spec),
|
|
13110
13569
|
autonomyClause(autonomy),
|
|
13111
|
-
|
|
13570
|
+
orchestratorAttachmentClause(attachments),
|
|
13112
13571
|
push,
|
|
13113
13572
|
specContext(spec)
|
|
13114
13573
|
].filter(Boolean).join("\n\n");
|
|
@@ -13139,10 +13598,10 @@ function continuePrompt(flow2, spec, branchTo, autonomy, verifyScope, method, at
|
|
|
13139
13598
|
`JANGAN mengulang fase awal \u2014 spec & plan sudah ada di ${m.planDir}/**. Lanjutkan HANYA fase Execute lewat subagent fasenya.`,
|
|
13140
13599
|
// S3 · berkas fase tak pernah dikosongkan: baris `Execute done` run lama masih ada dan akan
|
|
13141
13600
|
// memenuhi gerbang penutup sebelum agen Execute sesi ini bekerja sama sekali.
|
|
13142
|
-
"$HANOMAN_PHASE_FILE masih memuat baris `Execute done` LAMA dari sesi sebelumnya \u2014 baris itu TIDAK berlaku untuk sesi ini dan tidak memenuhi gerbang penutup. Sesudah agen fase Execute SESI INI melapor `Status: selesai
|
|
13601
|
+
"$HANOMAN_PHASE_FILE masih memuat baris `Execute done` LAMA dari sesi sebelumnya \u2014 baris itu TIDAK berlaku untuk sesi ini dan tidak memenuhi gerbang penutup. Sesudah agen fase Execute SESI INI melapor `Status: selesai` (dan lolos langkah R bila ada), tulis baris `Execute done` BARU (verifikasi `tail -1`); commit/push final baru sah sesudah baris baru itu.",
|
|
13143
13602
|
orchestratorClause(plan),
|
|
13144
13603
|
autonomyClause(autonomy),
|
|
13145
|
-
|
|
13604
|
+
orchestratorAttachmentClause(attachments),
|
|
13146
13605
|
push,
|
|
13147
13606
|
specContext(spec)
|
|
13148
13607
|
].filter(Boolean).join("\n\n");
|
|
@@ -13172,7 +13631,7 @@ function resumePrompt(flow2, spec, branchTo, resume, autonomy, verifyScope, meth
|
|
|
13172
13631
|
resumed,
|
|
13173
13632
|
orchestratorClause(plan, { fastPath: flow2 === "qa" && !auditDecided }),
|
|
13174
13633
|
autonomyClause(autonomy),
|
|
13175
|
-
|
|
13634
|
+
orchestratorAttachmentClause(attachments),
|
|
13176
13635
|
push,
|
|
13177
13636
|
specContext(spec)
|
|
13178
13637
|
].filter(Boolean).join("\n\n");
|
|
@@ -13205,7 +13664,7 @@ function startGoalPrompt(flow2, spec, branchTo, opts = {}) {
|
|
|
13205
13664
|
goalDetail(spec),
|
|
13206
13665
|
orchestratorClause(opts.plan),
|
|
13207
13666
|
autonomyClause(opts.autonomy),
|
|
13208
|
-
|
|
13667
|
+
orchestratorAttachmentClause(opts.attachments),
|
|
13209
13668
|
push,
|
|
13210
13669
|
goalBlock(spec)
|
|
13211
13670
|
].filter(Boolean).join("\n\n");
|
|
@@ -13328,7 +13787,7 @@ Stack: ${project.stack || "\u2014"}`,
|
|
|
13328
13787
|
${cron.prompt}`
|
|
13329
13788
|
].join("\n\n");
|
|
13330
13789
|
}
|
|
13331
|
-
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, PROJECT_METHOD, specContext, goalDetail, goalBlock, goalContext, projectContext, scaffoldContext, prdBriefBlock, prdAuditBlock, prdContext, breakdownContext, prdPhaseLines, breakdownPhaseLines, resumeClause, RESUMED_WORKTREE_NOTE, REVERSE_PHASE_GUIDE, SCAFFOLD_PHASE_GUIDE;
|
|
13790
|
+
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;
|
|
13332
13791
|
var init_prompt = __esm({
|
|
13333
13792
|
"../runner/src/prompt.ts"() {
|
|
13334
13793
|
"use strict";
|
|
@@ -13414,6 +13873,10 @@ ${list2}
|
|
|
13414
13873
|
BACA semuanya SEBELUM mengerjakan fase pertama \u2014 dokumen teks (.md/.txt/.log/.json/.csv) baca langsung, PDF dan gambar baca lewat path berkasnya. Ini konteks yang dipegang manusia saat memfilekan item ini; mengabaikannya berarti bekerja dengan konteks yang lebih miskin darinya.
|
|
13415
13874
|
Lampiran bisa BERTAMBAH atau BERKURANG selagi sesi berjalan. Di AWAL SETIAP FASE, baca ulang manifest \`${ctx.dir}/INDEX.md\` lalu baca lampiran yang belum pernah kamu baca \u2014 daftar di atas adalah keadaan saat sesi ini lahir, bukan keadaan tetap.`;
|
|
13416
13875
|
};
|
|
13876
|
+
orchestratorAttachmentClause = (ctx) => {
|
|
13877
|
+
if (!ctx || ctx.items.length === 0) return "";
|
|
13878
|
+
return `LAMPIRAN backlog item ini: ${ctx.items.length} berkas saat sesi lahir, manifestnya \`${ctx.dir}/INDEX.md\` (selalu segar \u2014 lampiran bisa bertambah/berkurang). JANGAN membaca lampiran untuk mengerjakan fase: cantumkan path manifest itu di baris \`Lampiran:\` blok serah-terima SETIAP fase \u2014 agen fase yang membacanya.`;
|
|
13879
|
+
};
|
|
13417
13880
|
PROJECT_METHOD = resolveMethod();
|
|
13418
13881
|
specContext = (spec) => {
|
|
13419
13882
|
const detail = spec.payload ? `
|
|
@@ -13864,6 +14327,157 @@ var init_agent_cli = __esm({
|
|
|
13864
14327
|
}
|
|
13865
14328
|
});
|
|
13866
14329
|
|
|
14330
|
+
// ../runner/src/agent-readonly.ts
|
|
14331
|
+
import { chmodSync, writeFileSync } from "node:fs";
|
|
14332
|
+
import { join } from "node:path";
|
|
14333
|
+
function denyReadOnly(detail) {
|
|
14334
|
+
return { allowed: false, reason: `Hanoman read-only policy: ${detail}` };
|
|
14335
|
+
}
|
|
14336
|
+
function tokenizeReadOnlyCommand(command) {
|
|
14337
|
+
const tokens = [];
|
|
14338
|
+
let token = "";
|
|
14339
|
+
let quote3 = "";
|
|
14340
|
+
let escaped = false;
|
|
14341
|
+
let active = false;
|
|
14342
|
+
for (let index = 0; index < command.length; index++) {
|
|
14343
|
+
const char = command[index];
|
|
14344
|
+
if (escaped) {
|
|
14345
|
+
token += char;
|
|
14346
|
+
escaped = false;
|
|
14347
|
+
active = true;
|
|
14348
|
+
continue;
|
|
14349
|
+
}
|
|
14350
|
+
if (quote3) {
|
|
14351
|
+
if (char === quote3) quote3 = "";
|
|
14352
|
+
else if (char === "\\" && quote3 === '"') escaped = true;
|
|
14353
|
+
else token += char;
|
|
14354
|
+
active = true;
|
|
14355
|
+
continue;
|
|
14356
|
+
}
|
|
14357
|
+
if (char === "'" || char === '"') {
|
|
14358
|
+
quote3 = char;
|
|
14359
|
+
active = true;
|
|
14360
|
+
} else if (char === "\\") {
|
|
14361
|
+
escaped = true;
|
|
14362
|
+
active = true;
|
|
14363
|
+
} else if (/\s/.test(char)) {
|
|
14364
|
+
if (active) {
|
|
14365
|
+
tokens.push(token);
|
|
14366
|
+
token = "";
|
|
14367
|
+
active = false;
|
|
14368
|
+
}
|
|
14369
|
+
} else {
|
|
14370
|
+
token += char;
|
|
14371
|
+
active = true;
|
|
14372
|
+
}
|
|
14373
|
+
}
|
|
14374
|
+
if (quote3 || escaped) return null;
|
|
14375
|
+
if (active) tokens.push(token);
|
|
14376
|
+
return tokens;
|
|
14377
|
+
}
|
|
14378
|
+
function evaluateReadOnlyPayload(payload, policy, environment) {
|
|
14379
|
+
if (!payload || typeof payload !== "object") return denyReadOnly("payload hook tidak sah");
|
|
14380
|
+
const event = payload;
|
|
14381
|
+
const tool = typeof event.tool_name === "string" ? event.tool_name : typeof event.toolName === "string" ? event.toolName : "";
|
|
14382
|
+
if (!tool) return denyReadOnly("nama tool tidak tersedia");
|
|
14383
|
+
if (policy.directTools.includes(tool)) return { allowed: true };
|
|
14384
|
+
if (policy.deniedTools.includes(tool) || tool.startsWith("mcp__")) {
|
|
14385
|
+
return denyReadOnly(`tool ${tool} dapat mengubah state`);
|
|
14386
|
+
}
|
|
14387
|
+
if (!policy.shellTools.includes(tool)) return denyReadOnly(`tool ${tool} tidak terbukti read-only`);
|
|
14388
|
+
const rawInput = event.tool_input;
|
|
14389
|
+
const input = rawInput && typeof rawInput === "object" ? rawInput : {};
|
|
14390
|
+
const command = typeof input.command === "string" ? input.command : typeof input.cmd === "string" ? input.cmd : "";
|
|
14391
|
+
const trimmed = command.trim();
|
|
14392
|
+
if (!trimmed) return denyReadOnly("perintah shell kosong atau tidak dikenal");
|
|
14393
|
+
if (/\r|\n|;|&&|\|\||\||[<>]|\$|`/.test(trimmed)) {
|
|
14394
|
+
return denyReadOnly("operator shell yang dapat merangkai atau menulis dilarang");
|
|
14395
|
+
}
|
|
14396
|
+
const tokens = tokenizeReadOnlyCommand(trimmed);
|
|
14397
|
+
if (!tokens?.length) return denyReadOnly("perintah shell tidak dapat diparse dengan aman");
|
|
14398
|
+
const first = tokens[0] ?? "";
|
|
14399
|
+
const commandName = first.split("/").pop() ?? "";
|
|
14400
|
+
if (first !== commandName) {
|
|
14401
|
+
return denyReadOnly("executable ber-path tidak diizinkan; gunakan command allowlist dari PATH");
|
|
14402
|
+
}
|
|
14403
|
+
if (commandName === "git") {
|
|
14404
|
+
const subcommand = tokens[1] ?? "";
|
|
14405
|
+
if (!policy.gitCommands.includes(subcommand)) {
|
|
14406
|
+
return denyReadOnly(`git ${subcommand || "<kosong>"} bukan operasi baca yang diizinkan`);
|
|
14407
|
+
}
|
|
14408
|
+
const args = tokens.slice(2);
|
|
14409
|
+
if (args.some((arg) => arg === "--output" || arg.startsWith("--output=") || arg === "--ext-diff" || arg === "--textconv")) {
|
|
14410
|
+
return denyReadOnly("opsi git dapat menulis atau menjalankan helper eksternal");
|
|
14411
|
+
}
|
|
14412
|
+
if (subcommand !== "status" && (!args.includes("--no-ext-diff") || !args.includes("--no-textconv"))) {
|
|
14413
|
+
return denyReadOnly("git diff/show/log wajib menonaktifkan helper eksternal dan textconv");
|
|
14414
|
+
}
|
|
14415
|
+
return { allowed: true };
|
|
14416
|
+
}
|
|
14417
|
+
if (!policy.shellCommands.includes(commandName)) {
|
|
14418
|
+
return denyReadOnly(`perintah ${commandName || "<kosong>"} tidak terbukti read-only`);
|
|
14419
|
+
}
|
|
14420
|
+
if (commandName === "rg" && environment.RIPGREP_CONFIG_PATH?.trim()) {
|
|
14421
|
+
return denyReadOnly("RIPGREP_CONFIG_PATH dapat menyuntikkan preprocessor eksternal");
|
|
14422
|
+
}
|
|
14423
|
+
if (commandName === "rg" && tokens.slice(1).some((arg) => arg === "--pre" || arg.startsWith("--pre=") || arg === "--pre-glob" || arg.startsWith("--pre-glob=") || arg === "--hostname-bin" || arg.startsWith("--hostname-bin="))) {
|
|
14424
|
+
return denyReadOnly("opsi rg dapat menjalankan helper eksternal");
|
|
14425
|
+
}
|
|
14426
|
+
if (commandName === "sed") {
|
|
14427
|
+
const quiet = tokens[1] === "-n" || tokens[1] === "--quiet" || tokens[1] === "--silent";
|
|
14428
|
+
const printOnly = /^\d+(?:,\d+)?p$/.test(tokens[2] ?? "");
|
|
14429
|
+
const files = tokens.slice(3);
|
|
14430
|
+
if (!quiet || !printOnly || files.length === 0 || files.some((arg) => arg.startsWith("-"))) {
|
|
14431
|
+
return denyReadOnly("hanya sed -n '<baris>[,<baris>]p' <berkas> yang diizinkan");
|
|
14432
|
+
}
|
|
14433
|
+
}
|
|
14434
|
+
return { allowed: true };
|
|
14435
|
+
}
|
|
14436
|
+
function readOnlyDecision(payload, environment = process.env) {
|
|
14437
|
+
return evaluateReadOnlyPayload(payload, POLICY, environment);
|
|
14438
|
+
}
|
|
14439
|
+
function readOnlyHookSource() {
|
|
14440
|
+
return [
|
|
14441
|
+
'"use strict";',
|
|
14442
|
+
`const denyReadOnly = ${denyReadOnly.toString()};`,
|
|
14443
|
+
`const tokenizeReadOnlyCommand = ${tokenizeReadOnlyCommand.toString()};`,
|
|
14444
|
+
`const evaluate = ${evaluateReadOnlyPayload.toString()};`,
|
|
14445
|
+
`const policy = ${JSON.stringify(POLICY)};`,
|
|
14446
|
+
"let input = '';",
|
|
14447
|
+
"process.stdin.setEncoding('utf8');",
|
|
14448
|
+
"process.stdin.on('data', chunk => { input += chunk; });",
|
|
14449
|
+
"process.stdin.on('end', () => {",
|
|
14450
|
+
" let payload;",
|
|
14451
|
+
" try { payload = JSON.parse(input); } catch { payload = null; }",
|
|
14452
|
+
" const decision = evaluate(payload, policy, process.env);",
|
|
14453
|
+
" if (!decision.allowed) { process.stderr.write(decision.reason + '\\n'); process.exitCode = 2; }",
|
|
14454
|
+
"});",
|
|
14455
|
+
"process.stdin.resume();",
|
|
14456
|
+
""
|
|
14457
|
+
].join("\n");
|
|
14458
|
+
}
|
|
14459
|
+
function writeReadOnlyHook(dir2) {
|
|
14460
|
+
const path = join(dir2, "custom-agent-readonly.cjs");
|
|
14461
|
+
writeFileSync(path, readOnlyHookSource(), { mode: 384 });
|
|
14462
|
+
chmodSync(path, 384);
|
|
14463
|
+
return { path, command: `node ${shellQuote(path)}` };
|
|
14464
|
+
}
|
|
14465
|
+
var POLICY, READ_ONLY_POLICY, shellQuote;
|
|
14466
|
+
var init_agent_readonly = __esm({
|
|
14467
|
+
"../runner/src/agent-readonly.ts"() {
|
|
14468
|
+
"use strict";
|
|
14469
|
+
POLICY = {
|
|
14470
|
+
directTools: ["Read", "Glob", "Grep", "WebFetch", "WebSearch"],
|
|
14471
|
+
shellTools: ["Bash", "local_shell", "exec_command"],
|
|
14472
|
+
deniedTools: ["Write", "Edit", "Task", "apply_patch", "spawn_agent"],
|
|
14473
|
+
shellCommands: ["rg", "sed", "head", "tail", "wc", "ls"],
|
|
14474
|
+
gitCommands: ["diff", "show", "status", "log"]
|
|
14475
|
+
};
|
|
14476
|
+
READ_ONLY_POLICY = POLICY;
|
|
14477
|
+
shellQuote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
|
|
14478
|
+
}
|
|
14479
|
+
});
|
|
14480
|
+
|
|
13867
14481
|
// ../runner/src/custom-agents.ts
|
|
13868
14482
|
function phasePromptOf(def2, file) {
|
|
13869
14483
|
if (def2.context === void 0) return def2.instructions;
|
|
@@ -13873,37 +14487,61 @@ function phasePromptOf(def2, file) {
|
|
|
13873
14487
|
${PHASE_CONTEXT_HEADER}
|
|
13874
14488
|
${body}`;
|
|
13875
14489
|
}
|
|
13876
|
-
function
|
|
14490
|
+
function agentContractOf(policy) {
|
|
14491
|
+
return [
|
|
14492
|
+
"Kontrak kerja custom agent hanoman \u2014 bagian dari instruksimu, bukan lampiran opsional.",
|
|
14493
|
+
"",
|
|
14494
|
+
...policyLines(policy),
|
|
14495
|
+
"",
|
|
14496
|
+
...handoffClause(),
|
|
14497
|
+
"",
|
|
14498
|
+
CODE_STYLE_CLAUSE,
|
|
14499
|
+
""
|
|
14500
|
+
].join("\n");
|
|
14501
|
+
}
|
|
14502
|
+
function agentPromptOf(def2, roster, runtime = "claude", contractPath) {
|
|
13877
14503
|
const can = liveMentions(def2, roster);
|
|
13878
|
-
const contract = [
|
|
14504
|
+
const contract = contractPath ? [
|
|
13879
14505
|
def2.instructions,
|
|
13880
14506
|
"",
|
|
13881
14507
|
"---",
|
|
13882
|
-
...
|
|
14508
|
+
...contractReference(def2, contractPath),
|
|
14509
|
+
...ownPolicyLines(def2),
|
|
14510
|
+
...workLimitClause(def2, runtime)
|
|
14511
|
+
] : [
|
|
14512
|
+
def2.instructions,
|
|
14513
|
+
"",
|
|
14514
|
+
"---",
|
|
14515
|
+
...policyLines(policyOf(def2)),
|
|
14516
|
+
...ownPolicyLines(def2),
|
|
13883
14517
|
...workLimitClause(def2, runtime),
|
|
13884
14518
|
"",
|
|
13885
14519
|
...handoffClause()
|
|
13886
14520
|
];
|
|
14521
|
+
const style = contractPath ? [] : ["", CODE_STYLE_CLAUSE];
|
|
13887
14522
|
if (can.length === 0) {
|
|
13888
14523
|
return [
|
|
13889
14524
|
...contract,
|
|
13890
14525
|
"",
|
|
13891
14526
|
"---",
|
|
13892
14527
|
"Kamu TIDAK boleh mendelegasikan ke agen lain. Selesaikan sendiri lalu laporkan hasilnya.",
|
|
13893
|
-
|
|
13894
|
-
CODE_STYLE_CLAUSE
|
|
14528
|
+
...style
|
|
13895
14529
|
].join("\n");
|
|
13896
14530
|
}
|
|
13897
14531
|
const list2 = can.map((m) => `@${m}`).join(", ");
|
|
14532
|
+
const reviewers = can.filter((m) => roster.find((r) => r.name === m)?.workspacePolicy === "read-only");
|
|
14533
|
+
const reviewClause = reviewers.length === 0 ? [] : [
|
|
14534
|
+
`Sebelum melapor \`Status: selesai\`, commit hasilmu lalu panggil ${reviewers.map((m) => `@${m}`).join(", ")} untuk mereview perubahanmu: serahkan tujuan, scope berkas, base SHA dan SHA hasil sebagai NILAI heksadesimal literal (ia membaca \`git diff --no-ext-diff --no-textconv <base> <hasil>\`). Perbaiki temuan yang terbukti lalu commit lagi; cantumkan putusan reviewer dan temuan yang sengaja tak diperbaiki (beserta alasannya) di laporanmu. Reviewer yang terhalang bukan alasan mengklaim lolos review.`
|
|
14535
|
+
];
|
|
13898
14536
|
return [
|
|
13899
14537
|
...contract,
|
|
13900
14538
|
"",
|
|
13901
14539
|
"---",
|
|
13902
14540
|
`Kamu boleh mendelegasikan HANYA ke: ${list2}. Panggil lewat ${MENTION_TOOL} dengan nama agennya.`,
|
|
14541
|
+
...reviewClause,
|
|
13903
14542
|
`Anggaran rantai delegasi seluruh sesi ini ${MENTION_MAX_HOPS} hop. Bila kamu sudah berada di hop ke-${MENTION_MAX_HOPS}, JANGAN mendelegasikan lagi \u2014 selesaikan sendiri lalu laporkan.`,
|
|
13904
14543
|
"Sebutkan hop keberapa kamu berada saat mendelegasikan, dan jangan pernah memanggil agen yang sudah ada di rantai yang membawamu ke sini.",
|
|
13905
|
-
|
|
13906
|
-
CODE_STYLE_CLAUSE
|
|
14544
|
+
...style
|
|
13907
14545
|
].join("\n");
|
|
13908
14546
|
}
|
|
13909
14547
|
function renderAgentsJson(defs, options2 = {}) {
|
|
@@ -13921,10 +14559,13 @@ function renderAgentsJson(defs, options2 = {}) {
|
|
|
13921
14559
|
}
|
|
13922
14560
|
const resolvedTools = resolveTools({ tools: d.tools, mentions: d.mentions });
|
|
13923
14561
|
const readOnly = d.workspacePolicy === "read-only";
|
|
14562
|
+
const tools = readOnly ? resolvedTools.filter((tool) => READ_ONLY_TOOLS.has(tool)) : resolvedTools;
|
|
14563
|
+
const policy = policyOf(d);
|
|
14564
|
+
const contract = tools.includes("Read") ? options2.agentContractFiles?.find((f) => f.policy === policy && f.content === agentContractOf(policy)) : void 0;
|
|
13924
14565
|
out4[d.name] = {
|
|
13925
14566
|
description: d.description,
|
|
13926
|
-
prompt: agentPromptOf(d, defs, "claude") + (options2.promptSuffix ?? ""),
|
|
13927
|
-
tools
|
|
14567
|
+
prompt: agentPromptOf(d, defs, "claude", contract?.path) + (options2.promptSuffix ?? ""),
|
|
14568
|
+
tools,
|
|
13928
14569
|
...d.model ? { model: d.model } : {},
|
|
13929
14570
|
...d.effort ? { effort: d.effort } : {},
|
|
13930
14571
|
...typeof d.maxTurns === "number" ? { maxTurns: d.maxTurns } : {},
|
|
@@ -13952,52 +14593,109 @@ function agentDelegationClause(defs, runtime = "claude") {
|
|
|
13952
14593
|
return [
|
|
13953
14594
|
"",
|
|
13954
14595
|
"",
|
|
13955
|
-
`Delegasikan tugas yang relevan melalui ${codex2 ? "spawn_agent" : MENTION_TOOL}. Sertakan tujuan, scope, base SHA
|
|
14596
|
+
`Delegasikan tugas yang relevan melalui ${codex2 ? "spawn_agent" : MENTION_TOOL}. Sertakan tujuan, scope, base SHA dan kandidat SHA sebagai nilai literal, bukti sebelumnya, dan aturan verifikasi. Agen isolated-worktree hanya melihat COMMIT: commit kandidat dulu, lalu ambil hasilnya dari SHA yang ia laporkan (\`git cherry-pick <sha>\`). Tinjau hasil subagent sebelum digunakan. Bila laporannya memuat \`Keputusan terbuka:\` yang bukan \`-\`, JANGAN menjawabnya sendiri: ${relay}. Yang menjawab ${DECIDER}.`,
|
|
13956
14597
|
""
|
|
13957
14598
|
].join("\n");
|
|
13958
14599
|
}
|
|
13959
|
-
var PHASE_CONTEXT_HEADER, liveMentions,
|
|
14600
|
+
var PHASE_CONTEXT_HEADER, liveMentions, policyOf, READ_ONLY_DENIED_OPERATORS, READ_ONLY_DENIED_COMMANDS, deniedCommandsProse, readOnlyAllowlistLines, NO_FAKE_EXPERIMENT, policyLines, ownPolicyLines, workLimitClause, STATUS_LINE, handoffClause, contractReference, READ_ONLY_TOOLS;
|
|
13960
14601
|
var init_custom_agents = __esm({
|
|
13961
14602
|
"../runner/src/custom-agents.ts"() {
|
|
13962
14603
|
"use strict";
|
|
13963
14604
|
init_src();
|
|
13964
14605
|
init_code_style();
|
|
13965
14606
|
init_prompt();
|
|
14607
|
+
init_agent_readonly();
|
|
13966
14608
|
PHASE_CONTEXT_HEADER = "=== KONTEKS ===";
|
|
13967
14609
|
liveMentions = (def2, roster) => {
|
|
13968
14610
|
if (def2.workspacePolicy === "read-only") return [];
|
|
13969
14611
|
const names = new Set(roster.map((r) => r.name));
|
|
13970
14612
|
return def2.mentions.filter((m) => names.has(m) && m !== def2.name);
|
|
13971
14613
|
};
|
|
13972
|
-
|
|
13973
|
-
|
|
14614
|
+
policyOf = (def2) => def2.workspacePolicy ?? "inherit";
|
|
14615
|
+
READ_ONLY_DENIED_OPERATORS = ["|", ";", "&&", "<", ">", "$", "`"];
|
|
14616
|
+
READ_ONLY_DENIED_COMMANDS = [
|
|
14617
|
+
"cat",
|
|
14618
|
+
"grep",
|
|
14619
|
+
"find",
|
|
14620
|
+
"git blame",
|
|
14621
|
+
"git grep",
|
|
14622
|
+
"git rev-parse",
|
|
14623
|
+
"git merge-base",
|
|
14624
|
+
"pnpm",
|
|
14625
|
+
"node",
|
|
14626
|
+
"curl"
|
|
14627
|
+
];
|
|
14628
|
+
deniedCommandsProse = () => {
|
|
14629
|
+
const git3 = READ_ONLY_DENIED_COMMANDS.filter((c) => c.startsWith("git ")).map((c) => c.slice(4));
|
|
14630
|
+
const out4 = [];
|
|
14631
|
+
for (const c of READ_ONLY_DENIED_COMMANDS) {
|
|
14632
|
+
if (!c.startsWith("git ")) out4.push(c);
|
|
14633
|
+
else if (!out4.some((o) => o.startsWith("git "))) out4.push(`git ${git3.join("/")}`);
|
|
14634
|
+
}
|
|
14635
|
+
return out4.join(", ");
|
|
14636
|
+
};
|
|
14637
|
+
readOnlyAllowlistLines = () => {
|
|
14638
|
+
const shell = READ_ONLY_POLICY.shellCommands.map((c) => "`" + c + "`").join(", ");
|
|
14639
|
+
const gitReads = READ_ONLY_POLICY.gitCommands.filter((c) => c !== "status").join("|");
|
|
14640
|
+
const operators = READ_ONLY_DENIED_OPERATORS.map((o) => o === "`" ? "backtick" : "`" + o + "`").join(" ");
|
|
14641
|
+
return [
|
|
14642
|
+
`Bash hanya menerima: ${shell} (sed hanya \`sed -n 'A,Bp' <berkas>\`), \`git status\`, dan`,
|
|
14643
|
+
`\`git ${gitReads}\` yang WAJIB membawa \`--no-ext-diff --no-textconv\`.`,
|
|
14644
|
+
`Ditolak: ${operators} \u2014 juga di dalam kutip \u2014 serta ${deniedCommandsProse()}.`,
|
|
14645
|
+
"Regex (termasuk alternasi `a|b`) pakai tool Grep; isi berkas pakai tool Read. Diff kandidat:",
|
|
14646
|
+
"`git diff --no-ext-diff --no-textconv <baseSha>` + `git status --porcelain` untuk berkas baru;",
|
|
14647
|
+
"SHA wajib literal heksadesimal."
|
|
14648
|
+
];
|
|
14649
|
+
};
|
|
14650
|
+
NO_FAKE_EXPERIMENT = "Jangan mengklaim eksperimen telah dijalankan tanpa output yang benar-benar kamu terima.";
|
|
14651
|
+
policyLines = (policy) => {
|
|
13974
14652
|
if (policy === "read-only") {
|
|
13975
|
-
const root = def2.name === "root-causer" ? [
|
|
13976
|
-
"Untuk root-causer, lakukan diagnosis statis dari bukti yang sudah tersedia. Labeli hipotesis",
|
|
13977
|
-
"yang belum terbukti dan berikan rencana eksperimen untuk parent; jangan menjalankan reproduksi",
|
|
13978
|
-
"yang memerlukan eksekusi atau mutasi di workspace ini."
|
|
13979
|
-
] : [];
|
|
13980
14653
|
return [
|
|
13981
14654
|
"Policy efektif: read-only. Inspeksi statis saja; jangan mengubah workspace atau menjalankan",
|
|
13982
14655
|
"operasi yang ditolak validator read-only.",
|
|
13983
|
-
...
|
|
13984
|
-
|
|
14656
|
+
...readOnlyAllowlistLines(),
|
|
14657
|
+
NO_FAKE_EXPERIMENT
|
|
13985
14658
|
];
|
|
13986
14659
|
}
|
|
13987
14660
|
if (policy === "isolated-worktree") {
|
|
13988
|
-
const root = def2.name === "root-causer" ? ["Untuk root-causer, kamu boleh mereproduksi dan menjalankan eksperimen hanya di worktree terisolasi ini."] : [];
|
|
13989
14661
|
return [
|
|
13990
14662
|
"Policy efektif: isolated-worktree. Semua tulisan, test patch, reproduksi, dan eksperimen",
|
|
13991
14663
|
"harus tetap di worktree terisolasi yang diberikan; jangan menyentuh worktree parent.",
|
|
13992
|
-
|
|
13993
|
-
"
|
|
14664
|
+
"Worktree ini lahir dari sebuah COMMIT, bukan dari pohon kerja parent: perubahan parent yang belum",
|
|
14665
|
+
"di-commit TIDAK ada di sini. Langkah 0, sebelum kerja apa pun: jalankan `git rev-parse HEAD` dan",
|
|
14666
|
+
"`git status --porcelain`, lalu bandingkan dengan `Kandidat SHA` dari parent. Bila berbeda, pindah",
|
|
14667
|
+
"dengan `git checkout --detach <kandidat>` (penulis: `git checkout -B <nama-task> <kandidat>`). Bila",
|
|
14668
|
+
"parent tidak memberi SHA literal atau checkout gagal, laporkan `Status: terhalang` dan berhenti.",
|
|
14669
|
+
"Worktree baru belum punya dependensi/artefak generate: siapkan SEKALI sesuai resep test di",
|
|
14670
|
+
"AGENTS.md/CLAUDE.md proyek (termasuk isolasi DB test). Bila gagal, `Status: terhalang`.",
|
|
14671
|
+
// Audit P0-5 · pertahanan kedua: env pane bisa mewarisi DATABASE_URL operasional hanoman.
|
|
14672
|
+
"Jangan menjalankan migrate/db push/test terhadap `DATABASE_URL` yang menunjuk `~/.hanoman`;",
|
|
14673
|
+
"pakai DB sekali-pakai.",
|
|
14674
|
+
"Hasilmu tidak otomatis sampai ke parent: commit dengan `git add <path>` eksplisit, lalu laporkan",
|
|
14675
|
+
"`SHA dasar`, `SHA hasil`, nama branch, dan path worktree agar parent bisa `git cherry-pick`.",
|
|
14676
|
+
NO_FAKE_EXPERIMENT
|
|
13994
14677
|
];
|
|
13995
14678
|
}
|
|
13996
14679
|
return [
|
|
13997
14680
|
"Policy efektif: inherit. Ikuti izin workspace sesi parent dan jangan memperluas scope sendiri.",
|
|
13998
|
-
|
|
14681
|
+
NO_FAKE_EXPERIMENT
|
|
13999
14682
|
];
|
|
14000
14683
|
};
|
|
14684
|
+
ownPolicyLines = (def2) => {
|
|
14685
|
+
if (def2.name !== "root-causer") return [];
|
|
14686
|
+
const policy = policyOf(def2);
|
|
14687
|
+
if (policy === "read-only") {
|
|
14688
|
+
return [
|
|
14689
|
+
"Untuk root-causer, lakukan diagnosis statis dari bukti yang sudah tersedia. Labeli tiap hipotesis",
|
|
14690
|
+
"`terbukti-statis`/`belum-terbukti`/`gugur` dan berikan rencana eksperimen untuk parent; jangan",
|
|
14691
|
+
"menjalankan reproduksi yang memerlukan eksekusi atau mutasi di workspace ini."
|
|
14692
|
+
];
|
|
14693
|
+
}
|
|
14694
|
+
if (policy === "isolated-worktree") {
|
|
14695
|
+
return ["Untuk root-causer, kamu boleh mereproduksi dan menjalankan eksperimen hanya di worktree terisolasi ini."];
|
|
14696
|
+
}
|
|
14697
|
+
return [];
|
|
14698
|
+
};
|
|
14001
14699
|
workLimitClause = (def2, runtime) => {
|
|
14002
14700
|
const lines2 = [];
|
|
14003
14701
|
if (typeof def2.maxTurns === "number") {
|
|
@@ -14010,11 +14708,12 @@ var init_custom_agents = __esm({
|
|
|
14010
14708
|
}
|
|
14011
14709
|
return lines2;
|
|
14012
14710
|
};
|
|
14711
|
+
STATUS_LINE = "`Status: selesai | sebagian | terhalang | menunggu-keputusan`";
|
|
14013
14712
|
handoffClause = () => [
|
|
14014
14713
|
"Kontrak serah-terima:",
|
|
14015
|
-
"- Masukan yang harus kamu gunakan: tujuan, scope, base SHA
|
|
14016
|
-
"
|
|
14017
|
-
|
|
14714
|
+
"- Masukan yang harus kamu gunakan: tujuan, scope, base SHA dan kandidat SHA sebagai NILAI heksadesimal",
|
|
14715
|
+
" (bukan nama variabel), bukti sebelumnya, dan aturan verifikasi. Bila ada yang hilang, nyatakan batasnya.",
|
|
14716
|
+
`- Awali laporan dengan ${STATUS_LINE}.`,
|
|
14018
14717
|
"- Laporkan simpulan, jangkar bukti, tingkat keyakinan, scope yang belum diperiksa, dan langkah",
|
|
14019
14718
|
" berikutnya. Batas laporan: maksimal 12 temuan utama dan maksimal 1200 kata.",
|
|
14020
14719
|
// ADR-0167 · batas temuan di atas bukan batas pertanyaan: keputusan terbuka tak berbatas.
|
|
@@ -14024,6 +14723,12 @@ var init_custom_agents = __esm({
|
|
|
14024
14723
|
" Jangan memutuskannya sendiri: bila ada, laporkan `Status: menunggu-keputusan` dan berhenti.",
|
|
14025
14724
|
` Yang menjawab ${DECIDER}.`
|
|
14026
14725
|
];
|
|
14726
|
+
contractReference = (def2, path) => [
|
|
14727
|
+
`Policy efektif: ${policyOf(def2)}. Rincian policy ini, kontrak serah-terima, dan gaya kode ada di berkas`,
|
|
14728
|
+
`\`${path}\`. Baca berkas itu UTUH dengan tool Read SEBELUM mengerjakan apa pun \u2014 isinya bagian dari`,
|
|
14729
|
+
"instruksimu, bukan lampiran opsional. Berkas itu hanya-baca dan berada di luar worktree; jangan menyalin",
|
|
14730
|
+
`atau meng-commit-nya. Laporanmu tetap diawali ${STATUS_LINE}.`
|
|
14731
|
+
];
|
|
14027
14732
|
READ_ONLY_TOOLS = /* @__PURE__ */ new Set(["Read", "Glob", "Grep", "Bash", "WebFetch", "WebSearch"]);
|
|
14028
14733
|
}
|
|
14029
14734
|
});
|
|
@@ -14046,8 +14751,8 @@ var init_runtime_profile = __esm({
|
|
|
14046
14751
|
});
|
|
14047
14752
|
|
|
14048
14753
|
// ../runner/src/codex-agent-config.ts
|
|
14049
|
-
import { chmodSync, writeFileSync } from "node:fs";
|
|
14050
|
-
import { join } from "node:path";
|
|
14754
|
+
import { chmodSync as chmodSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
14755
|
+
import { join as join2 } from "node:path";
|
|
14051
14756
|
function codexNativeVersionProbe(env, codexBin3 = env.HANOMAN_CODEX_BIN ?? "codex") {
|
|
14052
14757
|
const sandbox = env.HANOMAN_SESSION_SANDBOX ?? (resolveHardening(env) ? "required" : "off");
|
|
14053
14758
|
if (sandbox !== "podman") return { bin: codexBin3, args: ["--version"] };
|
|
@@ -14064,7 +14769,7 @@ function codexNativeVersionProbe(env, codexBin3 = env.HANOMAN_CODEX_BIN ?? "code
|
|
|
14064
14769
|
env.HANOMAN_SESSION_IMAGE ?? "hanoman-agent:latest",
|
|
14065
14770
|
"/bin/sh",
|
|
14066
14771
|
"-lc",
|
|
14067
|
-
`${
|
|
14772
|
+
`${shellQuote2(codexBin3)} --version`
|
|
14068
14773
|
]
|
|
14069
14774
|
};
|
|
14070
14775
|
}
|
|
@@ -14133,8 +14838,8 @@ function materializeCodexAgents(defs, tempDir, options2 = {}) {
|
|
|
14133
14838
|
}))
|
|
14134
14839
|
};
|
|
14135
14840
|
}
|
|
14136
|
-
const write2 = options2.writeFile ?? ((path, content) =>
|
|
14137
|
-
const chmod3 = options2.chmod ??
|
|
14841
|
+
const write2 = options2.writeFile ?? ((path, content) => writeFileSync2(path, content, { mode: 384 }));
|
|
14842
|
+
const chmod3 = options2.chmod ?? chmodSync2;
|
|
14138
14843
|
const successful = [];
|
|
14139
14844
|
const warnings = [];
|
|
14140
14845
|
for (const [index, def2] of defs.entries()) {
|
|
@@ -14145,7 +14850,7 @@ function materializeCodexAgents(defs, tempDir, options2 = {}) {
|
|
|
14145
14850
|
});
|
|
14146
14851
|
continue;
|
|
14147
14852
|
}
|
|
14148
|
-
const path =
|
|
14853
|
+
const path = join2(tempDir, `${String(index).padStart(2, "0")}-${safeFilename(def2.name)}.toml`);
|
|
14149
14854
|
try {
|
|
14150
14855
|
write2(path, renderCodexAgentToml(def2, defs, options2));
|
|
14151
14856
|
chmod3(path, 384);
|
|
@@ -14181,14 +14886,14 @@ function materializeCodexAgents(defs, tempDir, options2 = {}) {
|
|
|
14181
14886
|
liveDefs
|
|
14182
14887
|
};
|
|
14183
14888
|
}
|
|
14184
|
-
var
|
|
14889
|
+
var shellQuote2, TOML_SHORT_ESCAPES, tomlString, tomlKey, safeFilename;
|
|
14185
14890
|
var init_codex_agent_config = __esm({
|
|
14186
14891
|
"../runner/src/codex-agent-config.ts"() {
|
|
14187
14892
|
"use strict";
|
|
14188
14893
|
init_src();
|
|
14189
14894
|
init_custom_agents();
|
|
14190
14895
|
init_runtime_profile();
|
|
14191
|
-
|
|
14896
|
+
shellQuote2 = (value) => `'${value.replaceAll("'", "'\\''")}'`;
|
|
14192
14897
|
TOML_SHORT_ESCAPES = {
|
|
14193
14898
|
8: "\\b",
|
|
14194
14899
|
9: "\\t",
|
|
@@ -14204,156 +14909,6 @@ var init_codex_agent_config = __esm({
|
|
|
14204
14909
|
}
|
|
14205
14910
|
});
|
|
14206
14911
|
|
|
14207
|
-
// ../runner/src/agent-readonly.ts
|
|
14208
|
-
import { chmodSync as chmodSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
14209
|
-
import { join as join2 } from "node:path";
|
|
14210
|
-
function denyReadOnly(detail) {
|
|
14211
|
-
return { allowed: false, reason: `Hanoman read-only policy: ${detail}` };
|
|
14212
|
-
}
|
|
14213
|
-
function tokenizeReadOnlyCommand(command) {
|
|
14214
|
-
const tokens = [];
|
|
14215
|
-
let token = "";
|
|
14216
|
-
let quote3 = "";
|
|
14217
|
-
let escaped = false;
|
|
14218
|
-
let active = false;
|
|
14219
|
-
for (let index = 0; index < command.length; index++) {
|
|
14220
|
-
const char = command[index];
|
|
14221
|
-
if (escaped) {
|
|
14222
|
-
token += char;
|
|
14223
|
-
escaped = false;
|
|
14224
|
-
active = true;
|
|
14225
|
-
continue;
|
|
14226
|
-
}
|
|
14227
|
-
if (quote3) {
|
|
14228
|
-
if (char === quote3) quote3 = "";
|
|
14229
|
-
else if (char === "\\" && quote3 === '"') escaped = true;
|
|
14230
|
-
else token += char;
|
|
14231
|
-
active = true;
|
|
14232
|
-
continue;
|
|
14233
|
-
}
|
|
14234
|
-
if (char === "'" || char === '"') {
|
|
14235
|
-
quote3 = char;
|
|
14236
|
-
active = true;
|
|
14237
|
-
} else if (char === "\\") {
|
|
14238
|
-
escaped = true;
|
|
14239
|
-
active = true;
|
|
14240
|
-
} else if (/\s/.test(char)) {
|
|
14241
|
-
if (active) {
|
|
14242
|
-
tokens.push(token);
|
|
14243
|
-
token = "";
|
|
14244
|
-
active = false;
|
|
14245
|
-
}
|
|
14246
|
-
} else {
|
|
14247
|
-
token += char;
|
|
14248
|
-
active = true;
|
|
14249
|
-
}
|
|
14250
|
-
}
|
|
14251
|
-
if (quote3 || escaped) return null;
|
|
14252
|
-
if (active) tokens.push(token);
|
|
14253
|
-
return tokens;
|
|
14254
|
-
}
|
|
14255
|
-
function evaluateReadOnlyPayload(payload, policy, environment) {
|
|
14256
|
-
if (!payload || typeof payload !== "object") return denyReadOnly("payload hook tidak sah");
|
|
14257
|
-
const event = payload;
|
|
14258
|
-
const tool = typeof event.tool_name === "string" ? event.tool_name : typeof event.toolName === "string" ? event.toolName : "";
|
|
14259
|
-
if (!tool) return denyReadOnly("nama tool tidak tersedia");
|
|
14260
|
-
if (policy.directTools.includes(tool)) return { allowed: true };
|
|
14261
|
-
if (policy.deniedTools.includes(tool) || tool.startsWith("mcp__")) {
|
|
14262
|
-
return denyReadOnly(`tool ${tool} dapat mengubah state`);
|
|
14263
|
-
}
|
|
14264
|
-
if (!policy.shellTools.includes(tool)) return denyReadOnly(`tool ${tool} tidak terbukti read-only`);
|
|
14265
|
-
const rawInput = event.tool_input;
|
|
14266
|
-
const input = rawInput && typeof rawInput === "object" ? rawInput : {};
|
|
14267
|
-
const command = typeof input.command === "string" ? input.command : typeof input.cmd === "string" ? input.cmd : "";
|
|
14268
|
-
const trimmed = command.trim();
|
|
14269
|
-
if (!trimmed) return denyReadOnly("perintah shell kosong atau tidak dikenal");
|
|
14270
|
-
if (/\r|\n|;|&&|\|\||\||[<>]|\$|`/.test(trimmed)) {
|
|
14271
|
-
return denyReadOnly("operator shell yang dapat merangkai atau menulis dilarang");
|
|
14272
|
-
}
|
|
14273
|
-
const tokens = tokenizeReadOnlyCommand(trimmed);
|
|
14274
|
-
if (!tokens?.length) return denyReadOnly("perintah shell tidak dapat diparse dengan aman");
|
|
14275
|
-
const first = tokens[0] ?? "";
|
|
14276
|
-
const commandName = first.split("/").pop() ?? "";
|
|
14277
|
-
if (first !== commandName) {
|
|
14278
|
-
return denyReadOnly("executable ber-path tidak diizinkan; gunakan command allowlist dari PATH");
|
|
14279
|
-
}
|
|
14280
|
-
if (commandName === "git") {
|
|
14281
|
-
const subcommand = tokens[1] ?? "";
|
|
14282
|
-
if (!policy.gitCommands.includes(subcommand)) {
|
|
14283
|
-
return denyReadOnly(`git ${subcommand || "<kosong>"} bukan operasi baca yang diizinkan`);
|
|
14284
|
-
}
|
|
14285
|
-
const args = tokens.slice(2);
|
|
14286
|
-
if (args.some((arg) => arg === "--output" || arg.startsWith("--output=") || arg === "--ext-diff" || arg === "--textconv")) {
|
|
14287
|
-
return denyReadOnly("opsi git dapat menulis atau menjalankan helper eksternal");
|
|
14288
|
-
}
|
|
14289
|
-
if (subcommand !== "status" && (!args.includes("--no-ext-diff") || !args.includes("--no-textconv"))) {
|
|
14290
|
-
return denyReadOnly("git diff/show/log wajib menonaktifkan helper eksternal dan textconv");
|
|
14291
|
-
}
|
|
14292
|
-
return { allowed: true };
|
|
14293
|
-
}
|
|
14294
|
-
if (!policy.shellCommands.includes(commandName)) {
|
|
14295
|
-
return denyReadOnly(`perintah ${commandName || "<kosong>"} tidak terbukti read-only`);
|
|
14296
|
-
}
|
|
14297
|
-
if (commandName === "rg" && environment.RIPGREP_CONFIG_PATH?.trim()) {
|
|
14298
|
-
return denyReadOnly("RIPGREP_CONFIG_PATH dapat menyuntikkan preprocessor eksternal");
|
|
14299
|
-
}
|
|
14300
|
-
if (commandName === "rg" && tokens.slice(1).some((arg) => arg === "--pre" || arg.startsWith("--pre=") || arg === "--pre-glob" || arg.startsWith("--pre-glob=") || arg === "--hostname-bin" || arg.startsWith("--hostname-bin="))) {
|
|
14301
|
-
return denyReadOnly("opsi rg dapat menjalankan helper eksternal");
|
|
14302
|
-
}
|
|
14303
|
-
if (commandName === "sed") {
|
|
14304
|
-
const quiet = tokens[1] === "-n" || tokens[1] === "--quiet" || tokens[1] === "--silent";
|
|
14305
|
-
const printOnly = /^\d+(?:,\d+)?p$/.test(tokens[2] ?? "");
|
|
14306
|
-
const files = tokens.slice(3);
|
|
14307
|
-
if (!quiet || !printOnly || files.length === 0 || files.some((arg) => arg.startsWith("-"))) {
|
|
14308
|
-
return denyReadOnly("hanya sed -n '<baris>[,<baris>]p' <berkas> yang diizinkan");
|
|
14309
|
-
}
|
|
14310
|
-
}
|
|
14311
|
-
return { allowed: true };
|
|
14312
|
-
}
|
|
14313
|
-
function readOnlyDecision(payload, environment = process.env) {
|
|
14314
|
-
return evaluateReadOnlyPayload(payload, POLICY, environment);
|
|
14315
|
-
}
|
|
14316
|
-
function readOnlyHookSource() {
|
|
14317
|
-
return [
|
|
14318
|
-
'"use strict";',
|
|
14319
|
-
`const denyReadOnly = ${denyReadOnly.toString()};`,
|
|
14320
|
-
`const tokenizeReadOnlyCommand = ${tokenizeReadOnlyCommand.toString()};`,
|
|
14321
|
-
`const evaluate = ${evaluateReadOnlyPayload.toString()};`,
|
|
14322
|
-
`const policy = ${JSON.stringify(POLICY)};`,
|
|
14323
|
-
"let input = '';",
|
|
14324
|
-
"process.stdin.setEncoding('utf8');",
|
|
14325
|
-
"process.stdin.on('data', chunk => { input += chunk; });",
|
|
14326
|
-
"process.stdin.on('end', () => {",
|
|
14327
|
-
" let payload;",
|
|
14328
|
-
" try { payload = JSON.parse(input); } catch { payload = null; }",
|
|
14329
|
-
" const decision = evaluate(payload, policy, process.env);",
|
|
14330
|
-
" if (!decision.allowed) { process.stderr.write(decision.reason + '\\n'); process.exitCode = 2; }",
|
|
14331
|
-
"});",
|
|
14332
|
-
"process.stdin.resume();",
|
|
14333
|
-
""
|
|
14334
|
-
].join("\n");
|
|
14335
|
-
}
|
|
14336
|
-
function writeReadOnlyHook(dir2) {
|
|
14337
|
-
const path = join2(dir2, "custom-agent-readonly.cjs");
|
|
14338
|
-
writeFileSync2(path, readOnlyHookSource(), { mode: 384 });
|
|
14339
|
-
chmodSync2(path, 384);
|
|
14340
|
-
return { path, command: `node ${shellQuote2(path)}` };
|
|
14341
|
-
}
|
|
14342
|
-
var POLICY, shellQuote2;
|
|
14343
|
-
var init_agent_readonly = __esm({
|
|
14344
|
-
"../runner/src/agent-readonly.ts"() {
|
|
14345
|
-
"use strict";
|
|
14346
|
-
POLICY = {
|
|
14347
|
-
directTools: ["Read", "Glob", "Grep", "WebFetch", "WebSearch"],
|
|
14348
|
-
shellTools: ["Bash", "local_shell", "exec_command"],
|
|
14349
|
-
deniedTools: ["Write", "Edit", "Task", "apply_patch", "spawn_agent"],
|
|
14350
|
-
shellCommands: ["rg", "sed", "head", "tail", "wc", "ls"],
|
|
14351
|
-
gitCommands: ["diff", "show", "status", "log"]
|
|
14352
|
-
};
|
|
14353
|
-
shellQuote2 = (value) => `'${value.replaceAll("'", "'\\''")}'`;
|
|
14354
|
-
}
|
|
14355
|
-
});
|
|
14356
|
-
|
|
14357
14912
|
// ../runner/src/agent-definition.ts
|
|
14358
14913
|
import { createHash } from "node:crypto";
|
|
14359
14914
|
function agentDefinitionHash(def2, roster, runtime, inherited = {}) {
|
|
@@ -14403,10 +14958,13 @@ function backlogGuide(flow2, phase, ctx) {
|
|
|
14403
14958
|
return "- Objective: baca dokumen spec dari fase Brainstorm (path-nya di serah-terima), lalu tambahkan bagian `## Objective` berisi SATU objective terukur beserta kriteria suksesnya.";
|
|
14404
14959
|
case "Spec":
|
|
14405
14960
|
return flow2 === "qa" ? `- Spec: tulis dokumen spec perbaikan di \`${m.specDir}/\` dari dokumen audit fase Audit (path-nya di serah-terima): akar masalah, bentuk perbaikan, dan acceptance criteria gaya EARS.` + (ctx.fromAudit ? ` Backlog ini LANJUTAN audit ${ctx.fromAudit} \u2014 fase Audit sengaja DILEWATI orchestrator, jadi baca dokumennya di \`internal/docs/research/audit-${ctx.fromAudit.toLowerCase()}-*.md\` (path juga ada di serah-terima) sebagai temuan; jangan menginvestigasi ulang dari nol.` : "") : "- Spec: lengkapi dokumen spec yang sama \u2014 arsitektur, komponen, kontrak data/API, penanganan galat, dan acceptance criteria gaya EARS. Perbarui docs Source of Truth yang tersentuh.";
|
|
14961
|
+
// ADR-0170 P2 · gerbang plan server mencocokkan spec-id di NAMA berkas; plan tanpa spec-id (nama
|
|
14962
|
+
// default skill) lolos hampa. Nama dikunci seperti Brainstorm, dan path persisnya diteruskan
|
|
14963
|
+
// orchestrator ke Execute & reviewer — bukan pola glob yang bisa mencocoki plan backlog lain.
|
|
14406
14964
|
case "Plan":
|
|
14407
|
-
return `- Plan: tulis plan implementasi berkotak \`- [ ]\` di \`${m.planDir}
|
|
14965
|
+
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.`;
|
|
14408
14966
|
case "Execute":
|
|
14409
|
-
return `- Execute: kerjakan plan di \`${m.planDir}
|
|
14967
|
+
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.`;
|
|
14410
14968
|
case "Audit":
|
|
14411
14969
|
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`.";
|
|
14412
14970
|
case "Laporan":
|
|
@@ -14432,15 +14990,16 @@ function phaseAgentInstructions(entry, index, plan, ctx) {
|
|
|
14432
14990
|
const { flow: flow2, method } = ctx;
|
|
14433
14991
|
const phase = entry.phase;
|
|
14434
14992
|
const guide = PROJECT_FLOWS.has(flow2) ? projectGuide(flow2, phase, ctx) : backlogGuide(flow2, phase, ctx);
|
|
14435
|
-
const skills = phaseSkillsFor(flow2, phase, method);
|
|
14993
|
+
const skills = phaseSkillsFor(flow2, phase, method, plan.runtime);
|
|
14436
14994
|
const work = WORK_PHASES.includes(phase);
|
|
14995
|
+
const verifies = work || phase === "Verifikasi";
|
|
14437
14996
|
return [
|
|
14438
14997
|
`Kamu agen fase "${phase}" (fase ${index + 1}/${plan.phases.length}) flow ${flow2} hanoman. Ikuti internal/docs sebagai Source of Truth; perbarui docs yang tersentuh dan link-nya di index, dalam commit yang sama.`,
|
|
14439
14998
|
guide,
|
|
14440
14999
|
DOC_WRITING_PHASES.has(phase) ? `=== STANDAR DOCS ===
|
|
14441
15000
|
${REVERSE_STANDARD}` : "",
|
|
14442
15001
|
skills.length ? `Skills ${method.label} WAJIB untuk fase ini \u2014 muat & ikuti dengan mekanisme yang tersedia di agenmu: ` + skills.join(", ") : "",
|
|
14443
|
-
|
|
15002
|
+
verifies ? scopeClause(flow2, ctx.verifyScope) : "",
|
|
14444
15003
|
work ? codeStyleClause(flow2) : "",
|
|
14445
15004
|
methodClause(method),
|
|
14446
15005
|
ATTACHMENT_NOTE,
|
|
@@ -14453,7 +15012,7 @@ ${REVERSE_STANDARD}` : "",
|
|
|
14453
15012
|
].filter(Boolean).join("\n\n");
|
|
14454
15013
|
}
|
|
14455
15014
|
function buildPhaseAgents(plan, ctx) {
|
|
14456
|
-
|
|
15015
|
+
const phases2 = plan.phases.map((entry, index) => ({
|
|
14457
15016
|
kind: "phase",
|
|
14458
15017
|
phase: entry.phase,
|
|
14459
15018
|
name: entry.agentName,
|
|
@@ -14467,14 +15026,87 @@ function buildPhaseAgents(plan, ctx) {
|
|
|
14467
15026
|
effort: entry.effort,
|
|
14468
15027
|
mentions: []
|
|
14469
15028
|
}));
|
|
15029
|
+
const r = plan.reviewer;
|
|
15030
|
+
if (!r || !plan.phases.some((p3) => p3.phase === r.phase)) return phases2;
|
|
15031
|
+
return [...phases2, {
|
|
15032
|
+
kind: "phase",
|
|
15033
|
+
phase: r.phase,
|
|
15034
|
+
name: r.agentName,
|
|
15035
|
+
description: `Reviewer independen hasil fase Execute flow ${plan.flow} hanoman \u2014 hanya dipanggil orchestrator sesi ini, sesudah Execute melapor selesai.`,
|
|
15036
|
+
instructions: reviewerInstructions(plan, ctx),
|
|
15037
|
+
context: ctx.context,
|
|
15038
|
+
tools: null,
|
|
15039
|
+
model: r.model,
|
|
15040
|
+
effort: r.effort,
|
|
15041
|
+
mentions: []
|
|
15042
|
+
}];
|
|
15043
|
+
}
|
|
15044
|
+
function reviewerInstructions(plan, ctx) {
|
|
15045
|
+
const m = ctx.method;
|
|
15046
|
+
return [
|
|
15047
|
+
`Kamu reviewer independen hasil fase Execute flow ${plan.flow} hanoman. Kamu dipanggil orchestrator sesudah agen Execute melapor \`Status: selesai\` dan SEBELUM fase itu ditutup. Kamu pengadu janji: yang kamu nilai bukan bagus-tidaknya kode, melainkan apakah yang DIMINTA benar-benar ada dan TERBUKTI pada keadaan akhir worktree ini.`,
|
|
15048
|
+
"Prosedur:",
|
|
15049
|
+
[
|
|
15050
|
+
`1. Baca dokumen spec & plan dari baris \`Artefak:\` serah-terima (qa jalur-cepat: dokumen audit adalah doc-of-record; bila path tak ada, cari berkas ber-spec-id di \`${m.specDir}/\` & \`${m.planDir}/\`). Plan yang menyimpang dari spec adalah temuan.`,
|
|
15051
|
+
"2. Ubah acceptance criteria jadi daftar yang diperiksa SATU PER SATU. Kalimat tak terukur ditandai tak terukur, bukan ditafsirkan.",
|
|
15052
|
+
'3. Baca `git diff "$HANOMAN_BASE_SHA"...HEAD` dan `git status --porcelain` (perubahan belum di-commit adalah temuan). Laporan Execute dan kotak `- [x]` hanyalah klaim.',
|
|
15053
|
+
"4. Jalankan test yang relevan dengan berkas yang berubah, sesuai scope verifikasi di bawah, dan baca outputnya sendiri.",
|
|
15054
|
+
`5. Per kriteria putuskan salah satu: ${SPEC_AUDIT_VERDICT_LIST}. Kriteria tanpa jangkar di diff bukan otomatis gagal \u2014 cari bukti keadaan akhir; tak bisa dibuktikan \u2192 belum terverifikasi.`
|
|
15055
|
+
].join("\n"),
|
|
15056
|
+
scopeClause(plan.flow, ctx.verifyScope),
|
|
15057
|
+
REVIEWER_RULES,
|
|
15058
|
+
PHASE_AGENT_AUTONOMY,
|
|
15059
|
+
[
|
|
15060
|
+
"Kontrak laporan (wajib, di akhir, maksimal 1000 kata):",
|
|
15061
|
+
"- Baris pertama: `Status: selesai` (review tuntas) | `terhalang` | `menunggu-keputusan`.",
|
|
15062
|
+
"- Tabel: AC \xB7 putusan \xB7 bukti (perintah + hasil yang kamu baca, atau path:baris).",
|
|
15063
|
+
"- Satu baris `Verdict: lulus` atau `Verdict: rework`.",
|
|
15064
|
+
"- `Temuan wajib diperbaiki:` daftar bernomor (path:baris, apa yang kurang terhadap AC, buktinya), atau `-`.",
|
|
15065
|
+
"- `Catatan (tak memblokir):` selera, gaya, refactor opsional, pekerjaan di luar yang diminta.",
|
|
15066
|
+
"- `Keputusan terbuka:` (wajib) AC yang bisa dibaca lebih dari satu cara, atau `-`. `selesai` hanya sah bila isinya `-`.",
|
|
15067
|
+
"`rework` HANYA untuk kekurangan nyata: AC tak terpenuhi / terpenuhi berbeda / tak terbukti karena buktinya memang tak ada, test merah, regresi, perubahan belum di-commit, atau plan masih `- [ ]` \u2014 bukan selera. Tanpa temuan semacam itu, `lulus`."
|
|
15068
|
+
].join("\n")
|
|
15069
|
+
].filter(Boolean).join("\n\n");
|
|
15070
|
+
}
|
|
15071
|
+
function phaseDelegationClause(phase, roster, runtime) {
|
|
15072
|
+
const role = roleOf(phase);
|
|
15073
|
+
if (role === "none") return "";
|
|
15074
|
+
const codex2 = runtime === "codex";
|
|
15075
|
+
const call = codex2 ? "spawn_agent" : "tool Agent";
|
|
15076
|
+
const live = liveCustoms(roster, runtime);
|
|
15077
|
+
const tag2 = (d) => [
|
|
15078
|
+
d.workspacePolicy === "read-only" ? "read-only" : d.workspacePolicy === "isolated-worktree" ? "worktree terpisah: commit kandidat dulu, hasil via SHA \u2192 cherry-pick" : "",
|
|
15079
|
+
d.model ?? "model sesi"
|
|
15080
|
+
].filter(Boolean).join(" \xB7 ");
|
|
15081
|
+
const who = live.length ? `Agen di sesi ini: ${live.map((d) => `\`${d.name}\` (${tag2(d)})`).join(", ")}. Pilih ` + (codex2 ? "nama agen" : "`subagent_type`") + " dari daftar ini sesuai deskripsinya." : "Tak ada custom agent di sesi ini: bila mendelegasikan, pakai subagent bawaan runtime yang read-only untuk pencarian.";
|
|
15082
|
+
const when = role === "research" ? "Kapan: pertanyaan yang butuh menyapu banyak berkas \u2014 pecah jadi 2\u20133 pencarian sempit & independen, panggil semuanya dalam SATU pesan (berjalan paralel) ke agen read-only termurah, lalu sintesis dan tulis artefak fasemu SENDIRI. Satu berkas/simbol yang sudah kamu tahu: cari sendiri." : "Kapan: satu implementer per task plan, BERURUTAN (implementer paralel di satu worktree saling tabrak), lalu review task itu sebelum menandai `- [x]`. Paralel (dalam SATU pesan) hanya untuk pembacaan read-only: pencarian konteks atau review. Worktree ini sudah terisolasi \u2014 jangan membuat worktree/branch baru; merge & push milik orchestrator.";
|
|
15083
|
+
return [
|
|
15084
|
+
"=== DELEGASI ===",
|
|
15085
|
+
`Subagent yang kamu panggil lewat ${call} berjalan foreground: kamu menunggu hasilnya.`,
|
|
15086
|
+
when,
|
|
15087
|
+
who,
|
|
15088
|
+
(codex2 ? "Hindari agen umum untuk pencarian \u2014 ia mewarisi model sesi yang mahal." : "Jangan pakai `general-purpose` untuk pencarian \u2014 ia mewarisi model sesi yang mahal" + (role === "execute" ? "; bila terpaksa (implementer tanpa agen yang cocok), isi parameter `model` eksplisit." : ".")) + " JANGAN memanggil `hanoman-fase-*`.",
|
|
15089
|
+
"Serah-terima ke anak: tujuan, scope berkas, Base SHA (NILAI hasil `echo $HANOMAN_BASE_SHA`, bukan nama variabelnya), jangkar path:baris yang sudah kamu tahu, dan bentuk laporan yang kamu minta. Anak DILARANG `git push` dan `git stash`, dan commit hanya dengan `git add <path>` eksplisit.",
|
|
15090
|
+
"Baca SEMUA laporan anak sebelum melapor \u2014 klaim anak bukan bukti sampai kamu periksa. `Keputusan terbuka:` anak diteruskan ke `Keputusan terbuka:` laporanmu, jangan dijawab sendiri."
|
|
15091
|
+
].join("\n");
|
|
14470
15092
|
}
|
|
14471
|
-
|
|
15093
|
+
function withPhaseDelegation(phaseDefs, roster, runtime) {
|
|
15094
|
+
return phaseDefs.map((def2) => {
|
|
15095
|
+
if (def2.kind !== "phase" || !def2.phase || def2.name === REVIEWER_AGENT_NAME) return def2;
|
|
15096
|
+
const clause = phaseDelegationClause(def2.phase, roster, runtime);
|
|
15097
|
+
return clause ? { ...def2, instructions: `${def2.instructions}
|
|
15098
|
+
|
|
15099
|
+
${clause}` } : def2;
|
|
15100
|
+
});
|
|
15101
|
+
}
|
|
15102
|
+
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;
|
|
14472
15103
|
var init_phase_agents = __esm({
|
|
14473
15104
|
"../runner/src/phase-agents.ts"() {
|
|
14474
15105
|
"use strict";
|
|
14475
15106
|
init_src();
|
|
14476
15107
|
init_prompt();
|
|
14477
15108
|
init_reverse_standard();
|
|
15109
|
+
REVIEWER_AGENT_NAME = phaseAgentName(REVIEWER_CELL);
|
|
14478
15110
|
PROJECT_FLOWS = /* @__PURE__ */ new Set(["reverse", "scaffold", "prd", "breakdown"]);
|
|
14479
15111
|
DOC_WRITING_PHASES = /* @__PURE__ */ new Set(["Docs teknis", "Konvensi & index", "Doc index"]);
|
|
14480
15112
|
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}.`;
|
|
@@ -14504,6 +15136,15 @@ var init_phase_agents = __esm({
|
|
|
14504
15136
|
TERMINAL_HANDOFF = "tulis ringkasan hasil + daftar pertanyaan yang belum terjawab ke terminal.";
|
|
14505
15137
|
PHASE_AGENT_HANDOFF = "tulis bagian `Ringkasan serah terima:` di laporanmu berisi ringkasan hasil + daftar pertanyaan yang belum terjawab (informasi untuk manusia, BUKAN `Keputusan terbuka:`) \u2014 orchestrator yang menampilkannya ke manusia.";
|
|
14506
15138
|
forPhaseAgent = (line) => line.replace(TERMINAL_ASK, (_m, a) => a + PHASE_AGENT_ASK).replace(TERMINAL_HANDOFF, PHASE_AGENT_HANDOFF);
|
|
15139
|
+
REVIEWER_RULES = [
|
|
15140
|
+
"Batas peran reviewer:",
|
|
15141
|
+
"- 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.",
|
|
15142
|
+
"- JANGAN menulis `$HANOMAN_PHASE_FILE` dan JANGAN memperbaiki temuanmu sendiri \u2014 perbaikan milik agen Execute, marker milik orchestrator.",
|
|
15143
|
+
`- JANGAN memanggil agen berawalan \`${PHASE_AGENT_PREFIX}\`.`,
|
|
15144
|
+
"- 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:`."
|
|
15145
|
+
].join("\n");
|
|
15146
|
+
roleOf = (phase) => phase === "Execute" || phase === "Goal" ? "execute" : phase === "Kerjakan" || phase === "Verifikasi" ? "none" : "research";
|
|
15147
|
+
liveCustoms = (roster, runtime) => roster.filter((d) => d.kind !== "phase" && !(runtime === "codex" && d.workspacePolicy === "isolated-worktree"));
|
|
14507
15148
|
}
|
|
14508
15149
|
});
|
|
14509
15150
|
|
|
@@ -15800,12 +16441,16 @@ __export(src_exports, {
|
|
|
15800
16441
|
PHASE_CONTEXT_HEADER: () => PHASE_CONTEXT_HEADER,
|
|
15801
16442
|
PIPELINES: () => PIPELINES,
|
|
15802
16443
|
PROJECT_METHOD: () => PROJECT_METHOD,
|
|
16444
|
+
READ_ONLY_DENIED_COMMANDS: () => READ_ONLY_DENIED_COMMANDS,
|
|
16445
|
+
READ_ONLY_DENIED_OPERATORS: () => READ_ONLY_DENIED_OPERATORS,
|
|
16446
|
+
READ_ONLY_POLICY: () => READ_ONLY_POLICY,
|
|
15803
16447
|
RESUMED_WORKTREE_NOTE: () => RESUMED_WORKTREE_NOTE,
|
|
15804
16448
|
REVERSE_PHASE_GUIDE: () => REVERSE_PHASE_GUIDE,
|
|
15805
16449
|
REVERSE_STANDARD: () => REVERSE_STANDARD,
|
|
15806
16450
|
SCAFFOLD_PHASE_GUIDE: () => SCAFFOLD_PHASE_GUIDE,
|
|
15807
16451
|
SUBAGENT_STATUSLINE_SCRIPT: () => SUBAGENT_STATUSLINE_SCRIPT,
|
|
15808
16452
|
WORK_PHASES: () => WORK_PHASES,
|
|
16453
|
+
agentContractOf: () => agentContractOf,
|
|
15809
16454
|
agentDefinitionHash: () => agentDefinitionHash,
|
|
15810
16455
|
agentDelegationClause: () => agentDelegationClause,
|
|
15811
16456
|
agentFlags: () => agentFlags,
|
|
@@ -15848,6 +16493,7 @@ __export(src_exports, {
|
|
|
15848
16493
|
orchestratorClause: () => orchestratorClause,
|
|
15849
16494
|
parseConfigEnv: () => parseConfigEnv,
|
|
15850
16495
|
phaseAgentInstructions: () => phaseAgentInstructions,
|
|
16496
|
+
phaseDelegationClause: () => phaseDelegationClause,
|
|
15851
16497
|
phasePromptOf: () => phasePromptOf,
|
|
15852
16498
|
phaseSkillsFor: () => phaseSkillsFor,
|
|
15853
16499
|
prdAuditBlock: () => prdAuditBlock,
|
|
@@ -15889,6 +16535,7 @@ __export(src_exports, {
|
|
|
15889
16535
|
tomlBasicString: () => tomlBasicString,
|
|
15890
16536
|
validateAgentEvalManifest: () => validateAgentEvalManifest,
|
|
15891
16537
|
verifyScopeClause: () => verifyScopeClause,
|
|
16538
|
+
withPhaseDelegation: () => withPhaseDelegation,
|
|
15892
16539
|
writeConfigEnv: () => writeConfigEnv,
|
|
15893
16540
|
writeReadOnlyHook: () => writeReadOnlyHook,
|
|
15894
16541
|
writeSubagentStatusline: () => writeSubagentStatusline,
|
|
@@ -16141,9 +16788,10 @@ async function readPhasesAsync(file, flow2) {
|
|
|
16141
16788
|
}
|
|
16142
16789
|
function enrichPhases(phases2, roster, invocations, doneSeenAt, now, bornAt2, doneAtBirth = /* @__PURE__ */ new Set()) {
|
|
16143
16790
|
return phases2.map((p3) => {
|
|
16144
|
-
const
|
|
16791
|
+
const own = phaseAgentName(p3.name);
|
|
16792
|
+
const r = roster.find((entry) => entry.phase === p3.name && entry.name === own) ?? roster.find((entry) => entry.phase === p3.name);
|
|
16145
16793
|
if (!r) return p3;
|
|
16146
|
-
const all = invocations.filter((i) => i.phase === p3.name);
|
|
16794
|
+
const all = invocations.filter((i) => i.phase === p3.name && (!i.agentName || i.agentName === r.name));
|
|
16147
16795
|
const mine = all.filter((i) => bornAt2 === 0 || Date.parse(i.startedAt) >= bornAt2).sort((a, b) => a.startedAt.localeCompare(b.startedAt));
|
|
16148
16796
|
const last2 = mine[mine.length - 1];
|
|
16149
16797
|
const bornDone = doneAtBirth.has(p3.name);
|
|
@@ -16189,8 +16837,9 @@ function stageFor(phases2) {
|
|
|
16189
16837
|
if (phases2[0]?.state === "active") best = Math.max(best, STAGES.indexOf("brainstorming"));
|
|
16190
16838
|
return best < 0 ? null : STAGES[best];
|
|
16191
16839
|
}
|
|
16192
|
-
function planComplete(worktree, specId) {
|
|
16840
|
+
function planComplete(worktree, specId, planPhaseDone = false) {
|
|
16193
16841
|
const re = new RegExp(`(^|[^a-z0-9])${specId.toLowerCase()}([^0-9]|$)`);
|
|
16842
|
+
let matched = false;
|
|
16194
16843
|
for (const rel of PLAN_DIRS) {
|
|
16195
16844
|
const dir2 = `${worktree}/${rel}`;
|
|
16196
16845
|
let names;
|
|
@@ -16201,16 +16850,18 @@ function planComplete(worktree, specId) {
|
|
|
16201
16850
|
}
|
|
16202
16851
|
for (const n2 of names) {
|
|
16203
16852
|
if (!re.test(n2.toLowerCase())) continue;
|
|
16853
|
+
matched = true;
|
|
16204
16854
|
try {
|
|
16205
16855
|
if (/^[ \t]*- \[ \]/m.test(readFileSync6(`${dir2}/${n2}`, "utf8"))) return false;
|
|
16206
16856
|
} catch {
|
|
16207
16857
|
}
|
|
16208
16858
|
}
|
|
16209
16859
|
}
|
|
16210
|
-
return true;
|
|
16860
|
+
return matched ? true : !planPhaseDone;
|
|
16211
16861
|
}
|
|
16212
|
-
async function planCompleteAsync(worktree, specId) {
|
|
16862
|
+
async function planCompleteAsync(worktree, specId, planPhaseDone = false) {
|
|
16213
16863
|
const re = new RegExp(`(^|[^a-z0-9])${specId.toLowerCase()}([^0-9]|$)`);
|
|
16864
|
+
let matched = false;
|
|
16214
16865
|
for (const rel of PLAN_DIRS) {
|
|
16215
16866
|
const dir2 = `${worktree}/${rel}`;
|
|
16216
16867
|
let names;
|
|
@@ -16221,31 +16872,34 @@ async function planCompleteAsync(worktree, specId) {
|
|
|
16221
16872
|
}
|
|
16222
16873
|
for (const n2 of names) {
|
|
16223
16874
|
if (!re.test(n2.toLowerCase())) continue;
|
|
16875
|
+
matched = true;
|
|
16224
16876
|
try {
|
|
16225
16877
|
if (/^[ \t]*- \[ \]/m.test(await readFile(`${dir2}/${n2}`, "utf8"))) return false;
|
|
16226
16878
|
} catch {
|
|
16227
16879
|
}
|
|
16228
16880
|
}
|
|
16229
16881
|
}
|
|
16230
|
-
return true;
|
|
16882
|
+
return matched ? true : !planPhaseDone;
|
|
16231
16883
|
}
|
|
16232
16884
|
function sessionComplete(phases2, worktree, specId) {
|
|
16233
16885
|
if (!phasesComplete(phases2)) return false;
|
|
16234
|
-
return specId ? planComplete(worktree, specId) : true;
|
|
16886
|
+
return specId ? planComplete(worktree, specId, phases2.find((p3) => p3.name === "Plan")?.state === "done") : true;
|
|
16235
16887
|
}
|
|
16236
16888
|
function stageForRun(phases2, worktree, specId) {
|
|
16237
16889
|
const s2 = stageFor(phases2);
|
|
16238
|
-
|
|
16890
|
+
const planPhaseDone = phases2.find((p3) => p3.name === "Plan")?.state === "done";
|
|
16891
|
+
if (s2 === "done" && !planComplete(worktree, specId, planPhaseDone)) return "executing";
|
|
16239
16892
|
return s2;
|
|
16240
16893
|
}
|
|
16241
16894
|
async function stageForRunAsync(phases2, worktree, specId) {
|
|
16242
16895
|
const s2 = stageFor(phases2);
|
|
16243
|
-
|
|
16896
|
+
const planPhaseDone = phases2.find((p3) => p3.name === "Plan")?.state === "done";
|
|
16897
|
+
if (s2 === "done" && !await planCompleteAsync(worktree, specId, planPhaseDone)) return "executing";
|
|
16244
16898
|
return s2;
|
|
16245
16899
|
}
|
|
16246
16900
|
async function sessionCompleteAsync(phases2, worktree, specId) {
|
|
16247
16901
|
if (!phasesComplete(phases2)) return false;
|
|
16248
|
-
return specId ? planCompleteAsync(worktree, specId) : true;
|
|
16902
|
+
return specId ? planCompleteAsync(worktree, specId, phases2.find((p3) => p3.name === "Plan")?.state === "done") : true;
|
|
16249
16903
|
}
|
|
16250
16904
|
var phaseFilePath, decisionFilePath, phaseFileMemo, derivePhases, readPhases, REACHED, phasesComplete;
|
|
16251
16905
|
var init_session_phases = __esm({
|
|
@@ -29607,7 +30261,7 @@ var require_mock_interceptor = __commonJS({
|
|
|
29607
30261
|
var require_mock_client = __commonJS({
|
|
29608
30262
|
"../node_modules/.pnpm/undici@7.29.1/node_modules/undici/lib/mock/mock-client.js"(exports, module) {
|
|
29609
30263
|
"use strict";
|
|
29610
|
-
var { promisify:
|
|
30264
|
+
var { promisify: promisify17 } = __require("node:util");
|
|
29611
30265
|
var Client = require_client();
|
|
29612
30266
|
var { buildMockDispatch } = require_mock_utils();
|
|
29613
30267
|
var {
|
|
@@ -29655,7 +30309,7 @@ var require_mock_client = __commonJS({
|
|
|
29655
30309
|
this[kDispatches] = [];
|
|
29656
30310
|
}
|
|
29657
30311
|
async [kClose]() {
|
|
29658
|
-
await
|
|
30312
|
+
await promisify17(this[kOriginalClose])();
|
|
29659
30313
|
this[kConnected] = 0;
|
|
29660
30314
|
this[kMockAgent][Symbols.kClients].delete(this[kOrigin]);
|
|
29661
30315
|
}
|
|
@@ -29868,7 +30522,7 @@ var require_mock_call_history = __commonJS({
|
|
|
29868
30522
|
var require_mock_pool = __commonJS({
|
|
29869
30523
|
"../node_modules/.pnpm/undici@7.29.1/node_modules/undici/lib/mock/mock-pool.js"(exports, module) {
|
|
29870
30524
|
"use strict";
|
|
29871
|
-
var { promisify:
|
|
30525
|
+
var { promisify: promisify17 } = __require("node:util");
|
|
29872
30526
|
var Pool = require_pool();
|
|
29873
30527
|
var { buildMockDispatch } = require_mock_utils();
|
|
29874
30528
|
var {
|
|
@@ -29916,7 +30570,7 @@ var require_mock_pool = __commonJS({
|
|
|
29916
30570
|
this[kDispatches] = [];
|
|
29917
30571
|
}
|
|
29918
30572
|
async [kClose]() {
|
|
29919
|
-
await
|
|
30573
|
+
await promisify17(this[kOriginalClose])();
|
|
29920
30574
|
this[kConnected] = 0;
|
|
29921
30575
|
this[kMockAgent][Symbols.kClients].delete(this[kOrigin]);
|
|
29922
30576
|
}
|
|
@@ -30240,7 +30894,7 @@ var require_snapshot_utils = __commonJS({
|
|
|
30240
30894
|
var require_snapshot_recorder = __commonJS({
|
|
30241
30895
|
"../node_modules/.pnpm/undici@7.29.1/node_modules/undici/lib/mock/snapshot-recorder.js"(exports, module) {
|
|
30242
30896
|
"use strict";
|
|
30243
|
-
var { writeFile: writeFile8, readFile:
|
|
30897
|
+
var { writeFile: writeFile8, readFile: readFile11, mkdir: mkdir12 } = __require("node:fs/promises");
|
|
30244
30898
|
var { dirname: dirname17, resolve: resolve24 } = __require("node:path");
|
|
30245
30899
|
var { setTimeout: setTimeout2, clearTimeout: clearTimeout2 } = __require("node:timers");
|
|
30246
30900
|
var { InvalidArgumentError, UndiciError } = require_errors();
|
|
@@ -30442,7 +31096,7 @@ var require_snapshot_recorder = __commonJS({
|
|
|
30442
31096
|
throw new InvalidArgumentError("Snapshot path is required");
|
|
30443
31097
|
}
|
|
30444
31098
|
try {
|
|
30445
|
-
const data = await
|
|
31099
|
+
const data = await readFile11(resolve24(path), "utf8");
|
|
30446
31100
|
const parsed = JSON.parse(data);
|
|
30447
31101
|
if (Array.isArray(parsed)) {
|
|
30448
31102
|
this.#snapshots.clear();
|
|
@@ -43058,7 +43712,7 @@ import { createRequire } from "node:module";
|
|
|
43058
43712
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
43059
43713
|
import { chmodSync as chmodSync6, mkdirSync as mkdirSync7, readFileSync as readFileSync8, rmSync as rmSync4, statSync as statSync2, writeFileSync as writeFileSync8 } from "node:fs";
|
|
43060
43714
|
import { stat as stat2 } from "node:fs/promises";
|
|
43061
|
-
import { dirname as dirname8 } from "node:path";
|
|
43715
|
+
import { dirname as dirname8, resolve as resolvePath } from "node:path";
|
|
43062
43716
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
43063
43717
|
function doneSeenOf(id2) {
|
|
43064
43718
|
const known2 = phaseDoneSeen.get(id2);
|
|
@@ -43100,12 +43754,47 @@ function phaseView(p3, phases2) {
|
|
|
43100
43754
|
new Set(p3.doneAtBirth ?? [])
|
|
43101
43755
|
);
|
|
43102
43756
|
}
|
|
43757
|
+
function fitCustomAgentsToArgBudget(customDefs, render, limit = AGENTS_ARG_SAFE_BYTES) {
|
|
43758
|
+
const byDefault = new Set(BUILTIN_AGENTS.filter((a) => a.enabledByDefault).map((a) => a.name));
|
|
43759
|
+
const kept = [...customDefs];
|
|
43760
|
+
const dropped = [];
|
|
43761
|
+
let json = render(kept);
|
|
43762
|
+
while (Buffer.byteLength(json) > limit && kept.length > 0) {
|
|
43763
|
+
let at = kept.length - 1;
|
|
43764
|
+
for (let i = kept.length - 1; i >= 0; i--) if (!byDefault.has(kept[i].name)) {
|
|
43765
|
+
at = i;
|
|
43766
|
+
break;
|
|
43767
|
+
}
|
|
43768
|
+
dropped.push(...kept.splice(at, 1));
|
|
43769
|
+
json = render(kept);
|
|
43770
|
+
}
|
|
43771
|
+
return { kept, dropped, json };
|
|
43772
|
+
}
|
|
43103
43773
|
function noTtyPromptEnv() {
|
|
43104
43774
|
const path = askpassDenyPath();
|
|
43105
43775
|
mkdirSync7(dirname8(path), { recursive: true, mode: 448 });
|
|
43106
43776
|
writeFileSync8(path, ASKPASS_DENY, { mode: 448 });
|
|
43107
43777
|
return { SSH_ASKPASS: path, SSH_ASKPASS_REQUIRE: "force", GIT_TERMINAL_PROMPT: "0" };
|
|
43108
43778
|
}
|
|
43779
|
+
function hanomanDbInheritUnsets(env = process.env) {
|
|
43780
|
+
const raw = env.DATABASE_URL?.trim();
|
|
43781
|
+
if (!raw?.startsWith("file:")) return [];
|
|
43782
|
+
let own;
|
|
43783
|
+
try {
|
|
43784
|
+
own = resolveDbUrl(env, process.cwd());
|
|
43785
|
+
} catch {
|
|
43786
|
+
return [];
|
|
43787
|
+
}
|
|
43788
|
+
const file = (u) => resolvePath(u.slice("file:".length));
|
|
43789
|
+
return file(raw) === file(own) ? ["DATABASE_URL"] : [];
|
|
43790
|
+
}
|
|
43791
|
+
function phaseSessionAgentEnv(agent, phaseFile) {
|
|
43792
|
+
if (agent !== "claude" || !phaseFile) return {};
|
|
43793
|
+
return {
|
|
43794
|
+
CLAUDE_CODE_DISABLE_BACKGROUND_TASKS: "1",
|
|
43795
|
+
CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS: String(SUBAGENT_CONCURRENCY_CAP)
|
|
43796
|
+
};
|
|
43797
|
+
}
|
|
43109
43798
|
function tmux(...args) {
|
|
43110
43799
|
try {
|
|
43111
43800
|
return execFileSync2("tmux", ["-L", socket(), "-f", "/dev/null", ...args], {
|
|
@@ -43366,8 +44055,9 @@ function createSession(projectId, cwd, opts = {}) {
|
|
|
43366
44055
|
mkdirSync7(tempDir, { recursive: true, mode: 448 });
|
|
43367
44056
|
let hook;
|
|
43368
44057
|
const readOnlyHookFor = () => customDefs.some((def2) => def2.workspacePolicy === "read-only") ? hook ??= writeReadOnlyHook(tempDir) : void 0;
|
|
43369
|
-
const attempt = (
|
|
43370
|
-
const
|
|
44058
|
+
const attempt = (requested) => {
|
|
44059
|
+
const phaseDefs = withPhaseDelegation(requested, customDefs, agentForDefs);
|
|
44060
|
+
let defs = [...phaseDefs, ...customDefs];
|
|
43371
44061
|
if (defs.length === 0) return [];
|
|
43372
44062
|
const readOnlyHook = readOnlyHookFor();
|
|
43373
44063
|
if (agentForDefs === "claude") {
|
|
@@ -43381,15 +44071,34 @@ function createSession(projectId, cwd, opts = {}) {
|
|
|
43381
44071
|
chmodSync6(path, 384);
|
|
43382
44072
|
phaseContextFile = { context: shared, path };
|
|
43383
44073
|
}
|
|
43384
|
-
const
|
|
43385
|
-
|
|
43386
|
-
|
|
43387
|
-
|
|
43388
|
-
|
|
44074
|
+
const agentContractFiles = [];
|
|
44075
|
+
for (const policy of new Set(customDefs.map((d) => d.workspacePolicy ?? "inherit"))) {
|
|
44076
|
+
const path = agentContractFilePath(id2, policy);
|
|
44077
|
+
const content = agentContractOf(policy);
|
|
44078
|
+
writeFileSync8(path, content, { mode: 384 });
|
|
44079
|
+
chmodSync6(path, 384);
|
|
44080
|
+
agentContractFiles.push({ policy, content, path });
|
|
44081
|
+
}
|
|
44082
|
+
const fit = fitCustomAgentsToArgBudget(customDefs, (kept) => renderAgentsJson(
|
|
44083
|
+
[...withPhaseDelegation(requested, kept, agentForDefs), ...kept],
|
|
44084
|
+
{
|
|
44085
|
+
readOnlyHookCommand: readOnlyHook?.command,
|
|
44086
|
+
agentContractFiles,
|
|
44087
|
+
...phaseContextFile ? { phaseContextFile } : {}
|
|
44088
|
+
}
|
|
44089
|
+
));
|
|
44090
|
+
const bytes = Buffer.byteLength(fit.json);
|
|
43389
44091
|
if (phaseDefs.length > 0 && bytes > AGENTS_ARG_SAFE_BYTES) {
|
|
43390
44092
|
return [`argumen --agents ${bytes} B melewati ambang aman ${AGENTS_ARG_SAFE_BYTES} B (batas satu argumen exec Linux 128 KiB)`];
|
|
43391
44093
|
}
|
|
43392
|
-
|
|
44094
|
+
for (const def2 of fit.dropped) {
|
|
44095
|
+
process.stderr.write(`hanoman: custom agent ${def2.name} dibuang dari sesi ${id2} \u2014 argumen --agents melewati ambang aman ${AGENTS_ARG_SAFE_BYTES} B (agen opt-in paling akhir dibuang lebih dulu)
|
|
44096
|
+
`);
|
|
44097
|
+
}
|
|
44098
|
+
customDefs = fit.kept;
|
|
44099
|
+
defs = [...withPhaseDelegation(requested, customDefs, agentForDefs), ...customDefs];
|
|
44100
|
+
if (defs.length === 0) return [];
|
|
44101
|
+
writeFileSync8(file, fit.json, { mode: 384 });
|
|
43393
44102
|
} catch (error) {
|
|
43394
44103
|
if (phaseDefs.length === 0) throw error;
|
|
43395
44104
|
return [error instanceof Error ? error.message : String(error)];
|
|
@@ -43502,9 +44211,12 @@ function createSession(projectId, cwd, opts = {}) {
|
|
|
43502
44211
|
mkdirSync7(dirname8(opts.phaseFile), { recursive: true });
|
|
43503
44212
|
envPairs.push(`HANOMAN_PHASE_FILE=${sq(opts.phaseFile)}`);
|
|
43504
44213
|
}
|
|
44214
|
+
if (!opts.command) {
|
|
44215
|
+
for (const [k, v] of Object.entries(phaseSessionAgentEnv(agent, opts.phaseFile))) envPairs.push(`${k}=${sq(v)}`);
|
|
44216
|
+
}
|
|
43505
44217
|
if (opts.attachmentsDir) envPairs.push(`HANOMAN_ATTACHMENTS_DIR=${sq(opts.attachmentsDir)}`);
|
|
43506
44218
|
for (const [k, v] of Object.entries(opts.env ?? {})) envPairs.push(`${k}=${sq(v)}`);
|
|
43507
|
-
const unsets = opts.command ? [] : suppressedInheritKeys().flatMap((k) => ["-u", k]);
|
|
44219
|
+
const unsets = opts.command ? [] : [...suppressedInheritKeys(), ...hanomanDbInheritUnsets()].flatMap((k) => ["-u", k]);
|
|
43508
44220
|
let cmd = unsets.length ? ["env", ...unsets, ...envPairs, argv].join(" ") : envPairs.length ? `${envPairs.join(" ")} ${argv}` : argv;
|
|
43509
44221
|
if (!opts.command) {
|
|
43510
44222
|
const wrapped = sandboxCommand({
|
|
@@ -43927,7 +44639,7 @@ function spawnPty(...args) {
|
|
|
43927
44639
|
);
|
|
43928
44640
|
}
|
|
43929
44641
|
}
|
|
43930
|
-
var socket, PREFIX, MAX_SCROLLBACK, SCROLLBACK_SLACK, POLL_MS, markerFilled, markerFilledAsync, clearMarker, PANE_QUIET_MS, paneQuiet, markerOnset, decisionOnset, attached, phaseInvocations, PHASE_DONE_SEEN_OPT, phaseDoneSeen, claudeBin, shellBin, codexBin, agentBin, rootBypassEnv, frame, name, promptFilePath, goalGatePath, goalStatePath, agentTempDir, agentsFilePath, phaseContextFilePath, AGENTS_ARG_SAFE_BYTES, askpassDenyPath, ASKPASS_DENY, NO_SERVER, TmuxError, sq, idFor, FMT, toSessionInfo, listSessions, listSessionsAsync, decisionsOf, getSession, getSessionAsync, hooks, emitBirth, emitDeath, customAgentSource, codexNativeAgentSupport, nativeAgentsAvailable, customAgentsFor, runGitDiff, sleep, paneText, paneIO, DIALOG_CAPTURE_LINES, GOAL_ARMED_MARKERS, goalArmed, COALESCE_MS, COALESCE_MAX_BYTES, trimScrollback, phaseKey, sessionFinishedAsync, poll, polling, TERMINAL_QUERY, stripTerminalQueries, CAPTURE_ARGS, detach;
|
|
44642
|
+
var socket, PREFIX, MAX_SCROLLBACK, SCROLLBACK_SLACK, POLL_MS, markerFilled, markerFilledAsync, clearMarker, PANE_QUIET_MS, paneQuiet, markerOnset, decisionOnset, attached, phaseInvocations, PHASE_DONE_SEEN_OPT, phaseDoneSeen, claudeBin, shellBin, codexBin, agentBin, rootBypassEnv, frame, name, promptFilePath, goalGatePath, goalStatePath, agentTempDir, agentsFilePath, phaseContextFilePath, AGENTS_ARG_SAFE_BYTES, agentContractFilePath, askpassDenyPath, ASKPASS_DENY, SUBAGENT_CONCURRENCY_CAP, NO_SERVER, TmuxError, sq, idFor, FMT, toSessionInfo, listSessions, listSessionsAsync, decisionsOf, getSession, getSessionAsync, hooks, emitBirth, emitDeath, customAgentSource, codexNativeAgentSupport, nativeAgentsAvailable, customAgentsFor, runGitDiff, sleep, paneText, paneIO, DIALOG_CAPTURE_LINES, GOAL_ARMED_MARKERS, goalArmed, COALESCE_MS, COALESCE_MAX_BYTES, trimScrollback, phaseKey, sessionFinishedAsync, poll, polling, TERMINAL_QUERY, stripTerminalQueries, CAPTURE_ARGS, detach;
|
|
43931
44643
|
var init_pty = __esm({
|
|
43932
44644
|
"src/services/pty.ts"() {
|
|
43933
44645
|
"use strict";
|
|
@@ -44003,12 +44715,14 @@ var init_pty = __esm({
|
|
|
44003
44715
|
agentsFilePath = (id2) => `${agentTempDir(id2)}/claude.json`;
|
|
44004
44716
|
phaseContextFilePath = (id2) => `${agentTempDir(id2)}/phase-context.md`;
|
|
44005
44717
|
AGENTS_ARG_SAFE_BYTES = 100 * 1024;
|
|
44718
|
+
agentContractFilePath = (id2, policy) => `${agentTempDir(id2)}/agent-contract-${policy}.md`;
|
|
44006
44719
|
askpassDenyPath = () => `${tmpdir3()}/hanoman-askpass/deny.sh`;
|
|
44007
44720
|
ASKPASS_DENY = `#!/bin/sh
|
|
44008
44721
|
echo "hanoman: tak ada manusia di pane ini \u2014 permintaan ketikan ditolak: $1" >&2
|
|
44009
44722
|
echo "hanoman: buka kuncinya di luar sesi (mis. ssh-add ~/.ssh/id_rsa), lalu ulangi." >&2
|
|
44010
44723
|
exit 1
|
|
44011
44724
|
`;
|
|
44725
|
+
SUBAGENT_CONCURRENCY_CAP = 3;
|
|
44012
44726
|
NO_SERVER = /^(?:no server running on .+|error connecting to .+ \((?:No such file or directory|Connection refused)\))$/i;
|
|
44013
44727
|
TmuxError = class extends Error {
|
|
44014
44728
|
constructor(message, noServer) {
|
|
@@ -44437,14 +45151,19 @@ var init_event_log = __esm({
|
|
|
44437
45151
|
function launchStatus(panes, cfg, host2) {
|
|
44438
45152
|
const live = panes.filter((p3) => !p3.exited);
|
|
44439
45153
|
const available = host2.platform !== "win32" && Number.isFinite(host2.loadAverage) && host2.loadAverage >= 0 && Number.isFinite(host2.cores) && host2.cores > 0;
|
|
45154
|
+
const liveAgentCount = live.filter((p3) => p3.launchClass ? p3.launchClass === "agent" : !!(p3.specId || p3.flow || /[\\/]\.worktrees[\\/]/.test(p3.cwd ?? "") || p3.projectId?.startsWith("telegram:") || p3.projectId?.startsWith("vps:"))).length;
|
|
45155
|
+
const memAvailable = host2.memAvailablePct !== null && Number.isFinite(host2.memAvailablePct);
|
|
44440
45156
|
return {
|
|
44441
45157
|
enabled: cfg.launchGuard.enabled,
|
|
44442
45158
|
liveCount: live.length,
|
|
44443
|
-
liveAgentCount
|
|
45159
|
+
liveAgentCount,
|
|
44444
45160
|
maxConcurrent: cfg.maxConcurrent,
|
|
44445
45161
|
loadPerCore: available ? host2.loadAverage / host2.cores : null,
|
|
44446
45162
|
maxLoadPerCore: cfg.launchGuard.maxLoadPerCore,
|
|
44447
|
-
loadStatus: host2.platform === "win32" ? "unsupported" : available ? "available" : "unavailable"
|
|
45163
|
+
loadStatus: host2.platform === "win32" ? "unsupported" : available ? "available" : "unavailable",
|
|
45164
|
+
memAvailablePct: memAvailable ? host2.memAvailablePct : null,
|
|
45165
|
+
minMemAvailablePct: cfg.launchGuard.minMemAvailablePct,
|
|
45166
|
+
memStatus: memAvailable ? "available" : "unavailable"
|
|
44448
45167
|
};
|
|
44449
45168
|
}
|
|
44450
45169
|
function createLaunchGate(deps) {
|
|
@@ -44462,11 +45181,13 @@ function createLaunchGate(deps) {
|
|
|
44462
45181
|
const live = opts.id ? panes.find((p3) => p3.id === opts.id && !p3.exited) : void 0;
|
|
44463
45182
|
if (live) return reuse(live);
|
|
44464
45183
|
if (!opts.exempt) {
|
|
44465
|
-
const status2 = launchStatus(panes, await deps.config(), deps.host());
|
|
45184
|
+
const status2 = launchStatus(panes, await deps.config(), await deps.host());
|
|
44466
45185
|
if (status2.enabled && !opts.force) {
|
|
44467
|
-
if (status2.
|
|
45186
|
+
if (status2.liveAgentCount >= status2.maxConcurrent) throw new LaunchAdmissionError("capacity", status2);
|
|
44468
45187
|
if (status2.loadPerCore !== null && status2.loadPerCore > status2.maxLoadPerCore)
|
|
44469
45188
|
throw new LaunchAdmissionError("host-load", status2);
|
|
45189
|
+
if (status2.memAvailablePct !== null && status2.memAvailablePct < status2.minMemAvailablePct)
|
|
45190
|
+
throw new LaunchAdmissionError("host-memory", status2);
|
|
44470
45191
|
}
|
|
44471
45192
|
}
|
|
44472
45193
|
return await start();
|
|
@@ -44485,7 +45206,9 @@ var init_session_admission2 = __esm({
|
|
|
44485
45206
|
constructor(kind, admission) {
|
|
44486
45207
|
const a = admission;
|
|
44487
45208
|
const load2 = a.loadPerCore === null ? `tidak tersedia (${a.loadStatus})` : a.loadPerCore.toFixed(2);
|
|
44488
|
-
|
|
45209
|
+
const mem = a.memAvailablePct === null ? `tidak tersedia (${a.memStatus})` : `${a.memAvailablePct.toFixed(0)}%`;
|
|
45210
|
+
const label2 = kind === "capacity" ? "Cap sesi penuh" : kind === "host-load" ? "Beban host melampaui ambang" : "Memori host di bawah ambang";
|
|
45211
|
+
super(`${label2}: ${a.liveAgentCount} agen, ${a.liveCount} sesi hidup / cap ${a.maxConcurrent}; load/core ${load2} / ambang ${a.maxLoadPerCore}; memori tersedia ${mem} / ambang ${a.minMemAvailablePct}%. Tunggu atau gunakan force.`);
|
|
44489
45212
|
this.kind = kind;
|
|
44490
45213
|
this.admission = admission;
|
|
44491
45214
|
void appendEvent({ kind: "launch.rejected", level: "warn", msg: this.message, data: { kind, admission } });
|
|
@@ -45279,7 +46002,34 @@ var init_config3 = __esm({
|
|
|
45279
46002
|
});
|
|
45280
46003
|
|
|
45281
46004
|
// src/services/session-launch-gate.ts
|
|
46005
|
+
import { execFile as execFile5 } from "node:child_process";
|
|
46006
|
+
import { readFile as readFile5 } from "node:fs/promises";
|
|
45282
46007
|
import { cpus, loadavg, platform } from "node:os";
|
|
46008
|
+
import { promisify as promisify4 } from "node:util";
|
|
46009
|
+
async function readMemAvailablePct() {
|
|
46010
|
+
try {
|
|
46011
|
+
if (platform() === "darwin") {
|
|
46012
|
+
const { stdout } = await execFileAsync("sysctl", ["-n", "kern.memorystatus_level"]);
|
|
46013
|
+
const pct3 = Number(stdout.trim());
|
|
46014
|
+
return Number.isFinite(pct3) ? pct3 : null;
|
|
46015
|
+
}
|
|
46016
|
+
if (platform() === "linux") {
|
|
46017
|
+
const meminfo = await readFile5("/proc/meminfo", "utf8");
|
|
46018
|
+
const total = Number(/^MemTotal:\s+(\d+)/m.exec(meminfo)?.[1]);
|
|
46019
|
+
const available = Number(/^MemAvailable:\s+(\d+)/m.exec(meminfo)?.[1]);
|
|
46020
|
+
if (!Number.isFinite(total) || total <= 0 || !Number.isFinite(available)) return null;
|
|
46021
|
+
return available / total * 100;
|
|
46022
|
+
}
|
|
46023
|
+
return null;
|
|
46024
|
+
} catch {
|
|
46025
|
+
return null;
|
|
46026
|
+
}
|
|
46027
|
+
}
|
|
46028
|
+
function cachedMemAvailablePct() {
|
|
46029
|
+
const now = Date.now();
|
|
46030
|
+
if (!memCache || now - memCache.at >= MEM_CACHE_MS) memCache = { at: now, pending: readMemAvailablePct() };
|
|
46031
|
+
return memCache.pending;
|
|
46032
|
+
}
|
|
45283
46033
|
async function createAgentSession(projectId, cwd, opts = {}) {
|
|
45284
46034
|
const id2 = opts.id ?? (opts.specId ? sessionIdForSpec(opts.specId) : void 0);
|
|
45285
46035
|
return withSessionAdmission(
|
|
@@ -45288,28 +46038,36 @@ async function createAgentSession(projectId, cwd, opts = {}) {
|
|
|
45288
46038
|
(pane) => ({ ...pane, reused: true })
|
|
45289
46039
|
);
|
|
45290
46040
|
}
|
|
45291
|
-
async function createOperatorSession(projectId, cwd, opts = {}) {
|
|
46041
|
+
async function createOperatorSession(projectId, cwd, opts = {}, admission = {}) {
|
|
45292
46042
|
return withSessionAdmission(
|
|
45293
|
-
{ id: opts.id, exempt: true },
|
|
46043
|
+
{ id: opts.id, exempt: admission.exempt ?? true, force: admission.force },
|
|
45294
46044
|
async () => createSession(projectId, cwd, opts),
|
|
45295
46045
|
(pane) => pane
|
|
45296
46046
|
);
|
|
45297
46047
|
}
|
|
45298
|
-
var readHostLoad, gate, withSessionAdmission, currentLaunchStatus;
|
|
46048
|
+
var execFileAsync, MEM_CACHE_MS, memCache, readHostLoad, gate, withSessionAdmission, currentLaunchStatus;
|
|
45299
46049
|
var init_session_launch_gate = __esm({
|
|
45300
46050
|
"src/services/session-launch-gate.ts"() {
|
|
45301
46051
|
"use strict";
|
|
45302
46052
|
init_pty();
|
|
45303
46053
|
init_config3();
|
|
45304
46054
|
init_session_admission2();
|
|
45305
|
-
|
|
46055
|
+
execFileAsync = promisify4(execFile5);
|
|
46056
|
+
MEM_CACHE_MS = 5e3;
|
|
46057
|
+
memCache = null;
|
|
46058
|
+
readHostLoad = async () => ({
|
|
46059
|
+
platform: platform(),
|
|
46060
|
+
loadAverage: loadavg()[0] ?? NaN,
|
|
46061
|
+
cores: cpus().length,
|
|
46062
|
+
memAvailablePct: await cachedMemAvailablePct()
|
|
46063
|
+
});
|
|
45306
46064
|
gate = createLaunchGate({
|
|
45307
46065
|
listPanes: () => listPanesAsync(),
|
|
45308
46066
|
config: () => getScheduler(),
|
|
45309
46067
|
host: () => readHostLoad()
|
|
45310
46068
|
});
|
|
45311
46069
|
withSessionAdmission = gate.run;
|
|
45312
|
-
currentLaunchStatus = (panes, config) => launchStatus(panes, config, readHostLoad());
|
|
46070
|
+
currentLaunchStatus = async (panes, config) => launchStatus(panes, config, await readHostLoad());
|
|
45313
46071
|
}
|
|
45314
46072
|
});
|
|
45315
46073
|
|
|
@@ -45416,7 +46174,7 @@ async function buildLocalPresence() {
|
|
|
45416
46174
|
return rows;
|
|
45417
46175
|
}
|
|
45418
46176
|
async function buildLocalCapacity() {
|
|
45419
|
-
return currentLaunchStatus(await listPanesShared(), await getScheduler());
|
|
46177
|
+
return await currentLaunchStatus(await listPanesShared(), await getScheduler());
|
|
45420
46178
|
}
|
|
45421
46179
|
var isoFromEpochSeconds, PANES_MEMO_MS, panesMemo;
|
|
45422
46180
|
var init_snapshot = __esm({
|
|
@@ -48278,8 +49036,8 @@ var init_bootstrap = __esm({
|
|
|
48278
49036
|
});
|
|
48279
49037
|
|
|
48280
49038
|
// src/services/codex-version.ts
|
|
48281
|
-
import { execFile as
|
|
48282
|
-
import { promisify as
|
|
49039
|
+
import { execFile as execFile15 } from "node:child_process";
|
|
49040
|
+
import { promisify as promisify14 } from "node:util";
|
|
48283
49041
|
async function probeCodexVersion(env = process.env, execute = runVersion) {
|
|
48284
49042
|
const probe = codexNativeVersionProbe(env, codexBin2());
|
|
48285
49043
|
try {
|
|
@@ -48316,7 +49074,7 @@ var init_codex_version = __esm({
|
|
|
48316
49074
|
init_src();
|
|
48317
49075
|
init_src2();
|
|
48318
49076
|
init_config2();
|
|
48319
|
-
run3 =
|
|
49077
|
+
run3 = promisify14(execFile15);
|
|
48320
49078
|
CODEX_MIN_CLIENT = "0.144.0";
|
|
48321
49079
|
TTL_MS4 = 5 * 6e4;
|
|
48322
49080
|
cache3 = null;
|
|
@@ -52785,7 +53543,7 @@ var init_client2 = __esm({
|
|
|
52785
53543
|
});
|
|
52786
53544
|
|
|
52787
53545
|
// src/services/logs/spool.ts
|
|
52788
|
-
import { appendFile, mkdir as mkdir8, readFile as
|
|
53546
|
+
import { appendFile, mkdir as mkdir8, readFile as readFile8, readdir as readdir9, rm as rm8, stat as stat6 } from "node:fs/promises";
|
|
52789
53547
|
import { join as join35 } from "node:path";
|
|
52790
53548
|
function spoolDir(lane) {
|
|
52791
53549
|
return join35(resolveDataDirs().home, "log-spool", lane);
|
|
@@ -52817,7 +53575,7 @@ async function readSpoolSegments(lane) {
|
|
|
52817
53575
|
const out4 = [];
|
|
52818
53576
|
for (const name2 of files) {
|
|
52819
53577
|
const file = join35(dir2, name2);
|
|
52820
|
-
const raw = await
|
|
53578
|
+
const raw = await readFile8(file, "utf8").catch(() => "");
|
|
52821
53579
|
const entries3 = raw.split("\n").filter(Boolean).map((l) => JSON.parse(l));
|
|
52822
53580
|
out4.push({ file, entries: entries3 });
|
|
52823
53581
|
}
|
|
@@ -53432,7 +54190,18 @@ async function seedBuiltinAgents() {
|
|
|
53432
54190
|
const stamps = { ...setting.builtinAgents };
|
|
53433
54191
|
const policies = { ...setting.builtinAgentPolicies };
|
|
53434
54192
|
let changed = false;
|
|
54193
|
+
const flush = async () => {
|
|
54194
|
+
if (!changed) return;
|
|
54195
|
+
const data = { ...setting, builtinAgents: stamps, builtinAgentPolicies: policies };
|
|
54196
|
+
await prisma.setting.upsert({
|
|
54197
|
+
where: { id: 1 },
|
|
54198
|
+
update: { data },
|
|
54199
|
+
create: { id: 1, data }
|
|
54200
|
+
});
|
|
54201
|
+
changed = false;
|
|
54202
|
+
};
|
|
53435
54203
|
for (const a of BUILTIN_AGENTS) {
|
|
54204
|
+
await flush();
|
|
53436
54205
|
const id2 = customAgentId(null, a.name);
|
|
53437
54206
|
const fp = builtinFingerprint(a);
|
|
53438
54207
|
const row = await prisma.customAgent.findUnique({ where: { id: id2 } });
|
|
@@ -53453,7 +54222,7 @@ async function seedBuiltinAgents() {
|
|
|
53453
54222
|
instructions: a.instructions,
|
|
53454
54223
|
tools: [...a.tools],
|
|
53455
54224
|
model: null,
|
|
53456
|
-
mentions: [],
|
|
54225
|
+
mentions: [...a.mentions ?? []],
|
|
53457
54226
|
runtime: null,
|
|
53458
54227
|
activation: a.activation,
|
|
53459
54228
|
effort: a.effort,
|
|
@@ -53468,6 +54237,13 @@ async function seedBuiltinAgents() {
|
|
|
53468
54237
|
if (qaPolicyPending) policies[a.name] = QA_SAFETY_POLICY;
|
|
53469
54238
|
continue;
|
|
53470
54239
|
}
|
|
54240
|
+
if (!stamps[a.name]) {
|
|
54241
|
+
const current = rowFingerprint(row);
|
|
54242
|
+
if (current === fp || (BUILTIN_FINGERPRINT_HISTORY[a.name] ?? []).includes(current)) {
|
|
54243
|
+
stamps[a.name] = current;
|
|
54244
|
+
changed = true;
|
|
54245
|
+
}
|
|
54246
|
+
}
|
|
53471
54247
|
const stamped = stamps[a.name];
|
|
53472
54248
|
const unedited = Boolean(stamped) && (stamped === rowFingerprint(row) || stamped === legacyRowFingerprint(row));
|
|
53473
54249
|
const data = {};
|
|
@@ -53480,7 +54256,8 @@ async function seedBuiltinAgents() {
|
|
|
53480
54256
|
effort: a.effort,
|
|
53481
54257
|
workspacePolicy: a.workspacePolicy,
|
|
53482
54258
|
maxTurns: a.maxTurns,
|
|
53483
|
-
timeoutSeconds: a.timeoutSeconds
|
|
54259
|
+
timeoutSeconds: a.timeoutSeconds,
|
|
54260
|
+
mentions: [...a.mentions ?? []]
|
|
53484
54261
|
});
|
|
53485
54262
|
stamps[a.name] = fp;
|
|
53486
54263
|
changed = true;
|
|
@@ -53495,18 +54272,11 @@ async function seedBuiltinAgents() {
|
|
|
53495
54272
|
changed = true;
|
|
53496
54273
|
}
|
|
53497
54274
|
}
|
|
53498
|
-
|
|
53499
|
-
const data = { ...setting, builtinAgents: stamps, builtinAgentPolicies: policies };
|
|
53500
|
-
await prisma.setting.upsert({
|
|
53501
|
-
where: { id: 1 },
|
|
53502
|
-
update: { data },
|
|
53503
|
-
create: { id: 1, data }
|
|
53504
|
-
});
|
|
53505
|
-
}
|
|
54275
|
+
await flush();
|
|
53506
54276
|
} catch {
|
|
53507
54277
|
}
|
|
53508
54278
|
}
|
|
53509
|
-
var digest, legacyFingerprint, fingerprint, builtinFingerprint, rowFingerprint, legacyRowFingerprint, QA_SAFETY_POLICY;
|
|
54279
|
+
var digest, legacyFingerprint, mentionPart, fingerprint, builtinFingerprint, rowFingerprint, legacyRowFingerprint, QA_SAFETY_POLICY, BUILTIN_FINGERPRINT_HISTORY;
|
|
53510
54280
|
var init_builtin_agents2 = __esm({
|
|
53511
54281
|
"src/services/builtin-agents.ts"() {
|
|
53512
54282
|
"use strict";
|
|
@@ -53517,6 +54287,10 @@ var init_builtin_agents2 = __esm({
|
|
|
53517
54287
|
init_sync_notify();
|
|
53518
54288
|
digest = (parts) => createHash13("sha256").update(parts.join(" ")).digest("hex").slice(0, 16);
|
|
53519
54289
|
legacyFingerprint = (name2, description, instructions, tools) => digest([name2, description, instructions, [...tools].join(",")]);
|
|
54290
|
+
mentionPart = (v) => {
|
|
54291
|
+
const m = mentionsOf(v);
|
|
54292
|
+
return m.length === 0 ? [] : [`mentions:${m.join(",")}`];
|
|
54293
|
+
};
|
|
53520
54294
|
fingerprint = (a) => digest([
|
|
53521
54295
|
a.name,
|
|
53522
54296
|
a.description,
|
|
@@ -53526,12 +54300,24 @@ var init_builtin_agents2 = __esm({
|
|
|
53526
54300
|
effortOf(a.effort) ?? "",
|
|
53527
54301
|
workspacePolicyOf(a.workspacePolicy),
|
|
53528
54302
|
String(maxTurnsOf(a.maxTurns) ?? ""),
|
|
53529
|
-
String(timeoutSecondsOf(a.timeoutSeconds) ?? "")
|
|
54303
|
+
String(timeoutSecondsOf(a.timeoutSeconds) ?? ""),
|
|
54304
|
+
...mentionPart(a.mentions)
|
|
53530
54305
|
]);
|
|
53531
54306
|
builtinFingerprint = (a) => fingerprint(a);
|
|
53532
54307
|
rowFingerprint = (r) => fingerprint(r);
|
|
53533
54308
|
legacyRowFingerprint = (r) => legacyFingerprint(r.name, r.description, r.instructions, toolsOf(r.tools) ?? []);
|
|
53534
54309
|
QA_SAFETY_POLICY = "disable-unedited-v1";
|
|
54310
|
+
BUILTIN_FINGERPRINT_HISTORY = {
|
|
54311
|
+
// 0b90ab3c (app roles) ff98a8f6 (ADR-0167, s.d. 8cea296c)
|
|
54312
|
+
"product-designer": ["6cc1027cf73ce6fd", "dfd313ae2b402734"],
|
|
54313
|
+
"feature-builder": ["8a28ec2d98cb7055", "6d9c335a4577e695"],
|
|
54314
|
+
"performance-engineer": ["82e1f7418f7c8769", "005ed47095939e66"],
|
|
54315
|
+
"product-analyst": ["96a3b765139f5d74", "83324b9f112bd340"],
|
|
54316
|
+
"solution-architect": ["184a27b8830309e8", "4871cbe89c9da02f"],
|
|
54317
|
+
"operations-engineer": ["c21bfe010211d33a", "d47396055a549866"],
|
|
54318
|
+
"support-triager": ["6fab97e398ed88ad", "42a08ae21d354f06"],
|
|
54319
|
+
"knowledge-maintainer": ["f6bf5100faa2028e", "e1c696442299cd53"]
|
|
54320
|
+
};
|
|
53535
54321
|
}
|
|
53536
54322
|
});
|
|
53537
54323
|
|
|
@@ -53587,7 +54373,11 @@ function recommendedModel(row, runtime) {
|
|
|
53587
54373
|
return modelKnownForRuntime(runtime, model) ? model : null;
|
|
53588
54374
|
}
|
|
53589
54375
|
function toRuntimeDef(row, runtime) {
|
|
53590
|
-
|
|
54376
|
+
const def2 = { ...toDef(row), model: recommendedModel(row, runtime) };
|
|
54377
|
+
if (runtime === "claude" && def2.effort && def2.model && claudeSubagentEfforts(def2.model).length === 0) {
|
|
54378
|
+
return { ...def2, effort: null };
|
|
54379
|
+
}
|
|
54380
|
+
return def2;
|
|
53591
54381
|
}
|
|
53592
54382
|
async function loadCustomAgents() {
|
|
53593
54383
|
try {
|
|
@@ -57854,12 +58644,12 @@ init_codex_trust();
|
|
|
57854
58644
|
init_db();
|
|
57855
58645
|
|
|
57856
58646
|
// src/services/spec-review.ts
|
|
57857
|
-
import { execFile as
|
|
57858
|
-
import { promisify as
|
|
58647
|
+
import { execFile as execFile6 } from "node:child_process";
|
|
58648
|
+
import { promisify as promisify5 } from "node:util";
|
|
57859
58649
|
import { mkdtemp, copyFile, rm as rm2 } from "node:fs/promises";
|
|
57860
58650
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
57861
58651
|
import { join as join16, resolve as resolve14 } from "node:path";
|
|
57862
|
-
var exec4 =
|
|
58652
|
+
var exec4 = promisify5(execFile6);
|
|
57863
58653
|
var GIT3 = { maxBuffer: 1 << 24 };
|
|
57864
58654
|
var MAX = 256 * 1024;
|
|
57865
58655
|
var worktreeDir = (repoDir, specId) => join16(repoDir, ".worktrees", specId.toLowerCase().replace(/[^a-z0-9_-]/g, "_"));
|
|
@@ -58063,10 +58853,10 @@ function appendSourceHistory(current, entry) {
|
|
|
58063
58853
|
}
|
|
58064
58854
|
|
|
58065
58855
|
// src/services/spec-reset.ts
|
|
58066
|
-
import { execFile as
|
|
58856
|
+
import { execFile as execFile9 } from "node:child_process";
|
|
58067
58857
|
import { existsSync as existsSync9 } from "node:fs";
|
|
58068
58858
|
import { join as join18 } from "node:path";
|
|
58069
|
-
import { promisify as
|
|
58859
|
+
import { promisify as promisify8 } from "node:util";
|
|
58070
58860
|
|
|
58071
58861
|
// src/services/stage-artifacts.ts
|
|
58072
58862
|
init_src();
|
|
@@ -58111,14 +58901,14 @@ async function deleteDoc(projectId, path) {
|
|
|
58111
58901
|
// src/services/worktree-reaper.ts
|
|
58112
58902
|
init_src2();
|
|
58113
58903
|
init_db();
|
|
58114
|
-
import { execFile as
|
|
58904
|
+
import { execFile as execFile7 } from "node:child_process";
|
|
58115
58905
|
import { readdir as readdir3, rm as rm3 } from "node:fs/promises";
|
|
58116
|
-
import { promisify as
|
|
58906
|
+
import { promisify as promisify6 } from "node:util";
|
|
58117
58907
|
import { join as join17, resolve as resolve15 } from "node:path";
|
|
58118
58908
|
init_notifications2();
|
|
58119
58909
|
var TICK_MS = 6e4;
|
|
58120
58910
|
var trashDirOf = (repoDir) => resolve15(repoDir, ".worktrees", ".trash");
|
|
58121
|
-
var exec5 =
|
|
58911
|
+
var exec5 = promisify6(execFile7);
|
|
58122
58912
|
var prodReaperDeps = {
|
|
58123
58913
|
rm: (path) => rm3(path, { recursive: true, force: true }),
|
|
58124
58914
|
prune: async (repoDir) => {
|
|
@@ -58226,12 +59016,12 @@ function ownsWorktree(repoDir, cwd) {
|
|
|
58226
59016
|
}
|
|
58227
59017
|
|
|
58228
59018
|
// src/services/git-ide.ts
|
|
58229
|
-
import { execFile as
|
|
58230
|
-
import { promisify as
|
|
59019
|
+
import { execFile as execFile8 } from "node:child_process";
|
|
59020
|
+
import { promisify as promisify7 } from "node:util";
|
|
58231
59021
|
import { existsSync as existsSync8 } from "node:fs";
|
|
58232
59022
|
import { readdir as readdir4 } from "node:fs/promises";
|
|
58233
59023
|
import { resolve as resolve17 } from "node:path";
|
|
58234
|
-
var exec6 =
|
|
59024
|
+
var exec6 = promisify7(execFile8);
|
|
58235
59025
|
var GIT4 = { maxBuffer: 1 << 24 };
|
|
58236
59026
|
var MAX2 = 256 * 1024;
|
|
58237
59027
|
var splitZ2 = (s2) => s2.split("\0").filter(Boolean);
|
|
@@ -58726,7 +59516,7 @@ async function buildGitLive(repoDir, p3) {
|
|
|
58726
59516
|
|
|
58727
59517
|
// src/services/spec-reset.ts
|
|
58728
59518
|
init_session_id();
|
|
58729
|
-
var exec7 =
|
|
59519
|
+
var exec7 = promisify8(execFile9);
|
|
58730
59520
|
var EMPTY = { wouldDelete: [], worktree: null, branch: null };
|
|
58731
59521
|
async function planSpecReset(spec) {
|
|
58732
59522
|
const repoDir = await resolveRepoDir(spec.projectId);
|
|
@@ -63135,7 +63925,7 @@ async function dropSpecAttachments(specId) {
|
|
|
63135
63925
|
|
|
63136
63926
|
// src/services/spec-attachment-dir.ts
|
|
63137
63927
|
init_db();
|
|
63138
|
-
import { mkdir as mkdir5, readdir as readdir5, readFile as
|
|
63928
|
+
import { mkdir as mkdir5, readdir as readdir5, readFile as readFile6, rm as rm4, writeFile as writeFile4 } from "node:fs/promises";
|
|
63139
63929
|
import { join as join20 } from "node:path";
|
|
63140
63930
|
init_session_id();
|
|
63141
63931
|
init_uploads();
|
|
@@ -63189,7 +63979,7 @@ async function syncSpecAttachmentsDir(specId, projectId) {
|
|
|
63189
63979
|
for (const a of rows) {
|
|
63190
63980
|
let bytes;
|
|
63191
63981
|
try {
|
|
63192
|
-
bytes = await
|
|
63982
|
+
bytes = await readFile6(join20(uploadDir(), a.storageKey));
|
|
63193
63983
|
} catch {
|
|
63194
63984
|
continue;
|
|
63195
63985
|
}
|
|
@@ -66613,14 +67403,14 @@ async function docs_default(app3) {
|
|
|
66613
67403
|
// src/routes/ide.ts
|
|
66614
67404
|
init_src2();
|
|
66615
67405
|
import { basename as basename7 } from "node:path";
|
|
66616
|
-
import { execFile as
|
|
66617
|
-
import { promisify as
|
|
67406
|
+
import { execFile as execFile13, spawn as spawn3 } from "node:child_process";
|
|
67407
|
+
import { promisify as promisify12 } from "node:util";
|
|
66618
67408
|
|
|
66619
67409
|
// src/services/git-remotes.ts
|
|
66620
|
-
import { execFile as
|
|
66621
|
-
import { promisify as
|
|
67410
|
+
import { execFile as execFile10 } from "node:child_process";
|
|
67411
|
+
import { promisify as promisify9 } from "node:util";
|
|
66622
67412
|
import { existsSync as existsSync11 } from "node:fs";
|
|
66623
|
-
var exec8 =
|
|
67413
|
+
var exec8 = promisify9(execFile10);
|
|
66624
67414
|
var GIT5 = { maxBuffer: 1 << 24, encoding: "utf8" };
|
|
66625
67415
|
function parseRemote(url2) {
|
|
66626
67416
|
let m = /^git@([^:]+):(.+?)(?:\.git)?$/.exec(url2);
|
|
@@ -66684,9 +67474,9 @@ init_settings3();
|
|
|
66684
67474
|
init_codex_trust();
|
|
66685
67475
|
|
|
66686
67476
|
// src/services/branch-cleanup.ts
|
|
66687
|
-
import { execFile as
|
|
66688
|
-
import { promisify as
|
|
66689
|
-
var exec9 =
|
|
67477
|
+
import { execFile as execFile11 } from "node:child_process";
|
|
67478
|
+
import { promisify as promisify10 } from "node:util";
|
|
67479
|
+
var exec9 = promisify10(execFile11);
|
|
66690
67480
|
var GIT6 = { timeout: 6e4, maxBuffer: 1 << 24, encoding: "utf8" };
|
|
66691
67481
|
var LOCK_REASON = {
|
|
66692
67482
|
current: "branch aktif (HEAD)",
|
|
@@ -66854,12 +67644,12 @@ async function deleteBranches(repoDir, names, opts) {
|
|
|
66854
67644
|
}
|
|
66855
67645
|
|
|
66856
67646
|
// src/services/worktree-list.ts
|
|
66857
|
-
import { execFile as
|
|
67647
|
+
import { execFile as execFile12 } from "node:child_process";
|
|
66858
67648
|
import { realpathSync as realpathSync6 } from "node:fs";
|
|
66859
67649
|
import { stat as stat4 } from "node:fs/promises";
|
|
66860
67650
|
import { basename as basename6, dirname as dirname11, join as join21, resolve as resolve19, sep as sep6 } from "node:path";
|
|
66861
|
-
import { promisify as
|
|
66862
|
-
var exec10 =
|
|
67651
|
+
import { promisify as promisify11 } from "node:util";
|
|
67652
|
+
var exec10 = promisify11(execFile12);
|
|
66863
67653
|
var GIT7 = { timeout: 6e4, maxBuffer: 1 << 24, encoding: "utf8" };
|
|
66864
67654
|
async function out3(cwd, args) {
|
|
66865
67655
|
try {
|
|
@@ -67473,7 +68263,7 @@ async function lockInputs(id2) {
|
|
|
67473
68263
|
sessionBranches: new Set(sessions)
|
|
67474
68264
|
};
|
|
67475
68265
|
}
|
|
67476
|
-
var execAsync =
|
|
68266
|
+
var execAsync = promisify12(execFile13);
|
|
67477
68267
|
function entryError(reply, e) {
|
|
67478
68268
|
if (e instanceof EntryMissingError) return reply.code(404).send({ error: "not found" });
|
|
67479
68269
|
if (e instanceof EntryExistsError) return reply.code(409).send({ error: "sudah ada" });
|
|
@@ -68049,7 +68839,7 @@ function buildResumeCtx(repoDir, id2, flow2, worktreeKept) {
|
|
|
68049
68839
|
}
|
|
68050
68840
|
async function startSpecSession(spec, opts) {
|
|
68051
68841
|
const id2 = sessionIdForSpec(spec.id);
|
|
68052
|
-
return withSessionAdmission({ id: id2, force: opts.force
|
|
68842
|
+
return withSessionAdmission({ id: id2, force: opts.force }, async () => {
|
|
68053
68843
|
const pane = await getSessionAsync(id2);
|
|
68054
68844
|
if (!pane) {
|
|
68055
68845
|
const view13 = await presenceView();
|
|
@@ -68247,8 +69037,8 @@ function setBounded(map, key, value, maxKeys) {
|
|
|
68247
69037
|
init_src2();
|
|
68248
69038
|
init_db();
|
|
68249
69039
|
import { randomBytes as randomBytes3, scrypt as scryptCb, timingSafeEqual as timingSafeEqual3, createHash as createHash9 } from "node:crypto";
|
|
68250
|
-
import { promisify as
|
|
68251
|
-
var scrypt =
|
|
69040
|
+
import { promisify as promisify13 } from "node:util";
|
|
69041
|
+
var scrypt = promisify13(scryptCb);
|
|
68252
69042
|
var COOKIE_NAME = "hn_session";
|
|
68253
69043
|
var SESSION_TTL_MS = 7 * 24 * 3600 * 1e3;
|
|
68254
69044
|
async function hashPassword(pw) {
|
|
@@ -68835,18 +69625,46 @@ var inside = (path, root) => {
|
|
|
68835
69625
|
};
|
|
68836
69626
|
var EMPTY_USAGE = { inputTokens: null, outputTokens: null, cachedTokens: null };
|
|
68837
69627
|
var nonnegativeInt = (value) => Number.isSafeInteger(value) && Number(value) >= 0 ? Number(value) : null;
|
|
69628
|
+
var asRecord = (value) => value && typeof value === "object" ? value : null;
|
|
68838
69629
|
function usageFromText(text) {
|
|
68839
|
-
const
|
|
69630
|
+
const cumulative = [];
|
|
69631
|
+
const perMessage = /* @__PURE__ */ new Map();
|
|
69632
|
+
let anonymous = 0;
|
|
69633
|
+
const known2 = (...values) => {
|
|
69634
|
+
const ints = values.map(nonnegativeInt);
|
|
69635
|
+
return ints.some((n2) => n2 !== null) ? ints.reduce((sum, n2) => sum + (n2 ?? 0), 0) : -1;
|
|
69636
|
+
};
|
|
68840
69637
|
const inspect = (value) => {
|
|
68841
|
-
|
|
68842
|
-
|
|
68843
|
-
const
|
|
68844
|
-
|
|
68845
|
-
|
|
68846
|
-
|
|
68847
|
-
|
|
68848
|
-
|
|
68849
|
-
|
|
69638
|
+
const record2 = asRecord(value);
|
|
69639
|
+
if (!record2) return;
|
|
69640
|
+
const message = asRecord(record2.message);
|
|
69641
|
+
const claude = asRecord(message?.usage);
|
|
69642
|
+
if (claude) {
|
|
69643
|
+
const entry = [
|
|
69644
|
+
known2(claude.input_tokens, claude.cache_creation_input_tokens, claude.cache_read_input_tokens),
|
|
69645
|
+
known2(claude.output_tokens),
|
|
69646
|
+
known2(claude.cache_read_input_tokens)
|
|
69647
|
+
];
|
|
69648
|
+
const id2 = typeof message.id === "string" && message.id ? message.id : `\0${anonymous++}`;
|
|
69649
|
+
const prev = perMessage.get(id2);
|
|
69650
|
+
perMessage.set(id2, prev ? prev.map((n2, i) => Math.max(n2, entry[i])) : entry);
|
|
69651
|
+
return;
|
|
69652
|
+
}
|
|
69653
|
+
const codex2 = asRecord(asRecord(asRecord(record2.payload)?.info)?.total_token_usage);
|
|
69654
|
+
if (codex2) {
|
|
69655
|
+
cumulative.push([
|
|
69656
|
+
known2(codex2.input_tokens),
|
|
69657
|
+
known2(codex2.output_tokens),
|
|
69658
|
+
known2(codex2.cached_input_tokens)
|
|
69659
|
+
]);
|
|
69660
|
+
return;
|
|
69661
|
+
}
|
|
69662
|
+
const u = asRecord(record2.usage);
|
|
69663
|
+
if (u) {
|
|
69664
|
+
cumulative.push([
|
|
69665
|
+
known2(u.input_tokens ?? u.inputTokens),
|
|
69666
|
+
known2(u.output_tokens ?? u.outputTokens),
|
|
69667
|
+
known2(u.cached_tokens ?? u.cachedTokens ?? u.cached_input_tokens ?? u.cache_read_input_tokens ?? u.cacheReadInputTokens)
|
|
68850
69668
|
]);
|
|
68851
69669
|
}
|
|
68852
69670
|
};
|
|
@@ -68857,9 +69675,17 @@ function usageFromText(text) {
|
|
|
68857
69675
|
} catch {
|
|
68858
69676
|
}
|
|
68859
69677
|
}
|
|
68860
|
-
if (
|
|
69678
|
+
if (perMessage.size > 0) {
|
|
69679
|
+
const entries3 = [...perMessage.values()];
|
|
69680
|
+
const sum = (index) => {
|
|
69681
|
+
const values = entries3.map((entry) => entry[index]).filter((n2) => n2 >= 0);
|
|
69682
|
+
return values.length ? values.reduce((a, b) => a + b, 0) : null;
|
|
69683
|
+
};
|
|
69684
|
+
return { inputTokens: sum(0), outputTokens: sum(1), cachedTokens: sum(2) };
|
|
69685
|
+
}
|
|
69686
|
+
if (cumulative.length === 0) return EMPTY_USAGE;
|
|
68861
69687
|
const max = (index) => {
|
|
68862
|
-
const values =
|
|
69688
|
+
const values = cumulative.map((entry) => entry[index]).filter((n2) => n2 >= 0);
|
|
68863
69689
|
return values.length ? Math.max(...values) : null;
|
|
68864
69690
|
};
|
|
68865
69691
|
return { inputTokens: max(0), outputTokens: max(1), cachedTokens: max(2) };
|
|
@@ -69135,7 +69961,8 @@ async function listPhaseInvocations(sessionId2) {
|
|
|
69135
69961
|
inputTokens: row.inputTokens,
|
|
69136
69962
|
outputTokens: row.outputTokens,
|
|
69137
69963
|
cachedTokens: row.cachedTokens,
|
|
69138
|
-
resultExcerpt: row.resultExcerpt
|
|
69964
|
+
resultExcerpt: row.resultExcerpt,
|
|
69965
|
+
agentName: row.agentName
|
|
69139
69966
|
}));
|
|
69140
69967
|
}
|
|
69141
69968
|
|
|
@@ -69516,7 +70343,7 @@ function leadPrompt(q, c) {
|
|
|
69516
70343
|
// src/services/lead/verdict.ts
|
|
69517
70344
|
init_src();
|
|
69518
70345
|
import { existsSync as existsSync12 } from "node:fs";
|
|
69519
|
-
import { isAbsolute as isAbsolute9, resolve as
|
|
70346
|
+
import { isAbsolute as isAbsolute9, resolve as resolvePath2, relative as relative5 } from "node:path";
|
|
69520
70347
|
function extractJsonBlock(raw) {
|
|
69521
70348
|
const re = /```json\s*\n([\s\S]*?)```/g;
|
|
69522
70349
|
let last2 = null;
|
|
@@ -69550,7 +70377,7 @@ function keepExistingRefs(refs2, repoDir) {
|
|
|
69550
70377
|
}
|
|
69551
70378
|
if (!repoDir) continue;
|
|
69552
70379
|
if (isAbsolute9(ref)) continue;
|
|
69553
|
-
const full =
|
|
70380
|
+
const full = resolvePath2(repoDir, ref);
|
|
69554
70381
|
const rel = relative5(repoDir, full);
|
|
69555
70382
|
if (rel.startsWith("..")) continue;
|
|
69556
70383
|
if (existsSync12(full)) out4.push(ref);
|
|
@@ -69689,7 +70516,7 @@ init_src2();
|
|
|
69689
70516
|
init_config2();
|
|
69690
70517
|
init_pty();
|
|
69691
70518
|
init_session_sandbox();
|
|
69692
|
-
import { execFile as
|
|
70519
|
+
import { execFile as execFile14 } from "node:child_process";
|
|
69693
70520
|
import { randomUUID as randomUUID10 } from "node:crypto";
|
|
69694
70521
|
import { mkdirSync as mkdirSync12, rmSync as rmSync8, writeFileSync as writeFileSync10 } from "node:fs";
|
|
69695
70522
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
@@ -69768,7 +70595,7 @@ function leadFailureReason(agent, timeoutMs, err, stdout, stderr) {
|
|
|
69768
70595
|
function think(prompt, o) {
|
|
69769
70596
|
const process2 = leadProcess(prompt, o);
|
|
69770
70597
|
return new Promise((resolve24, reject2) => {
|
|
69771
|
-
const child =
|
|
70598
|
+
const child = execFile14(process2.file, process2.args, {
|
|
69772
70599
|
cwd: process2.cwd,
|
|
69773
70600
|
timeout: o.timeoutMs,
|
|
69774
70601
|
maxBuffer: 16 * 1024 * 1024,
|
|
@@ -70770,7 +71597,12 @@ async function terminal_default(app3, opts) {
|
|
|
70770
71597
|
}
|
|
70771
71598
|
const { agent, model, effort: effort2 } = await terminalAgentDefaults(parsed.data);
|
|
70772
71599
|
if (agent === "codex") ensureCodexTrust(repoDir);
|
|
70773
|
-
const s2 = await createOperatorSession(
|
|
71600
|
+
const s2 = await createOperatorSession(
|
|
71601
|
+
project.id,
|
|
71602
|
+
repoDir,
|
|
71603
|
+
{ agent, model, effort: effort2 },
|
|
71604
|
+
{ exempt: false, force: parsed.data.force }
|
|
71605
|
+
);
|
|
70774
71606
|
return reply.code(201).send({ id: s2.id });
|
|
70775
71607
|
});
|
|
70776
71608
|
app3.get("/terminal/sessions/:id/phases", async (req, reply) => {
|
|
@@ -72262,7 +73094,7 @@ async function codex(app3) {
|
|
|
72262
73094
|
// src/services/model-catalog.ts
|
|
72263
73095
|
init_src2();
|
|
72264
73096
|
init_src();
|
|
72265
|
-
import { readFile as
|
|
73097
|
+
import { readFile as readFile7, writeFile as writeFile5, rename as rename4, mkdir as mkdir7 } from "node:fs/promises";
|
|
72266
73098
|
import { dirname as dirname15, join as join34 } from "node:path";
|
|
72267
73099
|
|
|
72268
73100
|
// src/services/model-catalog-parser.ts
|
|
@@ -72523,7 +73355,7 @@ var cacheFile = () => join34(resolveHome(), "model-catalog.json");
|
|
|
72523
73355
|
var modelCatalogService = createModelCatalogService({
|
|
72524
73356
|
probe: probeModelCatalog,
|
|
72525
73357
|
read: async () => {
|
|
72526
|
-
const content = await
|
|
73358
|
+
const content = await readFile7(cacheFile(), "utf8");
|
|
72527
73359
|
if (Buffer.byteLength(content) > 4 * 1024 * 1024) throw new Error("oversized cache");
|
|
72528
73360
|
return JSON.parse(content);
|
|
72529
73361
|
},
|
|
@@ -72796,7 +73628,7 @@ async function buildSchedulerState() {
|
|
|
72796
73628
|
sources: sources2,
|
|
72797
73629
|
queueCounts: counts,
|
|
72798
73630
|
sessions,
|
|
72799
|
-
admission: currentLaunchStatus(live, cfg)
|
|
73631
|
+
admission: await currentLaunchStatus(live, cfg)
|
|
72800
73632
|
};
|
|
72801
73633
|
}
|
|
72802
73634
|
|
|
@@ -75308,8 +76140,8 @@ init_db();
|
|
|
75308
76140
|
|
|
75309
76141
|
// src/services/changelog/collect.ts
|
|
75310
76142
|
init_db();
|
|
75311
|
-
import { execFile as
|
|
75312
|
-
import { promisify as
|
|
76143
|
+
import { execFile as execFile16 } from "node:child_process";
|
|
76144
|
+
import { promisify as promisify15 } from "node:util";
|
|
75313
76145
|
import { existsSync as existsSync18 } from "node:fs";
|
|
75314
76146
|
|
|
75315
76147
|
// src/services/changelog/scrub.ts
|
|
@@ -75371,7 +76203,7 @@ async function collectBacklog(projectId, from, to) {
|
|
|
75371
76203
|
notes.push(`${stampless} item selesai tanpa stempel waktu (selesai sebelum stempel ini ada) dan tak ikut dihitung.`);
|
|
75372
76204
|
return { ok: true, input: { mode: "backlog", title: `${from} \u2013 ${to}`, items, notes } };
|
|
75373
76205
|
}
|
|
75374
|
-
var exec11 =
|
|
76206
|
+
var exec11 = promisify15(execFile16);
|
|
75375
76207
|
var GIT8 = { maxBuffer: 1 << 24, encoding: "utf8" };
|
|
75376
76208
|
var US2 = "";
|
|
75377
76209
|
var RS = "";
|
|
@@ -76239,7 +77071,7 @@ async function resolveGithubRepo(projectId) {
|
|
|
76239
77071
|
|
|
76240
77072
|
// src/services/github-fetch.ts
|
|
76241
77073
|
init_config2();
|
|
76242
|
-
import { execFile as
|
|
77074
|
+
import { execFile as execFile17 } from "node:child_process";
|
|
76243
77075
|
var labelNames = (l) => (l ?? []).map((x) => typeof x === "string" ? x : x.name ?? "").filter(Boolean);
|
|
76244
77076
|
var norm2 = (s2) => s2.toLowerCase() === "closed" ? "closed" : "open";
|
|
76245
77077
|
function issueFromGh(raw) {
|
|
@@ -76280,7 +77112,7 @@ function issuesFromRest(raw) {
|
|
|
76280
77112
|
var GH_FIELDS = "number,title,body,author,labels,url,state,createdAt,updatedAt";
|
|
76281
77113
|
var API2 = "https://api.github.com";
|
|
76282
77114
|
var defaultRunGh = (args, env) => new Promise((resolve24, reject2) => {
|
|
76283
|
-
|
|
77115
|
+
execFile17(
|
|
76284
77116
|
args[0],
|
|
76285
77117
|
args.slice(1),
|
|
76286
77118
|
{ env, maxBuffer: 1 << 26, encoding: "utf8", timeout: 6e4 },
|
|
@@ -77315,7 +78147,7 @@ init_settings3();
|
|
|
77315
78147
|
// src/services/portal-chat/turn.ts
|
|
77316
78148
|
init_src();
|
|
77317
78149
|
init_db();
|
|
77318
|
-
import { execFile as
|
|
78150
|
+
import { execFile as execFile18 } from "node:child_process";
|
|
77319
78151
|
|
|
77320
78152
|
// src/services/portal-chat/guard-input.ts
|
|
77321
78153
|
import { randomBytes as randomBytes10 } from "node:crypto";
|
|
@@ -77602,7 +78434,7 @@ function chatFailureReason(err, stdout, stderr, timeoutMs) {
|
|
|
77602
78434
|
}
|
|
77603
78435
|
function runProcess(p3, timeoutMs) {
|
|
77604
78436
|
return new Promise((resolve24, reject2) => {
|
|
77605
|
-
const child =
|
|
78437
|
+
const child = execFile18(p3.file, p3.args, {
|
|
77606
78438
|
cwd: p3.cwd,
|
|
77607
78439
|
timeout: timeoutMs,
|
|
77608
78440
|
maxBuffer: 16 * 1024 * 1024,
|
|
@@ -78031,7 +78863,7 @@ init_db();
|
|
|
78031
78863
|
|
|
78032
78864
|
// src/services/bootstrap.ts
|
|
78033
78865
|
import { createHash as createHash15, randomBytes as randomBytes11, timingSafeEqual as timingSafeEqual5 } from "node:crypto";
|
|
78034
|
-
import { chmod, lstat as lstat3, mkdir as mkdir10, readFile as
|
|
78866
|
+
import { chmod, lstat as lstat3, mkdir as mkdir10, readFile as readFile9, unlink as unlink5, writeFile as writeFile7 } from "node:fs/promises";
|
|
78035
78867
|
import { join as join39 } from "node:path";
|
|
78036
78868
|
var TTL_MS5 = 15 * 6e4;
|
|
78037
78869
|
var SETUP_TOKEN_FILE = "setup.token";
|
|
@@ -78043,7 +78875,7 @@ async function readStored(home3) {
|
|
|
78043
78875
|
try {
|
|
78044
78876
|
const info = await lstat3(tokenPath(home3));
|
|
78045
78877
|
if (info.isSymbolicLink() || !info.isFile()) throw new BootstrapError("invalid setup proof");
|
|
78046
|
-
const [token, expires] = (await
|
|
78878
|
+
const [token, expires] = (await readFile9(tokenPath(home3), "utf8")).trim().split("\n");
|
|
78047
78879
|
const expiresAt = Date.parse(expires ?? "");
|
|
78048
78880
|
if (!token || !Number.isFinite(expiresAt)) throw new BootstrapError("invalid setup proof");
|
|
78049
78881
|
return { token, expiresAt };
|
|
@@ -78573,7 +79405,7 @@ init_db();
|
|
|
78573
79405
|
init_event_log();
|
|
78574
79406
|
init_transcript_store();
|
|
78575
79407
|
init_src2();
|
|
78576
|
-
import { readFile as
|
|
79408
|
+
import { readFile as readFile10 } from "node:fs/promises";
|
|
78577
79409
|
import { join as join40 } from "node:path";
|
|
78578
79410
|
function decodeCursor(c) {
|
|
78579
79411
|
if (!c) return null;
|
|
@@ -78614,7 +79446,7 @@ async function readRemoteTranscript(id2) {
|
|
|
78614
79446
|
if (row.deviceId === "local") return readTranscript(row.transcriptKey);
|
|
78615
79447
|
const path = join40(resolveDataDirs().home, "remote-transcripts", row.transcriptKey);
|
|
78616
79448
|
try {
|
|
78617
|
-
return await
|
|
79449
|
+
return await readFile10(path, "utf8");
|
|
78618
79450
|
} catch {
|
|
78619
79451
|
return null;
|
|
78620
79452
|
}
|
|
@@ -79329,21 +80161,19 @@ function registerTriaseSource() {
|
|
|
79329
80161
|
// src/services/session-boot-resume.ts
|
|
79330
80162
|
init_src();
|
|
79331
80163
|
init_db();
|
|
80164
|
+
init_session_admission2();
|
|
79332
80165
|
init_notifications2();
|
|
79333
80166
|
var prodDeps2 = {
|
|
79334
|
-
// ADR-
|
|
79335
|
-
//
|
|
79336
|
-
//
|
|
79337
|
-
//
|
|
79338
|
-
startSpec: (spec, runtime) => startSpecSession(spec, {
|
|
79339
|
-
|
|
79340
|
-
|
|
79341
|
-
...runtime ?? {}
|
|
79342
|
-
}),
|
|
79343
|
-
recordFail: recordFailure
|
|
80167
|
+
// ADR-0170 · mengamandemen ADR-0169 keputusan #4: `bypassCapacity` DICABUT. Auto-resume kini
|
|
80168
|
+
// tunduk cap/beban host seperti "Lanjutkan" manual — mesin 8 GB operator sudah dua kali kernel
|
|
80169
|
+
// panic akibat batch sesi paralel tepat SAAT boot (memori mac-mini-8gb-panic-agen-paralel),
|
|
80170
|
+
// titik waktu paling rawan menumpuk kandidat resume. Gerbang dependency ADR-0093 tak berubah.
|
|
80171
|
+
startSpec: (spec, runtime) => startSpecSession(spec, { flow: flowForSource(spec.source), ...runtime ?? {} }),
|
|
80172
|
+
recordFail: recordFailure,
|
|
80173
|
+
recordDeferred: recordFailure
|
|
79344
80174
|
};
|
|
79345
80175
|
async function resumeReconciledSessions(cutoff, deps = prodDeps2) {
|
|
79346
|
-
const report = { resumed: [], failed: [] };
|
|
80176
|
+
const report = { resumed: [], failed: [], deferred: [] };
|
|
79347
80177
|
const specIds = await reconciledSpecIdsSince(cutoff);
|
|
79348
80178
|
if (!specIds.length) return report;
|
|
79349
80179
|
const specs = await prisma.spec.findMany({ where: { id: { in: specIds }, stage: { not: "done" } } });
|
|
@@ -79353,6 +80183,16 @@ async function resumeReconciledSessions(cutoff, deps = prodDeps2) {
|
|
|
79353
80183
|
await deps.startSpec(spec, runtimes.get(spec.id));
|
|
79354
80184
|
report.resumed.push(spec.id);
|
|
79355
80185
|
} catch (e) {
|
|
80186
|
+
if (e instanceof LaunchAdmissionError) {
|
|
80187
|
+
await deps.recordDeferred(
|
|
80188
|
+
spec.id,
|
|
80189
|
+
spec.title,
|
|
80190
|
+
spec.projectId,
|
|
80191
|
+
`ditunda \u2014 cap/beban host penuh saat boot (${e.kind}). Lanjutkan manual saat slot kosong.`
|
|
80192
|
+
);
|
|
80193
|
+
report.deferred.push(spec.id);
|
|
80194
|
+
continue;
|
|
80195
|
+
}
|
|
79356
80196
|
const reason = e instanceof Error ? e.message : String(e);
|
|
79357
80197
|
await deps.recordFail(spec.id, spec.title, spec.projectId, `gagal dilanjutkan otomatis \u2014 ${reason}`);
|
|
79358
80198
|
report.failed.push(spec.id);
|
|
@@ -79634,13 +80474,13 @@ function startWebhookEngine() {
|
|
|
79634
80474
|
// src/services/auto-merge.ts
|
|
79635
80475
|
init_src();
|
|
79636
80476
|
init_db();
|
|
79637
|
-
import { execFile as
|
|
79638
|
-
import { promisify as
|
|
80477
|
+
import { execFile as execFile19 } from "node:child_process";
|
|
80478
|
+
import { promisify as promisify16 } from "node:util";
|
|
79639
80479
|
init_notifications2();
|
|
79640
80480
|
var AUTO_MERGE_WINDOW_MS = 24 * 60 * 60 * 1e3;
|
|
79641
80481
|
var AUTO_MERGE_GRACE_MS = 15 * 60 * 1e3;
|
|
79642
80482
|
var TICK_MS4 = 6e4;
|
|
79643
|
-
var exec12 =
|
|
80483
|
+
var exec12 = promisify16(execFile19);
|
|
79644
80484
|
var GIT9 = { timeout: 3e4, maxBuffer: 1 << 24, encoding: "utf8" };
|
|
79645
80485
|
var gitOut = async (cwd, args) => {
|
|
79646
80486
|
try {
|
|
@@ -80129,11 +80969,13 @@ bootstrapReady.then(async () => {
|
|
|
80129
80969
|
console.log(`worktree yatim: ${row.projectId} \u2014 ${row.count} menunggu konfirmasi di tab Worktrees`);
|
|
80130
80970
|
}
|
|
80131
80971
|
if (n2) {
|
|
80132
|
-
const { resumed, failed } = await resumeReconciledSessions(reconcileCutoff);
|
|
80972
|
+
const { resumed, failed, deferred } = await resumeReconciledSessions(reconcileCutoff);
|
|
80133
80973
|
if (resumed.length)
|
|
80134
80974
|
console.log(`auto-resume: ${resumed.length} sesi dilanjutkan otomatis (${resumed.join(", ")})`);
|
|
80135
80975
|
if (failed.length)
|
|
80136
80976
|
console.log(`auto-resume: ${failed.length} sesi gagal dilanjutkan otomatis, lihat notifikasi (${failed.join(", ")})`);
|
|
80977
|
+
if (deferred.length)
|
|
80978
|
+
console.log(`auto-resume: ${deferred.length} sesi ditunda (cap/beban host penuh), lihat notifikasi (${deferred.join(", ")})`);
|
|
80137
80979
|
}
|
|
80138
80980
|
}).catch((e) => console.error("rekonsiliasi riwayat sesi:", e));
|
|
80139
80981
|
void reconcileAgentInvocations(liveIds).then((n2) => {
|