hanoman 0.3.3 → 0.4.0
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 +120 -8
- package/dist/server.js +1430 -794
- package/package.json +1 -1
- package/prisma/migrations/20260914120000_agent_invocation_phase/migration.sql +3 -0
- package/prisma/schema.prisma +3 -0
- package/web/assets/{ChangelogScreen-D_-RmJkz.js → ChangelogScreen-aqsJmmfe.js} +1 -1
- package/web/assets/{DalangHanomanScreen-pVfnLx9z.js → DalangHanomanScreen-D3rNURz7.js} +1 -1
- package/web/assets/{DocPreviewModal-ZFjISCL0.js → DocPreviewModal-B7r_7p8N.js} +1 -1
- package/web/assets/{DocsWorkspace-Vp_vik3v.js → DocsWorkspace-_w6pncu9.js} +1 -1
- package/web/assets/{IdeScreen-DQSRs3fr.js → IdeScreen-etZ2QyO-.js} +1 -1
- package/web/assets/{LeadScreen-Bv5LiudD.js → LeadScreen-Bo0K2-ML.js} +1 -1
- package/web/assets/{ReviewScreen-Cime4Tci.js → ReviewScreen-lRf2FW30.js} +1 -1
- package/web/assets/{SchedulerScreen-DKmRNbTs.js → SchedulerScreen-D75twy4J.js} +1 -1
- package/web/assets/SettingsScreen-rMIR22vv.js +62 -0
- package/web/assets/{TeamScreen-5ugoXsOv.js → TeamScreen-nsttkE2J.js} +1 -1
- package/web/assets/TerminalScreen-BG9vRE1-.js +9 -0
- package/web/assets/{TriageScreen-BajACF5K.js → TriageScreen-7eM8gjr_.js} +1 -1
- package/web/assets/{VpsScreen-C7muNRX5.js → VpsScreen-C29j52Tu.js} +1 -1
- package/web/assets/{diff-view-CsZPBznT.js → diff-view-RZNZ9Nz6.js} +1 -1
- package/web/assets/{index-DI7G_sit.js → index-BHI_f9DD.js} +192 -192
- package/web/assets/{live-DVDstDzi.js → live-CniaVDeP.js} +1 -1
- package/web/index.html +1 -1
- package/web/assets/SettingsScreen-Bh8XhyVQ.js +0 -62
- package/web/assets/TerminalScreen-DsAn5dlH.js +0 -9
package/dist/server.js
CHANGED
|
@@ -4607,6 +4607,11 @@ function replaceModelCatalog(claude, codex2) {
|
|
|
4607
4607
|
function claudeEfforts(modelId) {
|
|
4608
4608
|
return MODELS.find((m) => m.id === modelId)?.efforts ?? EFFORTS;
|
|
4609
4609
|
}
|
|
4610
|
+
function coerceClaudeEffort(modelId, effort2) {
|
|
4611
|
+
const levels = MODELS.find((m) => m.id === modelId)?.efforts;
|
|
4612
|
+
if (!levels?.length || levels.includes(effort2)) return effort2;
|
|
4613
|
+
return levels.includes("xhigh") ? "xhigh" : levels[0];
|
|
4614
|
+
}
|
|
4610
4615
|
function codexModel(id2) {
|
|
4611
4616
|
return CODEX_MODELS.find((m) => m.id === id2);
|
|
4612
4617
|
}
|
|
@@ -4627,7 +4632,7 @@ function cmpVersion(a, b) {
|
|
|
4627
4632
|
}
|
|
4628
4633
|
return 0;
|
|
4629
4634
|
}
|
|
4630
|
-
var zProject, zBriefPayload, zQaPayload, zGoalPayload, zSpecBlocker, zSourceChange, zManualDone, zSpec, NOTIFY_SOUNDS, MODELS, EFFORTS, 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, zSetting, zNotification, zDocFile, zDeviceTokenView;
|
|
4635
|
+
var zProject, zBriefPayload, zQaPayload, zGoalPayload, zSpecBlocker, zSourceChange, zManualDone, zSpec, NOTIFY_SOUNDS, MODELS, EFFORTS, 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, zFlowOrchestration, zOrchestration, ORCHESTRATION_DEFAULTS, zSetting, zNotification, zDocFile, zDeviceTokenView;
|
|
4631
4636
|
var init_entities = __esm({
|
|
4632
4637
|
"../shared/src/entities.ts"() {
|
|
4633
4638
|
"use strict";
|
|
@@ -4858,6 +4863,27 @@ var init_entities = __esm({
|
|
|
4858
4863
|
timeoutSec: external_exports.number().int().min(10).max(900).default(180)
|
|
4859
4864
|
});
|
|
4860
4865
|
PORTAL_CHAT_DEFAULTS = zPortalChat.parse({});
|
|
4866
|
+
zPhaseCell = external_exports.object({
|
|
4867
|
+
model: external_exports.string().nullable().default(null),
|
|
4868
|
+
effort: external_exports.string().nullable().default(null)
|
|
4869
|
+
});
|
|
4870
|
+
zFlowOrchestration = external_exports.object({
|
|
4871
|
+
enabled: external_exports.boolean().default(true),
|
|
4872
|
+
claude: external_exports.record(external_exports.string(), zPhaseCell).default({}),
|
|
4873
|
+
codex: external_exports.record(external_exports.string(), zPhaseCell).default({})
|
|
4874
|
+
});
|
|
4875
|
+
zOrchestration = external_exports.object({
|
|
4876
|
+
feature: zFlowOrchestration.default({}),
|
|
4877
|
+
qa: zFlowOrchestration.default({}),
|
|
4878
|
+
scaffold: zFlowOrchestration.default({}),
|
|
4879
|
+
reverse: zFlowOrchestration.default({}),
|
|
4880
|
+
prd: zFlowOrchestration.default({}),
|
|
4881
|
+
audit: zFlowOrchestration.default({}),
|
|
4882
|
+
breakdown: zFlowOrchestration.default({}),
|
|
4883
|
+
goal: zFlowOrchestration.default({}),
|
|
4884
|
+
no_effort: zFlowOrchestration.default({})
|
|
4885
|
+
});
|
|
4886
|
+
ORCHESTRATION_DEFAULTS = zOrchestration.parse({});
|
|
4861
4887
|
zSetting = external_exports.object({
|
|
4862
4888
|
model: external_exports.string().default("claude-opus-5"),
|
|
4863
4889
|
effort: external_exports.string().default("xhigh"),
|
|
@@ -4900,6 +4926,8 @@ var init_entities = __esm({
|
|
|
4900
4926
|
// SPEC-518 · agen pembuat changelog (opt-in, mati)
|
|
4901
4927
|
portalChat: zPortalChat.default(PORTAL_CHAT_DEFAULTS),
|
|
4902
4928
|
// SPEC-854 · ADR-0130 · chat portal klien (opt-in, mati)
|
|
4929
|
+
orchestration: zOrchestration.default(ORCHESTRATION_DEFAULTS),
|
|
4930
|
+
// ADR-0164 · orkestrasi subagent per fase (default aktif)
|
|
4903
4931
|
// SPEC-881 · ADR-0136 · sidik jari isi bawaan yang TERAKHIR ditulis seed di mesin ini, per nama
|
|
4904
4932
|
// agen. Dipakai seed untuk membedakan "belum pernah disunting operator" dari "sudah". WAJIB
|
|
4905
4933
|
// dideklarasikan di sini: zod membuang kunci tak dikenal dan `PUT /settings` menulis balik hasil
|
|
@@ -4944,6 +4972,73 @@ var init_entities = __esm({
|
|
|
4944
4972
|
}
|
|
4945
4973
|
});
|
|
4946
4974
|
|
|
4975
|
+
// ../shared/src/orchestration.ts
|
|
4976
|
+
function phaseAgentName(phase) {
|
|
4977
|
+
return PHASE_AGENT_PREFIX + phase.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
4978
|
+
}
|
|
4979
|
+
var FLOW_PHASES, PHASE_AGENT_PREFIX, isPhaseAgentName, PHASE_EVIDENCE_GRACE_MS;
|
|
4980
|
+
var init_orchestration = __esm({
|
|
4981
|
+
"../shared/src/orchestration.ts"() {
|
|
4982
|
+
"use strict";
|
|
4983
|
+
FLOW_PHASES = {
|
|
4984
|
+
feature: ["Brainstorm", "Objective", "Spec", "Plan", "Execute"],
|
|
4985
|
+
qa: ["Audit", "Spec", "Plan", "Execute"],
|
|
4986
|
+
scaffold: ["Brainstorm", "Objective", "Doc index"],
|
|
4987
|
+
reverse: ["Scan", "Docs teknis", "Wawancara", "Konvensi & index", "Serah terima"],
|
|
4988
|
+
prd: ["Brainstorm", "PRD"],
|
|
4989
|
+
audit: ["Audit", "Laporan"],
|
|
4990
|
+
breakdown: ["Analisis", "Breakdown"],
|
|
4991
|
+
// SPEC-337 · ADR-0075 · audit lintas project: fase & stage-map identik audit-only, scope-nya
|
|
4992
|
+
// yang berbeda (project utama + tetangga ProjectLink).
|
|
4993
|
+
// SPEC-407 · ADR-0089 · backlog goal: tak ada fase perencanaan sama sekali. `Goal` = kerjakan,
|
|
4994
|
+
// `Verifikasi` = buktikan. Kedua nama unik lintas daftar ini — syarat peta REACHED di server,
|
|
4995
|
+
// yang berkunci nama fase saja.
|
|
4996
|
+
goal: ["Goal", "Verifikasi"],
|
|
4997
|
+
// SPEC-825 · ADR-0123 · task remeh: SATU fase. Nama `Kerjakan` unik lintas daftar ini — syarat
|
|
4998
|
+
// peta REACHED server, yang berkunci nama fase saja.
|
|
4999
|
+
no_effort: ["Kerjakan"]
|
|
5000
|
+
};
|
|
5001
|
+
PHASE_AGENT_PREFIX = "hanoman-fase-";
|
|
5002
|
+
isPhaseAgentName = (name2) => name2.startsWith(PHASE_AGENT_PREFIX);
|
|
5003
|
+
PHASE_EVIDENCE_GRACE_MS = 6e4;
|
|
5004
|
+
}
|
|
5005
|
+
});
|
|
5006
|
+
|
|
5007
|
+
// ../shared/src/orchestration-plan.ts
|
|
5008
|
+
function codexNativeAgentsSupported(version) {
|
|
5009
|
+
const parsed = version ? /(\d+)\.(\d+)\.(\d+)/.exec(version)?.[0] : null;
|
|
5010
|
+
return parsed ? cmpVersion(parsed, CODEX_NATIVE_AGENTS_MIN_CLIENT) >= 0 : false;
|
|
5011
|
+
}
|
|
5012
|
+
function resolvePhasePlan(input) {
|
|
5013
|
+
const cfg = input.orchestration?.[input.flow];
|
|
5014
|
+
if (!input.nativeAgents || cfg?.enabled === false) return null;
|
|
5015
|
+
const cells = cfg?.[input.runtime] ?? {};
|
|
5016
|
+
const coerce3 = input.runtime === "codex" ? coerceCodexEffort : coerceClaudeEffort;
|
|
5017
|
+
return {
|
|
5018
|
+
flow: input.flow,
|
|
5019
|
+
runtime: input.runtime,
|
|
5020
|
+
phases: FLOW_PHASES[input.flow].map((phase) => {
|
|
5021
|
+
const cell = cells[phase];
|
|
5022
|
+
const model = cell?.model ?? input.orchestrator.model;
|
|
5023
|
+
return {
|
|
5024
|
+
phase,
|
|
5025
|
+
agentName: phaseAgentName(phase),
|
|
5026
|
+
model,
|
|
5027
|
+
effort: coerce3(model, cell?.effort ?? input.orchestrator.effort)
|
|
5028
|
+
};
|
|
5029
|
+
})
|
|
5030
|
+
};
|
|
5031
|
+
}
|
|
5032
|
+
var CODEX_NATIVE_AGENTS_MIN_CLIENT;
|
|
5033
|
+
var init_orchestration_plan = __esm({
|
|
5034
|
+
"../shared/src/orchestration-plan.ts"() {
|
|
5035
|
+
"use strict";
|
|
5036
|
+
init_entities();
|
|
5037
|
+
init_orchestration();
|
|
5038
|
+
CODEX_NATIVE_AGENTS_MIN_CLIENT = "0.151.0";
|
|
5039
|
+
}
|
|
5040
|
+
});
|
|
5041
|
+
|
|
4947
5042
|
// ../shared/src/agent.ts
|
|
4948
5043
|
function grantsCapability(granted, need) {
|
|
4949
5044
|
if (granted.includes(need)) return true;
|
|
@@ -5154,6 +5249,7 @@ var init_custom_agent = __esm({
|
|
|
5154
5249
|
"../shared/src/custom-agent.ts"() {
|
|
5155
5250
|
"use strict";
|
|
5156
5251
|
init_zod();
|
|
5252
|
+
init_orchestration();
|
|
5157
5253
|
AGENT_RUNTIMES = ["claude", "codex"];
|
|
5158
5254
|
zAgentRuntime = external_exports.enum(AGENT_RUNTIMES);
|
|
5159
5255
|
AGENT_RUNTIME_LABELS = {
|
|
@@ -5200,7 +5296,9 @@ var init_custom_agent = __esm({
|
|
|
5200
5296
|
});
|
|
5201
5297
|
zCreateCustomAgentFields = external_exports.object({
|
|
5202
5298
|
projectId: external_exports.string().nullable().optional(),
|
|
5203
|
-
|
|
5299
|
+
// ADR-0164 · awalan agen fase dicadangkan — registry native berkunci nama, jadi custom agent
|
|
5300
|
+
// bernama sama akan menimpa agen fase sesi orchestrator tanpa satu pun galat.
|
|
5301
|
+
name: external_exports.string().regex(AGENT_NAME_RE).refine((n2) => !isPhaseAgentName(n2), { message: `awalan ${PHASE_AGENT_PREFIX} dicadangkan untuk agen fase hanoman` }),
|
|
5204
5302
|
description: external_exports.string().trim().min(1).max(500),
|
|
5205
5303
|
instructions: external_exports.string().trim().min(1).max(2e4),
|
|
5206
5304
|
tools: external_exports.array(external_exports.string()).nullable().optional(),
|
|
@@ -12202,6 +12300,8 @@ var init_src = __esm({
|
|
|
12202
12300
|
init_method_status();
|
|
12203
12301
|
init_agent_engine();
|
|
12204
12302
|
init_entities();
|
|
12303
|
+
init_orchestration();
|
|
12304
|
+
init_orchestration_plan();
|
|
12205
12305
|
init_agent();
|
|
12206
12306
|
init_custom_agent();
|
|
12207
12307
|
init_builtin_agents();
|
|
@@ -12467,12 +12567,62 @@ var init_goal_spec = __esm({
|
|
|
12467
12567
|
});
|
|
12468
12568
|
|
|
12469
12569
|
// ../runner/src/prompt.ts
|
|
12470
|
-
function
|
|
12570
|
+
function guideLine(guide, phase) {
|
|
12571
|
+
return guide.split("\n").find((line) => line.startsWith(`- ${phase}:`)) ?? "";
|
|
12572
|
+
}
|
|
12573
|
+
function phaseSkillsFor(flow, phase, method) {
|
|
12574
|
+
const own = method.phaseSkills[phase] ?? [];
|
|
12575
|
+
const phases = PIPELINES[flow];
|
|
12576
|
+
return writesCode(flow) && phase === phases[phases.length - 1] ? [.../* @__PURE__ */ new Set([...own, ...method.exitSkills])] : [...own];
|
|
12577
|
+
}
|
|
12578
|
+
function orchestratorClause(plan, o = {}) {
|
|
12579
|
+
const codex2 = plan.runtime === "codex";
|
|
12580
|
+
const call = codex2 ? "spawn_agent" : "tool Agent";
|
|
12581
|
+
const resume = codex2 ? "send_input ke agent id yang sama" : "SendMessage ke agent ID yang kamu terima";
|
|
12582
|
+
const describeRule = codex2 ? "Deskripsi/label pemanggilan diisi persis `Fase <Nama Fase>` \u2014 tanpa nomor urut, dan BUKAN baris pertama blok serah-terima di atas." : "Deskripsi pemanggilan (parameter `description`) diisi persis `Fase <Nama Fase>` \u2014 tanpa nomor urut, dan BUKAN baris pertama blok serah-terima di atas.";
|
|
12583
|
+
const askEscalation = codex2 ? "tanyakan di terminal ini lalu tunggu jawaban" : "tanyakan lewat AskUserQuestion";
|
|
12584
|
+
const askReport = codex2 ? "tanyakan ke manusia di terminal ini lalu tunggu jawaban" : "tanyakan ke manusia (AskUserQuestion; di fase yang memang bergiliran dengan manusia \u2014 Wawancara, Brainstorm prd/scaffold \u2014 tanyakan di terminal ini)";
|
|
12585
|
+
const list2 = plan.phases.map((p3, i) => `${i + 1}. ${p3.phase} \u2192 \`${p3.agentName}\` \xB7 ${p3.model} \xB7 ${p3.effort}`).join("\n");
|
|
12586
|
+
return [
|
|
12587
|
+
"Sesi ini ORCHESTRATOR. Setiap fase dikerjakan subagent fase miliknya dengan model & effort yang sudah terkunci di definisinya \u2014 kamu TIDAK mengerjakan isi fase sendiri.",
|
|
12588
|
+
`Fase berurutan:
|
|
12589
|
+
${list2}`,
|
|
12590
|
+
"Untuk SETIAP fase, berurutan:",
|
|
12591
|
+
`1. Panggil subagent fasenya lewat ${call}, tugasnya berupa blok serah-terima berbentuk tetap:
|
|
12592
|
+
Urutan: <n>/<total> \xB7 <Nama Fase>
|
|
12593
|
+
Tujuan: <objective backlog/project>
|
|
12594
|
+
Base SHA: $HANOMAN_BASE_SHA (atau -)
|
|
12595
|
+
Artefak fase sebelumnya: <path yang dilaporkan, atau ->
|
|
12596
|
+
Keputusan manusia sejauh ini: <ringkas, atau ->
|
|
12597
|
+
Lampiran: <path INDEX.md lampiran, atau ->
|
|
12598
|
+
Percobaan: <k>/2
|
|
12599
|
+
|
|
12600
|
+
${describeRule}`,
|
|
12601
|
+
'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.',
|
|
12602
|
+
`3. \`Status: sebagian\`/\`terhalang\`, galat, atau laporan tanpa bukti \u2192 delegasikan ULANG SEKALI ke subagent fase yang sama 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.`,
|
|
12603
|
+
`4. \`Pertanyaan untuk manusia:\` di laporan \u2192 ${askReport}, lalu LANJUTKAN subagent yang SAMA lewat ${resume} dengan jawabannya. Giliran relay ini bukan percobaan ulang. Di sesi tanpa pengawas, putuskan sendiri lalu teruskan keputusanmu dengan cara yang sama.`,
|
|
12604
|
+
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.' : "",
|
|
12605
|
+
"DILARANG mengerjakan isi fase sendiri \u2014 termasuk saat subagent gagal. Menulis `skipped` untuk fase yang dilewati bukan mengerjakannya.",
|
|
12606
|
+
'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.'
|
|
12607
|
+
].filter(Boolean).join("\n\n");
|
|
12608
|
+
}
|
|
12609
|
+
function startPrompt(flow, spec, branchTo, autonomy, verifyScope, method, attachments, plan) {
|
|
12471
12610
|
const m = resolveMethod(method);
|
|
12472
|
-
const
|
|
12473
|
-
|
|
12611
|
+
const head = `hanoman ${flow}. Ikuti internal/docs sebagai Source of Truth; perbarui docs yang tersentuh dan link-nya di index, dalam commit yang sama.`;
|
|
12612
|
+
const push = `Setelah fase terakhir: commit, lalu \`git push origin HEAD:refs/heads/${branchTo}\`. Worktree ini detached HEAD \u2014 itu memang disengaja.`;
|
|
12613
|
+
if (plan) {
|
|
12614
|
+
return [
|
|
12615
|
+
head,
|
|
12616
|
+
orchestratorClause(plan, { fastPath: flow === "qa" }),
|
|
12617
|
+
auditContinuationForOrchestrator(flow, spec),
|
|
12618
|
+
autonomyClause(autonomy),
|
|
12619
|
+
attachmentClause(attachments),
|
|
12620
|
+
push,
|
|
12621
|
+
specContext(spec)
|
|
12622
|
+
].filter(Boolean).join("\n\n");
|
|
12623
|
+
}
|
|
12474
12624
|
return [
|
|
12475
|
-
|
|
12625
|
+
head,
|
|
12476
12626
|
phaseInstruction(PIPELINES[flow], m),
|
|
12477
12627
|
auditDecisionInstruction(flow),
|
|
12478
12628
|
auditContinuationInstruction(flow, spec),
|
|
@@ -12482,40 +12632,59 @@ Detail: ${JSON.stringify(spec.payload)}` : "";
|
|
|
12482
12632
|
codeStyleClause(flow),
|
|
12483
12633
|
methodClause(m),
|
|
12484
12634
|
attachmentClause(attachments),
|
|
12485
|
-
skillInstruction(PIPELINES[flow], m
|
|
12486
|
-
|
|
12487
|
-
|
|
12488
|
-
Judul: ${spec.title}
|
|
12489
|
-
Objective: ${spec.objective}${detail}`
|
|
12635
|
+
skillInstruction(flow, PIPELINES[flow], m),
|
|
12636
|
+
push,
|
|
12637
|
+
specContext(spec)
|
|
12490
12638
|
].filter(Boolean).join("\n\n");
|
|
12491
12639
|
}
|
|
12492
|
-
function continuePrompt(flow, spec, branchTo, autonomy, verifyScope, method, attachments) {
|
|
12640
|
+
function continuePrompt(flow, spec, branchTo, autonomy, verifyScope, method, attachments, plan) {
|
|
12493
12641
|
const m = resolveMethod(method);
|
|
12494
|
-
const
|
|
12495
|
-
|
|
12642
|
+
const head = `hanoman ${flow} \u2014 MELANJUTKAN backlog item yang sebelumnya ditandai selesai padahal pekerjaannya belum tuntas. Ikuti internal/docs sebagai Source of Truth; perbarui docs yang tersentuh dan link-nya di index, dalam commit yang sama.`;
|
|
12643
|
+
const push = `Setelah selesai: commit, lalu \`git push origin HEAD:refs/heads/${branchTo}\`. Worktree ini detached HEAD \u2014 itu memang disengaja.`;
|
|
12644
|
+
if (plan) {
|
|
12645
|
+
return [
|
|
12646
|
+
head,
|
|
12647
|
+
`JANGAN mengulang fase awal \u2014 spec & plan sudah ada di ${m.planDir}/**. Lanjutkan HANYA fase Execute lewat subagent fasenya.`,
|
|
12648
|
+
orchestratorClause(plan),
|
|
12649
|
+
autonomyClause(autonomy),
|
|
12650
|
+
attachmentClause(attachments),
|
|
12651
|
+
push,
|
|
12652
|
+
specContext(spec)
|
|
12653
|
+
].filter(Boolean).join("\n\n");
|
|
12654
|
+
}
|
|
12496
12655
|
return [
|
|
12497
|
-
|
|
12656
|
+
head,
|
|
12498
12657
|
`JANGAN mengulang fase awal \u2014 spec & plan sudah ada. Lanjut di fase Execute: baca plan di ${m.planDir}/** untuk backlog item ini, periksa task yang sudah \`[x]\` dan selesaikan yang masih \`[ ]\`. Verifikasi nyata sebelum klaim selesai.`,
|
|
12499
12658
|
autonomyClause(autonomy),
|
|
12500
12659
|
scopeClause(flow, verifyScope),
|
|
12501
12660
|
codeStyleClause(flow),
|
|
12502
12661
|
methodClause(m),
|
|
12503
12662
|
attachmentClause(attachments),
|
|
12504
|
-
skillInstruction(["Execute"], m
|
|
12505
|
-
|
|
12506
|
-
|
|
12507
|
-
Judul: ${spec.title}
|
|
12508
|
-
Objective: ${spec.objective}${detail}`
|
|
12663
|
+
skillInstruction(flow, ["Execute"], m),
|
|
12664
|
+
push,
|
|
12665
|
+
specContext(spec)
|
|
12509
12666
|
].filter(Boolean).join("\n\n");
|
|
12510
12667
|
}
|
|
12511
|
-
function resumePrompt(flow, spec, branchTo, resume, autonomy, verifyScope, method, attachments) {
|
|
12668
|
+
function resumePrompt(flow, spec, branchTo, resume, autonomy, verifyScope, method, attachments, plan) {
|
|
12512
12669
|
const m = resolveMethod(method);
|
|
12513
|
-
const detail = spec.payload ? `
|
|
12514
|
-
Detail: ${JSON.stringify(spec.payload)}` : "";
|
|
12515
12670
|
const auditDecided = resume.recorded.some((line) => line.startsWith("Audit "));
|
|
12671
|
+
const head = `hanoman ${flow} \u2014 MELANJUTKAN sesi backlog yang sudah berjalan. Ikuti internal/docs sebagai Source of Truth; perbarui docs yang tersentuh dan link-nya di index, dalam commit yang sama.`;
|
|
12672
|
+
const resumed = resumeClause(resume, branchTo, m.planDir, PIPELINES[flow].includes("Plan"));
|
|
12673
|
+
const push = `Setelah fase terakhir: commit, lalu \`git push origin HEAD:refs/heads/${branchTo}\`. Worktree ini detached HEAD \u2014 itu memang disengaja.`;
|
|
12674
|
+
if (plan) {
|
|
12675
|
+
return [
|
|
12676
|
+
head,
|
|
12677
|
+
resumed,
|
|
12678
|
+
orchestratorClause(plan, { fastPath: flow === "qa" && !auditDecided }),
|
|
12679
|
+
autonomyClause(autonomy),
|
|
12680
|
+
attachmentClause(attachments),
|
|
12681
|
+
push,
|
|
12682
|
+
specContext(spec)
|
|
12683
|
+
].filter(Boolean).join("\n\n");
|
|
12684
|
+
}
|
|
12516
12685
|
return [
|
|
12517
|
-
|
|
12518
|
-
|
|
12686
|
+
head,
|
|
12687
|
+
resumed,
|
|
12519
12688
|
phaseInstruction(PIPELINES[flow], m),
|
|
12520
12689
|
auditDecided ? "" : auditDecisionInstruction(flow),
|
|
12521
12690
|
autonomyClause(autonomy),
|
|
@@ -12523,26 +12692,33 @@ Detail: ${JSON.stringify(spec.payload)}` : "";
|
|
|
12523
12692
|
codeStyleClause(flow),
|
|
12524
12693
|
methodClause(m),
|
|
12525
12694
|
attachmentClause(attachments),
|
|
12526
|
-
skillInstruction(PIPELINES[flow], m
|
|
12527
|
-
|
|
12528
|
-
|
|
12529
|
-
Judul: ${spec.title}
|
|
12530
|
-
Objective: ${spec.objective}${detail}`
|
|
12695
|
+
skillInstruction(flow, PIPELINES[flow], m),
|
|
12696
|
+
push,
|
|
12697
|
+
specContext(spec)
|
|
12531
12698
|
].filter(Boolean).join("\n\n");
|
|
12532
12699
|
}
|
|
12533
12700
|
function startGoalPrompt(flow, spec, branchTo, opts = {}) {
|
|
12534
12701
|
const m = resolveMethod(opts.method);
|
|
12535
|
-
const g = readGoalPayload(spec.payload);
|
|
12536
12702
|
const noEffort = flow === "no_effort";
|
|
12537
|
-
const
|
|
12538
|
-
|
|
12539
|
-
|
|
12540
|
-
|
|
12541
|
-
|
|
12703
|
+
const head = noEffort ? "hanoman no-effort \u2014 sesi ini mengerjakan SATU pekerjaan remeh lalu berhenti. TIDAK ada fase Brainstorm, Objective, Spec, Plan, maupun fase pembuktian terpisah: jangan menulis design doc, jangan menulis plan berkotak, jangan memecah pekerjaan ini jadi backlog baru, dan jangan menambah fase sendiri. Langsung kerjakan, buktikan seperlunya di fase yang sama, lalu berhenti. Tetap ikuti internal/docs sebagai Source of Truth; perbarui docs yang tersentuh dan link-nya di index, dalam commit yang sama." : "hanoman goal \u2014 sesi ini mengejar SATU goal sampai tercapai. TIDAK ada fase Brainstorm, Objective, Spec, maupun Plan: jangan menulis design doc, jangan menulis plan berkotak, jangan memecah pekerjaan ini jadi backlog baru. Langsung kerjakan goal-nya. Tetap ikuti internal/docs sebagai Source of Truth; perbarui docs yang tersentuh dan link-nya di index, dalam commit yang sama.";
|
|
12704
|
+
const resumed = opts.resume ? resumeClause(opts.resume, branchTo, m.planDir, false) : "";
|
|
12705
|
+
const push = `Setelah fase terakhir: commit, lalu \`git push origin HEAD:refs/heads/${branchTo}\`. Worktree ini detached HEAD \u2014 itu memang disengaja.`;
|
|
12706
|
+
if (opts.plan) {
|
|
12707
|
+
return [
|
|
12708
|
+
head,
|
|
12709
|
+
resumed,
|
|
12710
|
+
goalDetail(spec),
|
|
12711
|
+
orchestratorClause(opts.plan),
|
|
12712
|
+
autonomyClause(opts.autonomy),
|
|
12713
|
+
attachmentClause(opts.attachments),
|
|
12714
|
+
push,
|
|
12715
|
+
goalBlock(spec)
|
|
12716
|
+
].filter(Boolean).join("\n\n");
|
|
12717
|
+
}
|
|
12542
12718
|
return [
|
|
12543
|
-
|
|
12544
|
-
|
|
12545
|
-
|
|
12719
|
+
head,
|
|
12720
|
+
resumed,
|
|
12721
|
+
goalDetail(spec),
|
|
12546
12722
|
phaseInstruction(PIPELINES[flow], m),
|
|
12547
12723
|
noEffort ? "" : "Fase Verifikasi bukan formalitas: jalankan perintah yang membuktikan goal-nya tercapai (test/typecheck/benchmark/perintah yang relevan) dan baca outputnya. Klaim tanpa output bukan bukti.",
|
|
12548
12724
|
autonomyClause(opts.autonomy),
|
|
@@ -12550,74 +12726,95 @@ function startGoalPrompt(flow, spec, branchTo, opts = {}) {
|
|
|
12550
12726
|
codeStyleClause(flow),
|
|
12551
12727
|
methodClause(m),
|
|
12552
12728
|
attachmentClause(opts.attachments),
|
|
12553
|
-
skillInstruction(PIPELINES[flow], m
|
|
12554
|
-
|
|
12555
|
-
|
|
12556
|
-
Judul: ${spec.title}`
|
|
12729
|
+
skillInstruction(flow, PIPELINES[flow], m),
|
|
12730
|
+
push,
|
|
12731
|
+
goalBlock(spec)
|
|
12557
12732
|
].filter(Boolean).join("\n\n");
|
|
12558
12733
|
}
|
|
12559
|
-
function startProjectPrompt(flow, project, branchTo) {
|
|
12734
|
+
function startProjectPrompt(flow, project, branchTo, plan) {
|
|
12735
|
+
const push = `Setiap fase selesai: commit hasilnya, lalu \`git push origin HEAD:refs/heads/${branchTo}\` \u2014 push per fase, supaya pekerjaan tak hilang bila worktree lenyap. Bila remote origin tidak ada, lewati push dan catat itu di laporan akhir \u2014 jangan gagal diam-diam. Worktree ini detached HEAD \u2014 memang disengaja. Manusia yang me-review dan merge branch ${branchTo}.`;
|
|
12736
|
+
if (plan) {
|
|
12737
|
+
return [
|
|
12738
|
+
`hanoman ${flow}. Susun Source of Truth repo ini dari kodenya di internal/docs/** lewat subagent fase; STANDAR DOCS ada di definisi agen fase penulis docs.`,
|
|
12739
|
+
orchestratorClause(plan),
|
|
12740
|
+
push,
|
|
12741
|
+
projectContext(project)
|
|
12742
|
+
].join("\n\n");
|
|
12743
|
+
}
|
|
12560
12744
|
return [
|
|
12561
12745
|
`hanoman ${flow}. Susun Source of Truth repo ini dari kodenya di internal/docs/**, mengikuti STANDAR DOCS di bagian bawah prompt ini.`,
|
|
12562
12746
|
phaseInstruction(PIPELINES[flow], PROJECT_METHOD),
|
|
12563
12747
|
REVERSE_PHASE_GUIDE,
|
|
12564
|
-
|
|
12565
|
-
|
|
12566
|
-
Deskripsi: ${project.desc || "\u2014"}
|
|
12567
|
-
Stack: ${project.stack || "\u2014"}`,
|
|
12748
|
+
push,
|
|
12749
|
+
projectContext(project),
|
|
12568
12750
|
`=== STANDAR DOCS ===
|
|
12569
12751
|
${REVERSE_STANDARD}`
|
|
12570
12752
|
].join("\n\n");
|
|
12571
12753
|
}
|
|
12572
|
-
function startPrdPrompt(project, brief, branchTo, audit) {
|
|
12754
|
+
function startPrdPrompt(project, brief, branchTo, audit, plan) {
|
|
12573
12755
|
const slug = branchTo.slice(branchTo.lastIndexOf("/") + 1);
|
|
12574
|
-
const
|
|
12575
|
-
|
|
12576
|
-
|
|
12577
|
-
|
|
12756
|
+
const push = `Setelah PRD ditulis: commit, lalu \`git push origin HEAD:refs/heads/${branchTo}\`. Bila remote origin tidak ada, lewati push dan catat itu di terminal \u2014 jangan gagal diam-diam. Worktree ini detached HEAD \u2014 memang disengaja. Manusia yang me-review lalu merge branch ${branchTo}.`;
|
|
12757
|
+
if (plan) {
|
|
12758
|
+
return [
|
|
12759
|
+
"hanoman prd. Kamu memimpin penyusunan SATU dokumen PRD untuk project ini lewat subagent fase. Keluaranmu HANYA dokumen PRD \u2014 JANGAN menulis kode fitur.",
|
|
12760
|
+
orchestratorClause(plan),
|
|
12761
|
+
push,
|
|
12762
|
+
prdBriefBlock(project, brief)
|
|
12763
|
+
].join("\n\n");
|
|
12764
|
+
}
|
|
12765
|
+
const lines2 = prdPhaseLines(slug);
|
|
12578
12766
|
return [
|
|
12579
12767
|
`hanoman prd. Kamu memandu PM/PO menyusun SATU dokumen PRD untuk project ini dari brief + brainstorm. Keluaranmu HANYA dokumen PRD \u2014 JANGAN menulis kode fitur.`,
|
|
12580
12768
|
phaseInstruction(PIPELINES.prd, PROJECT_METHOD),
|
|
12581
|
-
|
|
12582
|
-
|
|
12583
|
-
skillInstruction(PIPELINES.prd, PROJECT_METHOD
|
|
12584
|
-
|
|
12585
|
-
|
|
12586
|
-
|
|
12587
|
-
Konteks: ${brief.context}
|
|
12588
|
-
Outcome: ${brief.outcome}${brief.constraints ? `
|
|
12589
|
-
Batasan: ${brief.constraints}` : ""}`,
|
|
12590
|
-
auditBlock
|
|
12769
|
+
lines2.Brainstorm,
|
|
12770
|
+
lines2.PRD,
|
|
12771
|
+
skillInstruction("prd", PIPELINES.prd, PROJECT_METHOD),
|
|
12772
|
+
push,
|
|
12773
|
+
prdBriefBlock(project, brief),
|
|
12774
|
+
prdAuditBlock(audit)
|
|
12591
12775
|
].filter(Boolean).join("\n\n");
|
|
12592
12776
|
}
|
|
12593
|
-
function startBreakdownPrompt(project, prd, branchTo) {
|
|
12777
|
+
function startBreakdownPrompt(project, prd, branchTo, plan) {
|
|
12594
12778
|
const slug = branchTo.slice(branchTo.lastIndexOf("/") + 1);
|
|
12779
|
+
const push = `Setelah manifest ditulis: commit, lalu \`git push origin HEAD:refs/heads/${branchTo}\`. Bila remote origin tidak ada, lewati push dan catat itu di terminal \u2014 jangan gagal diam-diam. Worktree ini detached HEAD \u2014 memang disengaja. Manusia me-review manifest lalu materialize backlog darinya.`;
|
|
12780
|
+
if (plan) {
|
|
12781
|
+
return [
|
|
12782
|
+
"hanoman breakdown. Kamu memimpin pemecahan SATU PRD kompleks menjadi BEBERAPA backlog kecil yang bisa dikerjakan PARALEL lewat subagent fase. Keluaranmu HANYA dokumen manifest \u2014 JANGAN menulis kode fitur.",
|
|
12783
|
+
orchestratorClause(plan),
|
|
12784
|
+
AUTONOMY_CLAUSE,
|
|
12785
|
+
push,
|
|
12786
|
+
`Project ${project.id} \xB7 ${project.name}
|
|
12787
|
+
PRD: ${prd.title} (${prd.path})`
|
|
12788
|
+
].join("\n\n");
|
|
12789
|
+
}
|
|
12790
|
+
const lines2 = breakdownPhaseLines(slug, prd.title);
|
|
12595
12791
|
return [
|
|
12596
12792
|
`hanoman breakdown. Kamu memecah SATU PRD kompleks menjadi BEBERAPA backlog kecil yang bisa dikerjakan PARALEL tanpa saling bergantung. Keluaranmu HANYA dokumen manifest \u2014 JANGAN menulis kode fitur.`,
|
|
12597
12793
|
phaseInstruction(PIPELINES.breakdown, PROJECT_METHOD),
|
|
12598
|
-
|
|
12599
|
-
|
|
12600
|
-
\`\`\`json
|
|
12601
|
-
{ "items": [ { "title": "\u2026", "context": "\u2026", "outcome": "\u2026", "priority": "sedang" } ] }
|
|
12602
|
-
\`\`\`
|
|
12603
|
-
\`context\` = bagian PRD yang dicakup; \`outcome\` = kondisi selesai terukur; \`title\` ringkas. Minimal 2 item bila PRD memang kompleks; bila PRD ternyata sekecil 1 unit, katakan itu di prosa dan tetap tulis 1 item.`,
|
|
12794
|
+
lines2.Analisis,
|
|
12795
|
+
lines2.Breakdown,
|
|
12604
12796
|
AUTONOMY_CLAUSE,
|
|
12605
|
-
|
|
12606
|
-
|
|
12607
|
-
=== PRD: ${prd.title} (${prd.path}) ===
|
|
12608
|
-
${prd.content}`
|
|
12797
|
+
push,
|
|
12798
|
+
breakdownContext(project, prd)
|
|
12609
12799
|
].filter(Boolean).join("\n\n");
|
|
12610
12800
|
}
|
|
12611
|
-
function startScaffoldPrompt(project, branchTo) {
|
|
12801
|
+
function startScaffoldPrompt(project, branchTo, plan) {
|
|
12802
|
+
const push = `Setiap fase selesai: commit hasilnya, lalu \`git push origin HEAD:refs/heads/${branchTo}\` \u2014 push per fase, supaya pekerjaan tak hilang bila worktree lenyap. Bila remote origin tidak ada, lewati push dan catat itu di laporan akhir \u2014 jangan gagal diam-diam. Worktree ini detached HEAD \u2014 memang disengaja. Manusia yang me-review dan merge branch ${branchTo}.`;
|
|
12803
|
+
if (plan) {
|
|
12804
|
+
return [
|
|
12805
|
+
"hanoman scaffold. Susun Source of Truth LENGKAP untuk project from-scratch ini di internal/docs/** DARI IDE-nya lewat subagent fase; STANDAR DOCS ada di definisi agen fase Doc index. Belum ada kode \u2014 docs dulu.",
|
|
12806
|
+
orchestratorClause(plan),
|
|
12807
|
+
push,
|
|
12808
|
+
scaffoldContext(project)
|
|
12809
|
+
].join("\n\n");
|
|
12810
|
+
}
|
|
12612
12811
|
return [
|
|
12613
12812
|
`hanoman scaffold. Susun Source of Truth LENGKAP untuk project from-scratch ini di internal/docs/** DARI IDE-nya, mengikuti STANDAR DOCS di bagian bawah prompt ini. Belum ada kode \u2014 docs dulu.`,
|
|
12614
12813
|
phaseInstruction(PIPELINES.scaffold, PROJECT_METHOD),
|
|
12615
12814
|
SCAFFOLD_PHASE_GUIDE,
|
|
12616
|
-
|
|
12617
|
-
skillInstruction(PIPELINES.scaffold, PROJECT_METHOD
|
|
12618
|
-
|
|
12619
|
-
Ide awal: ${project.desc || "\u2014"}
|
|
12620
|
-
Stack: ${project.stack || "\u2014"}`,
|
|
12815
|
+
push,
|
|
12816
|
+
skillInstruction("scaffold", PIPELINES.scaffold, PROJECT_METHOD),
|
|
12817
|
+
scaffoldContext(project),
|
|
12621
12818
|
`=== STANDAR DOCS ===
|
|
12622
12819
|
${REVERSE_STANDARD}`
|
|
12623
12820
|
].filter(Boolean).join("\n\n");
|
|
@@ -12636,7 +12833,7 @@ Stack: ${project.stack || "\u2014"}`,
|
|
|
12636
12833
|
${cron.prompt}`
|
|
12637
12834
|
].join("\n\n");
|
|
12638
12835
|
}
|
|
12639
|
-
var PIPELINES, WORK_PHASES, AUTONOMY_CLAUSE, AUTONOMY_CLAUSE_FULL, autonomyClause, phaseInstruction, skillInstruction, auditDecisionInstruction, ESCALATION_CONTRACT, auditOnlyInstruction, auditContinuationInstruction, writesCode, scopeClause, codeStyleClause, methodClause, attachmentClause, PROJECT_METHOD, resumeClause, RESUMED_WORKTREE_NOTE, REVERSE_PHASE_GUIDE, SCAFFOLD_PHASE_GUIDE;
|
|
12836
|
+
var PIPELINES, WORK_PHASES, 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;
|
|
12640
12837
|
var init_prompt = __esm({
|
|
12641
12838
|
"../runner/src/prompt.ts"() {
|
|
12642
12839
|
"use strict";
|
|
@@ -12645,26 +12842,7 @@ var init_prompt = __esm({
|
|
|
12645
12842
|
init_verify_scope();
|
|
12646
12843
|
init_code_style();
|
|
12647
12844
|
init_goal_spec();
|
|
12648
|
-
PIPELINES =
|
|
12649
|
-
feature: ["Brainstorm", "Objective", "Spec", "Plan", "Execute"],
|
|
12650
|
-
qa: ["Audit", "Spec", "Plan", "Execute"],
|
|
12651
|
-
scaffold: ["Brainstorm", "Objective", "Doc index"],
|
|
12652
|
-
reverse: ["Scan", "Docs teknis", "Wawancara", "Konvensi & index", "Serah terima"],
|
|
12653
|
-
prd: ["Brainstorm", "PRD"],
|
|
12654
|
-
audit: ["Audit", "Laporan"],
|
|
12655
|
-
breakdown: ["Analisis", "Breakdown"],
|
|
12656
|
-
// SPEC-337 · ADR-0075 · audit lintas project: fase & stage-map identik audit-only, scope-nya
|
|
12657
|
-
// yang berbeda (project utama + tetangga ProjectLink).
|
|
12658
|
-
// SPEC-407 · ADR-0089 · backlog goal: tak ada fase perencanaan sama sekali. `Goal` = kerjakan,
|
|
12659
|
-
// `Verifikasi` = buktikan. Kedua nama unik lintas PIPELINES — syarat peta REACHED di server,
|
|
12660
|
-
// yang berkunci nama fase saja.
|
|
12661
|
-
goal: ["Goal", "Verifikasi"],
|
|
12662
|
-
// SPEC-825 · ADR-0123 · task remeh: SATU fase. Fase `Verifikasi` milik flow goal menghabiskan
|
|
12663
|
-
// satu giliran agen untuk membuktikan sesuatu yang diff-nya sendiri sudah membuktikan; untuk
|
|
12664
|
-
// ganti copy / bump konstanta / typo docs itu murni biaya. Nama `Kerjakan` unik lintas
|
|
12665
|
-
// PIPELINES — syarat peta REACHED server, yang berkunci nama fase saja.
|
|
12666
|
-
no_effort: ["Kerjakan"]
|
|
12667
|
-
};
|
|
12845
|
+
PIPELINES = FLOW_PHASES;
|
|
12668
12846
|
WORK_PHASES = ["Execute", "Goal", "Kerjakan"];
|
|
12669
12847
|
AUTONOMY_CLAUSE = 'Jalankan seluruh pipeline sampai tuntas tanpa berhenti di batas antar-fase. Checkpoint "review"/"approval"/"need review" milik skill superpowers BUKAN titik berhenti di sini \u2014 lanjut saja ke fase berikutnya. Berhenti HANYA saat butuh keputusan manusia sejati (percabangan yang mengubah bentuk kerja: data model, kontrak API, scope); saat itu tanyakan di terminal ini dan tunggu jawabannya. Selain itu, terus lanjut.';
|
|
12670
12848
|
AUTONOMY_CLAUSE_FULL = "Kamu berjalan TANPA pengawas \u2014 tak ada manusia yang menonton terminal ini untuk menjawab. Putuskan sendiri di SETIAP percabangan (termasuk yang mengubah bentuk kerja: data model, kontrak API, scope) berdasarkan Source of Truth dan penilaian terbaikmu; JANGAN berhenti bertanya. Tembus seluruh pipeline sampai stage `done`, lalu commit & push. Jangan menunggu review/persetujuan siapa pun \u2014 catat asumsi & keputusan penting di pesan commit agar bisa di-review pasca-fakta. Merge ke branch utama tetap dilakukan manusia, bukan kamu.";
|
|
@@ -12676,11 +12854,9 @@ Setiap kali sebuah fase selesai (atau kamu putuskan dilewati), append satu baris
|
|
|
12676
12854
|
return base2 + `
|
|
12677
12855
|
Execute BELUM selesai selama plan (\`${method.planDir}/**\`) masih punya task \`- [ ]\`: kerjakan SEMUA PR/task sampai tiap kotak jadi \`- [x]\` sebelum menulis \`Execute done\`. hanoman menahan backlog di \`executing\`, bukan \`done\`, selama masih ada \`- [ ]\`.`;
|
|
12678
12856
|
};
|
|
12679
|
-
skillInstruction = (phases, method
|
|
12680
|
-
const last = phases[phases.length - 1];
|
|
12857
|
+
skillInstruction = (flow, phases, method) => {
|
|
12681
12858
|
const lines2 = phases.map((p3) => {
|
|
12682
|
-
const
|
|
12683
|
-
const skills = withExit && p3 === last ? [.../* @__PURE__ */ new Set([...own, ...method.exitSkills])] : own;
|
|
12859
|
+
const skills = phaseSkillsFor(flow, p3, method);
|
|
12684
12860
|
return skills.length ? `- ${p3}: ${skills.join(", ")}` : "";
|
|
12685
12861
|
}).filter(Boolean);
|
|
12686
12862
|
return lines2.length ? `Skills ${method.label} WAJIB: sebelum mengerjakan fase di bawah, muat & ikuti skill-nya dengan mekanisme yang tersedia di agenmu \u2014 bila skill relevan tersedia, pakai.
|
|
@@ -12719,6 +12895,15 @@ ${lines2.join("\n")}` : "";
|
|
|
12719
12895
|
return `Backlog brief ini LANJUTAN dari audit ${fromAudit}. Worktree ini lahir dari branch audit itu, jadi dokumen audit sudah ada di ${doc}. BACA dokumen itu lebih dulu dan pakai sebagai bahan fase Brainstorm & Objective \u2014 temuannya sudah terbukti, jangan menginvestigasi ulang dari nol. Semua fase tetap dijalankan: dokumen audit memuat TEMUAN, bukan bentuk solusi, jadi perancangan fitur tetap pekerjaanmu.`;
|
|
12720
12896
|
return `Backlog qa ini LANJUTAN dari audit ${fromAudit}. Worktree ini lahir dari branch audit itu, jadi dokumen audit sudah ada di ${doc}. JANGAN mengulang investigasi fase Audit dari nol \u2014 baca dokumen audit itu sebagai temuan, tandai fase Audit dilewati (\`echo "Audit skipped" >> "$HANOMAN_PHASE_FILE"\`), lalu ambil keputusan pasca-Audit: perbaikan jelas & kecil \u2192 langsung Execute (tandai \`Spec skipped\` dan \`Plan skipped\` bila sesuai); selain itu Spec \u2192 Plan \u2192 Execute penuh.`;
|
|
12721
12897
|
};
|
|
12898
|
+
auditContinuationForOrchestrator = (flow, spec) => {
|
|
12899
|
+
if (flow !== "qa" && flow !== "feature") return "";
|
|
12900
|
+
const fromAudit = spec.payload && typeof spec.payload === "object" ? spec.payload.fromAudit : void 0;
|
|
12901
|
+
if (typeof fromAudit !== "string" || !fromAudit) return "";
|
|
12902
|
+
const doc = `internal/docs/research/audit-${fromAudit.toLowerCase()}-*.md`;
|
|
12903
|
+
if (flow === "feature")
|
|
12904
|
+
return `Backlog brief ini LANJUTAN dari audit ${fromAudit}. Worktree ini lahir dari branch audit itu, jadi dokumen audit sudah ada di ${doc}. Saat memanggil agen fase Brainstorm, cantumkan path itu di baris \`Artefak fase sebelumnya:\` blok serah-terima \u2014 JANGAN membacanya sendiri untuk merancang fitur ini. Saat memanggil agen fase Objective, baris itu biasanya sudah membawa path artefak Brainstorm (dokumen spec yang ditulis fase itu); tambahkan path dokumen audit di samping path itu, bukan menggantikannya. Temuannya sudah terbukti, tapi bentuk solusinya belum; itu tetap dikerjakan agen fasenya masing-masing, bukan olehmu.`;
|
|
12905
|
+
return `Backlog qa ini LANJUTAN dari audit ${fromAudit}. Temuannya sudah terbukti di ${doc} \u2014 JANGAN mendelegasikan ulang fase Audit. Sebelum fase lain: jalankan persis \`echo "Audit skipped" >> "$HANOMAN_PHASE_FILE"\`, verifikasi dengan \`tail -1 "$HANOMAN_PHASE_FILE"\` bahwa barisnya benar tertulis (gerbang yang sama seperti langkah 2), lalu cantumkan path dokumen audit itu di baris \`Artefak fase sebelumnya:\` saat memanggil agen fase Spec. Keputusan routing di sini menggantikan langkah 5 di atas \u2014 JANGAN menunggu frasa \`Rekomendasi fase: jalur-cepat\` dari agen Audit karena Audit tidak dijalankan pada kelanjutan ini. Kamu diizinkan mengambil SATU keputusan ROUTING dari isi dokumen audit itu SAJA, dibaca langsung dari kriterianya: temuan berconfidence tinggi dan perbaikan langsung berdiff kecil \u2192 tandai \`Spec skipped\` lalu \`Plan skipped\` (gerbang yang sama) lalu panggil agen fase Execute; selebihnya panggil Spec \u2192 Plan \u2192 Execute penuh lewat agen fasenya masing-masing. Kamu TIDAK menginvestigasi ulang maupun merancang perbaikannya sendiri \u2014 itu tetap pekerjaan agen fase.`;
|
|
12906
|
+
};
|
|
12722
12907
|
writesCode = (flow) => PIPELINES[flow].some((p3) => WORK_PHASES.includes(p3));
|
|
12723
12908
|
scopeClause = (flow, scope) => scope && writesCode(flow) ? verifyScopeClause(scope) : "";
|
|
12724
12909
|
codeStyleClause = (flow) => writesCode(flow) ? CODE_STYLE_CLAUSE : "";
|
|
@@ -12732,6 +12917,57 @@ BACA semuanya SEBELUM mengerjakan fase pertama \u2014 dokumen teks (.md/.txt/.lo
|
|
|
12732
12917
|
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.`;
|
|
12733
12918
|
};
|
|
12734
12919
|
PROJECT_METHOD = resolveMethod();
|
|
12920
|
+
specContext = (spec) => {
|
|
12921
|
+
const detail = spec.payload ? `
|
|
12922
|
+
Detail: ${JSON.stringify(spec.payload)}` : "";
|
|
12923
|
+
return `Backlog item ${spec.id} \xB7 sumber ${spec.source} \xB7 prioritas ${spec.priority}
|
|
12924
|
+
Judul: ${spec.title}
|
|
12925
|
+
Objective: ${spec.objective}${detail}`;
|
|
12926
|
+
};
|
|
12927
|
+
goalDetail = (spec) => {
|
|
12928
|
+
const g = readGoalPayload(spec.payload);
|
|
12929
|
+
return [
|
|
12930
|
+
`Goal: ${g?.goal ?? spec.objective}`,
|
|
12931
|
+
g?.done ? `Selesai bila: ${g.done}` : "",
|
|
12932
|
+
g?.constraints ? `Batasan: ${g.constraints}` : ""
|
|
12933
|
+
].filter(Boolean).join("\n");
|
|
12934
|
+
};
|
|
12935
|
+
goalBlock = (spec) => `Backlog item ${spec.id} \xB7 sumber ${spec.source} \xB7 prioritas ${spec.priority}
|
|
12936
|
+
Judul: ${spec.title}`;
|
|
12937
|
+
goalContext = (spec) => `${goalDetail(spec)}
|
|
12938
|
+
|
|
12939
|
+
${goalBlock(spec)}`;
|
|
12940
|
+
projectContext = (project) => `Project ${project.id} \xB7 ${project.name}
|
|
12941
|
+
Deskripsi: ${project.desc || "\u2014"}
|
|
12942
|
+
Stack: ${project.stack || "\u2014"}`;
|
|
12943
|
+
scaffoldContext = (project) => `Project ${project.id} \xB7 ${project.name}
|
|
12944
|
+
Ide awal: ${project.desc || "\u2014"}
|
|
12945
|
+
Stack: ${project.stack || "\u2014"}`;
|
|
12946
|
+
prdBriefBlock = (project, brief) => `Project ${project.id} \xB7 ${project.name}
|
|
12947
|
+
Brief \u2014 Judul: ${brief.title}
|
|
12948
|
+
Konteks: ${brief.context}
|
|
12949
|
+
Outcome: ${brief.outcome}${brief.constraints ? `
|
|
12950
|
+
Batasan: ${brief.constraints}` : ""}`;
|
|
12951
|
+
prdAuditBlock = (audit) => audit ? `=== DOKUMEN AUDIT ${audit.id} (${audit.path}) ===
|
|
12952
|
+
PRD ini adalah TINDAK LANJUT audit di bawah. Pakai temuannya sebagai bahan brainstorm \u2014 jangan menginvestigasi ulang, dan jangan pula menyalinnya mentah-mentah ke PRD.
|
|
12953
|
+
|
|
12954
|
+
${audit.content}` : "";
|
|
12955
|
+
prdContext = (project, brief, audit) => [prdBriefBlock(project, brief), prdAuditBlock(audit)].filter(Boolean).join("\n\n");
|
|
12956
|
+
breakdownContext = (project, prd) => `Project ${project.id} \xB7 ${project.name}
|
|
12957
|
+
=== PRD: ${prd.title} (${prd.path}) ===
|
|
12958
|
+
${prd.content}`;
|
|
12959
|
+
prdPhaseLines = (slug) => ({
|
|
12960
|
+
Brainstorm: `- Brainstorm: pandu PM secara interaktif. Ajukan SATU pertanyaan per giliran ke manusia di terminal ini, tunggu jawabannya, perdalam brief sampai jelas (masalah, pengguna, scope, metrik sukses). Jangan mengarang; topik yang PM belum jawab tandai sebagai open question.`,
|
|
12961
|
+
PRD: `- PRD: tulis dokumen ke \`docs/prd/${slug}.md\`. Awali dengan heading \`# <judul PRD>\`, lalu bagian: Ringkasan \xB7 Masalah & konteks \xB7 Persona/pengguna \xB7 Goals & non-goals \xB7 Scope (in/out) \xB7 User stories \xB7 Acceptance criteria (gaya EARS) \xB7 Metrik sukses \xB7 Open questions. Isi lengkap dan spesifik dari hasil brainstorm, bukan kerangka kosong.`
|
|
12962
|
+
});
|
|
12963
|
+
breakdownPhaseLines = (slug, title) => ({
|
|
12964
|
+
Analisis: `- Analisis: baca PRD (di bawah) sampai paham SELURUH scope in-PRD. Petakan pekerjaan menjadi unit-unit yang: (a) kecil & terukur \u2014 tiap unit tuntas dalam satu sesi; (b) non-overlapping \u2014 cakupan tak tumpang tindih; (c) TANPA cross-dependency \u2014 urutan bebas, bisa jalan bersamaan; (d) gabungannya MENUTUP seluruh scope PRD. Bila dua unit terpaksa berurutan, gabung jadi satu.`,
|
|
12965
|
+
Breakdown: `- Breakdown: tulis manifest ke \`docs/prd/${slug}.breakdown.md\`. Awali heading \`# Breakdown: ${title}\`, lalu prosa: ringkasan + untuk TIAP backlog satu paragraf (judul, cakupan, dan SATU kalimat kenapa aman-paralel / tak bergantung yang lain). Di AKHIR dokumen sertakan TEPAT SATU blok kode berpagar json berisi kontrak mesin PERSIS bentuk ini (tanpa komentar, priority \u2208 tinggi|sedang|rendah):
|
|
12966
|
+
\`\`\`json
|
|
12967
|
+
{ "items": [ { "title": "\u2026", "context": "\u2026", "outcome": "\u2026", "priority": "sedang" } ] }
|
|
12968
|
+
\`\`\`
|
|
12969
|
+
\`context\` = bagian PRD yang dicakup; \`outcome\` = kondisi selesai terukur; \`title\` ringkas. Minimal 2 item bila PRD memang kompleks; bila PRD ternyata sekecil 1 unit, katakan itu di prosa dan tetap tulis 1 item.`
|
|
12970
|
+
});
|
|
12735
12971
|
resumeClause = (r, branchTo, planDir, hasPlan = true) => {
|
|
12736
12972
|
const fase = r.recorded.length ? `Fase yang SUDAH tercatat di $HANOMAN_PHASE_FILE: ${r.recorded.join(" \xB7 ")}. JANGAN mengulang fase itu dan JANGAN menulis ulang barisnya.` : "Belum ada fase yang tercatat di $HANOMAN_PHASE_FILE \u2014 worktree ini sendiri yang jadi alasan melanjutkan.";
|
|
12737
12973
|
const lanjut = r.next ? `Lanjutkan dari fase: ${r.next}.` : hasPlan ? `Semua fase sudah tercatat. Periksa apakah plan di \`${planDir}/**\` masih menyisakan task \`- [ ]\` dan selesaikan sisanya; bila sudah bersih, tinggal commit & push.` : "Semua fase sudah tercatat. Buktikan sekali lagi goal-nya benar-benar tercapai, lalu commit & push.";
|
|
@@ -12919,7 +13155,7 @@ var init_settings2 = __esm({
|
|
|
12919
13155
|
'${HANOMAN_EVENT_HOST:+-H "host: $HANOMAN_EVENT_HOST"}',
|
|
12920
13156
|
"--data-binary @- >/dev/null 2>&1; fi; exit 0"
|
|
12921
13157
|
].join(" ");
|
|
12922
|
-
guardSettings = (decisionFile, goal, eventHook) => {
|
|
13158
|
+
guardSettings = (decisionFile, goal, eventHook, subagentStatusLine) => {
|
|
12923
13159
|
const hooks2 = {};
|
|
12924
13160
|
if (decisionFile) {
|
|
12925
13161
|
const f = `'${decisionFile.split("'").join("'\\''")}'`;
|
|
@@ -12938,7 +13174,10 @@ var init_settings2 = __esm({
|
|
|
12938
13174
|
hooks2.SubagentStop = [{ hooks: [{ type: "command", command: EVENT_HOOK_COMMAND }] }];
|
|
12939
13175
|
}
|
|
12940
13176
|
if (goal) hooks2.Stop = [{ hooks: [{ type: "prompt", prompt: goal }] }];
|
|
12941
|
-
return {
|
|
13177
|
+
return {
|
|
13178
|
+
hooks: hooks2,
|
|
13179
|
+
...subagentStatusLine ? { subagentStatusLine: { type: "command", command: subagentStatusLine } } : {}
|
|
13180
|
+
};
|
|
12942
13181
|
};
|
|
12943
13182
|
}
|
|
12944
13183
|
});
|
|
@@ -13100,7 +13339,7 @@ function agentFlags(o) {
|
|
|
13100
13339
|
...o.effort ? ["--effort", o.effort] : [],
|
|
13101
13340
|
"--dangerously-skip-permissions",
|
|
13102
13341
|
"--settings",
|
|
13103
|
-
JSON.stringify(guardSettings(o.decisionFile, o.goal, o.eventHook))
|
|
13342
|
+
JSON.stringify(guardSettings(o.decisionFile, o.goal, o.eventHook, o.subagentStatusLine))
|
|
13104
13343
|
];
|
|
13105
13344
|
}
|
|
13106
13345
|
var init_agent_cli = __esm({
|
|
@@ -13149,6 +13388,15 @@ function renderAgentsJson(defs, options2 = {}) {
|
|
|
13149
13388
|
if (defs.length === 0) return "";
|
|
13150
13389
|
const out4 = {};
|
|
13151
13390
|
for (const d of defs) {
|
|
13391
|
+
if (d.kind === "phase") {
|
|
13392
|
+
out4[d.name] = {
|
|
13393
|
+
description: d.description,
|
|
13394
|
+
prompt: d.instructions,
|
|
13395
|
+
...d.model ? { model: d.model } : {},
|
|
13396
|
+
...d.effort ? { effort: d.effort } : {}
|
|
13397
|
+
};
|
|
13398
|
+
continue;
|
|
13399
|
+
}
|
|
13152
13400
|
const resolvedTools = resolveTools({ tools: d.tools, mentions: d.mentions });
|
|
13153
13401
|
const readOnly = d.workspacePolicy === "read-only";
|
|
13154
13402
|
out4[d.name] = {
|
|
@@ -13176,7 +13424,7 @@ function renderAgentsJson(defs, options2 = {}) {
|
|
|
13176
13424
|
return JSON.stringify(out4);
|
|
13177
13425
|
}
|
|
13178
13426
|
function agentDelegationClause(defs, runtime = "claude") {
|
|
13179
|
-
if (defs.
|
|
13427
|
+
if (defs.every((def2) => def2.kind === "phase")) return "";
|
|
13180
13428
|
return [
|
|
13181
13429
|
"",
|
|
13182
13430
|
"",
|
|
@@ -13268,10 +13516,6 @@ var init_runtime_profile = __esm({
|
|
|
13268
13516
|
// ../runner/src/codex-agent-config.ts
|
|
13269
13517
|
import { chmodSync, writeFileSync } from "node:fs";
|
|
13270
13518
|
import { join } from "node:path";
|
|
13271
|
-
function codexNativeAgentsSupported(version) {
|
|
13272
|
-
const parsed = version ? /(\d+)\.(\d+)\.(\d+)/.exec(version)?.[0] : null;
|
|
13273
|
-
return parsed ? cmpVersion(parsed, CODEX_NATIVE_AGENTS_MIN_CLIENT) >= 0 : false;
|
|
13274
|
-
}
|
|
13275
13519
|
function codexNativeVersionProbe(env, codexBin3 = env.HANOMAN_CODEX_BIN ?? "codex") {
|
|
13276
13520
|
const sandbox = env.HANOMAN_SESSION_SANDBOX ?? (resolveHardening(env) ? "required" : "off");
|
|
13277
13521
|
if (sandbox !== "podman") return { bin: codexBin3, args: ["--version"] };
|
|
@@ -13296,7 +13540,7 @@ function renderCodexAgentToml(def2, roster, options2 = {}) {
|
|
|
13296
13540
|
const lines2 = [
|
|
13297
13541
|
`name = ${tomlString(def2.name)}`,
|
|
13298
13542
|
`description = ${tomlString(def2.description)}`,
|
|
13299
|
-
`developer_instructions = ${tomlString(agentPromptOf(def2, roster, "codex") + (options2.promptSuffix ?? ""))}`,
|
|
13543
|
+
`developer_instructions = ${tomlString(def2.kind === "phase" ? def2.instructions : agentPromptOf(def2, roster, "codex") + (options2.promptSuffix ?? ""))}`,
|
|
13300
13544
|
...def2.model ? [`model = ${tomlString(def2.model)}`] : [],
|
|
13301
13545
|
...def2.effort ? [`model_reasoning_effort = ${tomlString(def2.effort)}`] : [],
|
|
13302
13546
|
...def2.workspacePolicy === "read-only" ? ['sandbox_mode = "read-only"'] : []
|
|
@@ -13365,6 +13609,7 @@ function materializeCodexAgents(defs, tempDir, options2 = {}) {
|
|
|
13365
13609
|
"-c",
|
|
13366
13610
|
"agents.max_concurrent_threads_per_session=3"
|
|
13367
13611
|
];
|
|
13612
|
+
if (options2.maxDepth) args.push("-c", `agents.max_depth=${options2.maxDepth}`);
|
|
13368
13613
|
for (const { def: def2, path } of successful) {
|
|
13369
13614
|
const key = `agents.${tomlKey(def2.name)}`;
|
|
13370
13615
|
args.push("-c", `${key}.description=${tomlString(def2.description)}`);
|
|
@@ -13379,14 +13624,13 @@ function materializeCodexAgents(defs, tempDir, options2 = {}) {
|
|
|
13379
13624
|
liveDefs
|
|
13380
13625
|
};
|
|
13381
13626
|
}
|
|
13382
|
-
var
|
|
13627
|
+
var shellQuote, tomlString, tomlKey, safeFilename;
|
|
13383
13628
|
var init_codex_agent_config = __esm({
|
|
13384
13629
|
"../runner/src/codex-agent-config.ts"() {
|
|
13385
13630
|
"use strict";
|
|
13386
13631
|
init_src();
|
|
13387
13632
|
init_custom_agents();
|
|
13388
13633
|
init_runtime_profile();
|
|
13389
|
-
CODEX_NATIVE_AGENTS_MIN_CLIENT = "0.151.0";
|
|
13390
13634
|
shellQuote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
|
|
13391
13635
|
tomlString = (value) => JSON.stringify(value);
|
|
13392
13636
|
tomlKey = (value) => JSON.stringify(value);
|
|
@@ -13547,7 +13791,7 @@ function agentDefinitionHash(def2, roster, runtime, inherited = {}) {
|
|
|
13547
13791
|
const options2 = { readOnlyHookCommand: "<hanoman-read-only-hook>", promptSuffix: inherited.promptSuffix };
|
|
13548
13792
|
const liveRoster = [def2, ...roster.filter((entry) => entry.name !== def2.name)];
|
|
13549
13793
|
const native = runtime === "codex" ? renderCodexAgentToml(def2, liveRoster, options2) : JSON.parse(renderAgentsJson(liveRoster, options2))[def2.name];
|
|
13550
|
-
if (typeof native !== "string") native.tools.sort();
|
|
13794
|
+
if (typeof native !== "string" && native.tools) native.tools.sort();
|
|
13551
13795
|
return createHash("sha256").update(JSON.stringify({
|
|
13552
13796
|
version: 1,
|
|
13553
13797
|
runtime,
|
|
@@ -13567,6 +13811,112 @@ var init_agent_definition = __esm({
|
|
|
13567
13811
|
}
|
|
13568
13812
|
});
|
|
13569
13813
|
|
|
13814
|
+
// ../runner/src/phase-agents.ts
|
|
13815
|
+
function fromAuditOf(payload) {
|
|
13816
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) return void 0;
|
|
13817
|
+
const value = payload.fromAudit;
|
|
13818
|
+
return typeof value === "string" && value ? value : void 0;
|
|
13819
|
+
}
|
|
13820
|
+
function backlogGuide(flow, phase, ctx) {
|
|
13821
|
+
const m = ctx.method;
|
|
13822
|
+
switch (phase) {
|
|
13823
|
+
case "Brainstorm":
|
|
13824
|
+
return `- Brainstorm: gali konteks, alternatif, dan keputusan untuk backlog ini dari Source of Truth dan kode. Tulis hasilnya sebagai bagian \`## Konteks & keputusan\` di dokumen spec baru di \`${m.specDir}/\` (nama berkas \`<YYYY-MM-DD>-<spec-id>-<slug>-design.md\`) dan laporkan path-nya.` + (ctx.fromAudit ? ` Backlog ini LANJUTAN audit ${ctx.fromAudit}: baca \`internal/docs/research/audit-${ctx.fromAudit.toLowerCase()}-*.md\` lebih dulu dan pakai temuannya sebagai bahan \u2014 jangan menginvestigasi ulang dari nol.` : "");
|
|
13825
|
+
case "Objective":
|
|
13826
|
+
return "- Objective: baca dokumen spec dari fase Brainstorm (path-nya di serah-terima), lalu tambahkan bagian `## Objective` berisi SATU objective terukur beserta kriteria suksesnya.";
|
|
13827
|
+
case "Spec":
|
|
13828
|
+
return flow === "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.";
|
|
13829
|
+
case "Plan":
|
|
13830
|
+
return `- Plan: tulis plan implementasi berkotak \`- [ ]\` di \`${m.planDir}/\` untuk backlog ini dari dokumen spec-nya (path di serah-terima).`;
|
|
13831
|
+
case "Execute":
|
|
13832
|
+
return `- Execute: kerjakan plan di \`${m.planDir}/**\` untuk 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 \`- [ ]\`.`;
|
|
13833
|
+
case "Audit":
|
|
13834
|
+
return flow === "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`.";
|
|
13835
|
+
case "Laporan":
|
|
13836
|
+
return "- Laporan: tulis DOKUMEN AUDIT ke Source of Truth `internal/docs/research/audit-<spec-id>-<slug>.md` (ikuti konvensi audit yang ada), tautkan di `internal/docs/README.md`, memuat: keluhan/pertanyaan, temuan (dengan bukti/log), apakah issue terdefinisi baik, dan rekomendasi tindak lanjut. Commit dokumen itu. Tak ada kode fitur.\n\n" + ESCALATION_CONTRACT;
|
|
13837
|
+
case "Goal":
|
|
13838
|
+
return "- Goal: kerjakan goal di blok KONTEKS sampai tercapai. TIDAK ada design doc, plan berkotak, maupun backlog baru.";
|
|
13839
|
+
case "Verifikasi":
|
|
13840
|
+
return "- Verifikasi: bukan formalitas \u2014 jalankan perintah yang membuktikan goal-nya tercapai (test/typecheck/benchmark/perintah yang relevan) dan baca outputnya. Klaim tanpa output bukan bukti.";
|
|
13841
|
+
case "Kerjakan":
|
|
13842
|
+
return "- Kerjakan: SATU pekerjaan remeh. Langsung kerjakan dan buktikan seperlunya di fase yang sama. Jangan menulis design doc, plan berkotak, backlog baru, atau fase tambahan.";
|
|
13843
|
+
default:
|
|
13844
|
+
return "";
|
|
13845
|
+
}
|
|
13846
|
+
}
|
|
13847
|
+
function projectGuide(flow, phase, ctx) {
|
|
13848
|
+
if (flow === "reverse") return guideLine(REVERSE_PHASE_GUIDE, phase);
|
|
13849
|
+
if (flow === "scaffold") return guideLine(SCAFFOLD_PHASE_GUIDE, phase);
|
|
13850
|
+
const slug = ctx.prd?.slug ?? flow;
|
|
13851
|
+
const lines2 = flow === "prd" ? prdPhaseLines(slug) : breakdownPhaseLines(slug, ctx.prd?.title ?? slug);
|
|
13852
|
+
return lines2[phase] ?? "";
|
|
13853
|
+
}
|
|
13854
|
+
function phaseAgentInstructions(entry, index, plan, ctx) {
|
|
13855
|
+
const { flow, method } = ctx;
|
|
13856
|
+
const phase = entry.phase;
|
|
13857
|
+
const guide = PROJECT_FLOWS.has(flow) ? projectGuide(flow, phase, ctx) : backlogGuide(flow, phase, ctx);
|
|
13858
|
+
const skills = phaseSkillsFor(flow, phase, method);
|
|
13859
|
+
const work = WORK_PHASES.includes(phase);
|
|
13860
|
+
return [
|
|
13861
|
+
`Kamu agen fase "${phase}" (fase ${index + 1}/${plan.phases.length}) flow ${flow} hanoman. Ikuti internal/docs sebagai Source of Truth; perbarui docs yang tersentuh dan link-nya di index, dalam commit yang sama.`,
|
|
13862
|
+
guide,
|
|
13863
|
+
DOC_WRITING_PHASES.has(phase) ? `=== STANDAR DOCS ===
|
|
13864
|
+
${REVERSE_STANDARD}` : "",
|
|
13865
|
+
skills.length ? `Skills ${method.label} WAJIB untuk fase ini \u2014 muat & ikuti dengan mekanisme yang tersedia di agenmu: ` + skills.join(", ") : "",
|
|
13866
|
+
work ? scopeClause(flow, ctx.verifyScope) : "",
|
|
13867
|
+
work ? codeStyleClause(flow) : "",
|
|
13868
|
+
methodClause(method),
|
|
13869
|
+
ATTACHMENT_NOTE,
|
|
13870
|
+
PHASE_AGENT_AUTONOMY,
|
|
13871
|
+
PHASE_AGENT_RULES,
|
|
13872
|
+
PHASE_AGENT_REPORT,
|
|
13873
|
+
`=== KONTEKS ===
|
|
13874
|
+
${ctx.context}`
|
|
13875
|
+
].filter(Boolean).join("\n\n");
|
|
13876
|
+
}
|
|
13877
|
+
function buildPhaseAgents(plan, ctx) {
|
|
13878
|
+
return plan.phases.map((entry, index) => ({
|
|
13879
|
+
kind: "phase",
|
|
13880
|
+
phase: entry.phase,
|
|
13881
|
+
name: entry.agentName,
|
|
13882
|
+
description: `Fase ${entry.phase} flow ${plan.flow} hanoman \u2014 hanya dipanggil orchestrator sesi ini.`,
|
|
13883
|
+
instructions: phaseAgentInstructions(entry, index, plan, ctx),
|
|
13884
|
+
tools: null,
|
|
13885
|
+
model: entry.model,
|
|
13886
|
+
effort: entry.effort,
|
|
13887
|
+
mentions: []
|
|
13888
|
+
}));
|
|
13889
|
+
}
|
|
13890
|
+
var PROJECT_FLOWS, DOC_WRITING_PHASES, PHASE_AGENT_AUTONOMY, PHASE_AGENT_RULES, PHASE_AGENT_REPORT, ATTACHMENT_NOTE;
|
|
13891
|
+
var init_phase_agents = __esm({
|
|
13892
|
+
"../runner/src/phase-agents.ts"() {
|
|
13893
|
+
"use strict";
|
|
13894
|
+
init_src();
|
|
13895
|
+
init_prompt();
|
|
13896
|
+
init_reverse_standard();
|
|
13897
|
+
PROJECT_FLOWS = /* @__PURE__ */ new Set(["reverse", "scaffold", "prd", "breakdown"]);
|
|
13898
|
+
DOC_WRITING_PHASES = /* @__PURE__ */ new Set(["Docs teknis", "Konvensi & index", "Doc index"]);
|
|
13899
|
+
PHASE_AGENT_AUTONOMY = 'Kamu dipanggil orchestrator sesi hanoman, bukan manusia. Checkpoint "review"/"approval" milik skill BUKAN titik berhenti \u2014 lanjut sampai fase ini tuntas. JANGAN bertanya ke manusia secara langsung: bila butuh keputusan yang mengubah bentuk kerja (data model, kontrak API, scope), atau panduan fase menyuruhmu bertanya ke manusia di terminal, tulis SATU pertanyaan di bagian `Pertanyaan untuk manusia:` laporanmu lalu berhenti. Jawabannya datang sebagai pesan susulan ke agen yang sama \u2014 lanjutkan dari sana dengan konteks yang sudah kamu punya.';
|
|
13900
|
+
PHASE_AGENT_RULES = [
|
|
13901
|
+
"Batas peran fase:",
|
|
13902
|
+
"- JANGAN menulis `$HANOMAN_PHASE_FILE` \u2014 orchestrator satu-satunya penulis marker fase.",
|
|
13903
|
+
"- JANGAN `git push` \u2014 push milik orchestrator. Commit artefak fasemu sendiri sebelum melapor.",
|
|
13904
|
+
`- JANGAN memanggil agen berawalan \`${PHASE_AGENT_PREFIX}\`. Custom agent lain boleh dipanggil bila relevan.`,
|
|
13905
|
+
"- Kerjakan HANYA fase ini; fase lain dikerjakan agen fasenya sendiri."
|
|
13906
|
+
].join("\n");
|
|
13907
|
+
PHASE_AGENT_REPORT = [
|
|
13908
|
+
"Kontrak laporan (wajib, di akhir):",
|
|
13909
|
+
"- Baris pertama: `Status: selesai | sebagian | terhalang`.",
|
|
13910
|
+
"- `Artefak:` path berkas yang kamu tulis/ubah.",
|
|
13911
|
+
"- `Bukti:` perintah yang dijalankan beserta hasil yang benar-benar kamu baca.",
|
|
13912
|
+
"- `Pertanyaan untuk manusia:` (opsional) SATU pertanyaan; bila ada, berhenti di situ.",
|
|
13913
|
+
"- `Rekomendasi fase:` (opsional) mis. `jalur-cepat` sesudah Audit qa.",
|
|
13914
|
+
"Klaim tanpa bukti bukan bukti. Maksimal 1200 kata."
|
|
13915
|
+
].join("\n");
|
|
13916
|
+
ATTACHMENT_NOTE = "Bila serah-terima menyebut manifest lampiran (`INDEX.md`), baca manifest itu dan lampiran yang relevan di awal fase.";
|
|
13917
|
+
}
|
|
13918
|
+
});
|
|
13919
|
+
|
|
13570
13920
|
// ../runner/src/custom-agent-eval-evidence.ts
|
|
13571
13921
|
var init_custom_agent_eval_evidence = __esm({
|
|
13572
13922
|
"../runner/src/custom-agent-eval-evidence.ts"() {
|
|
@@ -14093,6 +14443,50 @@ var init_sandbox_probe = __esm({
|
|
|
14093
14443
|
}
|
|
14094
14444
|
});
|
|
14095
14445
|
|
|
14446
|
+
// ../runner/src/subagent-statusline.ts
|
|
14447
|
+
import { writeFileSync as writeFileSync4 } from "node:fs";
|
|
14448
|
+
import { join as join7 } from "node:path";
|
|
14449
|
+
function writeSubagentStatusline(dir2) {
|
|
14450
|
+
const script2 = join7(dir2, "subagent-statusline.cjs");
|
|
14451
|
+
const labels = join7(dir2, "subagent-models.json");
|
|
14452
|
+
writeFileSync4(script2, SUBAGENT_STATUSLINE_SCRIPT, { mode: 384 });
|
|
14453
|
+
writeFileSync4(labels, JSON.stringify(Object.fromEntries(MODELS.map((m) => [m.id, m.label]))), { mode: 384 });
|
|
14454
|
+
return `node ${JSON.stringify(script2)} ${JSON.stringify(labels)}`;
|
|
14455
|
+
}
|
|
14456
|
+
var SUBAGENT_STATUSLINE_SCRIPT;
|
|
14457
|
+
var init_subagent_statusline = __esm({
|
|
14458
|
+
"../runner/src/subagent-statusline.ts"() {
|
|
14459
|
+
"use strict";
|
|
14460
|
+
init_src();
|
|
14461
|
+
SUBAGENT_STATUSLINE_SCRIPT = [
|
|
14462
|
+
'const fs = require("node:fs");',
|
|
14463
|
+
"const labelsPath = process.argv[2];",
|
|
14464
|
+
'let raw = "";',
|
|
14465
|
+
'process.stdin.setEncoding("utf8");',
|
|
14466
|
+
'process.stdin.on("data", (c) => { raw += c; });',
|
|
14467
|
+
'process.stdin.on("end", () => {',
|
|
14468
|
+
" try {",
|
|
14469
|
+
' const labels = JSON.parse(fs.readFileSync(labelsPath, "utf8"));',
|
|
14470
|
+
" const tasks = JSON.parse(raw).tasks || [];",
|
|
14471
|
+
" const rows = [];",
|
|
14472
|
+
" for (const t of tasks) {",
|
|
14473
|
+
' if (!t || typeof t.id !== "string" || typeof t.model !== "string") continue;',
|
|
14474
|
+
' const parts = [t.label || t.description || "subagent", labels[t.model] || t.model];',
|
|
14475
|
+
' if (typeof t.effort === "string") parts.push(t.effort);',
|
|
14476
|
+
' if (typeof t.startTime === "number") {',
|
|
14477
|
+
" const s = Math.max(0, Math.round((Date.now() - t.startTime) / 1000));",
|
|
14478
|
+
' parts.push(s >= 60 ? Math.floor(s / 60) + "m" + String(s % 60).padStart(2, "0") + "s" : s + "s");',
|
|
14479
|
+
" }",
|
|
14480
|
+
' if (typeof t.tokenCount === "number" && t.tokenCount > 0) parts.push(Math.round(t.tokenCount / 1000) + "k tok");',
|
|
14481
|
+
' rows.push(JSON.stringify({ id: t.id, content: parts.join(" \xB7 ") }));',
|
|
14482
|
+
" }",
|
|
14483
|
+
' if (rows.length) process.stdout.write(rows.join("\\n") + "\\n");',
|
|
14484
|
+
" } catch {}",
|
|
14485
|
+
"});"
|
|
14486
|
+
].join("\n");
|
|
14487
|
+
}
|
|
14488
|
+
});
|
|
14489
|
+
|
|
14096
14490
|
// ../runner/src/index.ts
|
|
14097
14491
|
var init_src2 = __esm({
|
|
14098
14492
|
"../runner/src/index.ts"() {
|
|
@@ -14108,6 +14502,7 @@ var init_src2 = __esm({
|
|
|
14108
14502
|
init_agent_cli();
|
|
14109
14503
|
init_custom_agents();
|
|
14110
14504
|
init_agent_definition();
|
|
14505
|
+
init_phase_agents();
|
|
14111
14506
|
init_agent_readonly();
|
|
14112
14507
|
init_codex_agent_config();
|
|
14113
14508
|
init_custom_agent_eval();
|
|
@@ -14120,6 +14515,7 @@ var init_src2 = __esm({
|
|
|
14120
14515
|
init_config_env();
|
|
14121
14516
|
init_runtime_profile();
|
|
14122
14517
|
init_sandbox_probe();
|
|
14518
|
+
init_subagent_statusline();
|
|
14123
14519
|
}
|
|
14124
14520
|
});
|
|
14125
14521
|
|
|
@@ -14318,6 +14714,44 @@ function readPhases(file, flow) {
|
|
|
14318
14714
|
return { name: name2, state: "active" };
|
|
14319
14715
|
});
|
|
14320
14716
|
}
|
|
14717
|
+
function enrichPhases(phases, roster, invocations, doneSeenAt, now, bornAt2, doneAtBirth = /* @__PURE__ */ new Set()) {
|
|
14718
|
+
return phases.map((p3) => {
|
|
14719
|
+
const r = roster.find((entry) => entry.phase === p3.name);
|
|
14720
|
+
if (!r) return p3;
|
|
14721
|
+
const all = invocations.filter((i) => i.phase === p3.name);
|
|
14722
|
+
const mine = all.filter((i) => bornAt2 === 0 || Date.parse(i.startedAt) >= bornAt2).sort((a, b) => a.startedAt.localeCompare(b.startedAt));
|
|
14723
|
+
const last = mine[mine.length - 1];
|
|
14724
|
+
const bornDone = doneAtBirth.has(p3.name);
|
|
14725
|
+
const seen2 = doneSeenAt.get(p3.name);
|
|
14726
|
+
const evidence = mine.length > 0 ? "ok" : bornDone ? all.length > 0 ? "ok" : "pending" : p3.state === "done" && seen2 !== void 0 && now - seen2 >= PHASE_EVIDENCE_GRACE_MS ? "missing" : "pending";
|
|
14727
|
+
return {
|
|
14728
|
+
...p3,
|
|
14729
|
+
agent: {
|
|
14730
|
+
name: r.name,
|
|
14731
|
+
...r.model ? { model: r.model } : {},
|
|
14732
|
+
...r.effort ? { effort: r.effort } : {},
|
|
14733
|
+
attempts: new Set(mine.map((i) => i.runtimeInvocationId)).size,
|
|
14734
|
+
...last ? {
|
|
14735
|
+
status: last.status,
|
|
14736
|
+
startedAt: last.startedAt,
|
|
14737
|
+
durationMs: last.durationMs,
|
|
14738
|
+
inputTokens: last.inputTokens,
|
|
14739
|
+
outputTokens: last.outputTokens,
|
|
14740
|
+
cachedTokens: last.cachedTokens,
|
|
14741
|
+
resultExcerpt: last.resultExcerpt
|
|
14742
|
+
} : {},
|
|
14743
|
+
evidence
|
|
14744
|
+
}
|
|
14745
|
+
};
|
|
14746
|
+
});
|
|
14747
|
+
}
|
|
14748
|
+
function trackDoneSeen(phases, doneSeenAt, now) {
|
|
14749
|
+
const byName = new Map(phases.map((p3) => [p3.name, p3.state]));
|
|
14750
|
+
for (const name2 of [...doneSeenAt.keys()])
|
|
14751
|
+
if (byName.get(name2) !== "done") doneSeenAt.delete(name2);
|
|
14752
|
+
for (const p3 of phases)
|
|
14753
|
+
if (p3.state === "done" && !doneSeenAt.has(p3.name)) doneSeenAt.set(p3.name, now);
|
|
14754
|
+
}
|
|
14321
14755
|
function stageFor(phases) {
|
|
14322
14756
|
let best = -1;
|
|
14323
14757
|
for (const p3 of phases) {
|
|
@@ -14397,9 +14831,9 @@ var init_session_id = __esm({
|
|
|
14397
14831
|
});
|
|
14398
14832
|
|
|
14399
14833
|
// src/services/secret-box.ts
|
|
14400
|
-
import { chmodSync as chmodSync5, existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as
|
|
14834
|
+
import { chmodSync as chmodSync5, existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "node:fs";
|
|
14401
14835
|
import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
|
|
14402
|
-
import { dirname as dirname6, join as
|
|
14836
|
+
import { dirname as dirname6, join as join8 } from "node:path";
|
|
14403
14837
|
function isEncrypted(value) {
|
|
14404
14838
|
return value.startsWith(ENC_PREFIX);
|
|
14405
14839
|
}
|
|
@@ -14431,7 +14865,7 @@ function fromEnv(raw) {
|
|
|
14431
14865
|
return null;
|
|
14432
14866
|
}
|
|
14433
14867
|
function secretKeyPath() {
|
|
14434
|
-
return
|
|
14868
|
+
return join8(resolveHome(), "secret.key");
|
|
14435
14869
|
}
|
|
14436
14870
|
function secretKey() {
|
|
14437
14871
|
if (cached) return cached;
|
|
@@ -14451,7 +14885,7 @@ function secretKey() {
|
|
|
14451
14885
|
}
|
|
14452
14886
|
const key = randomBytes(KEY_BYTES);
|
|
14453
14887
|
mkdirSync4(dirname6(path), { recursive: true });
|
|
14454
|
-
|
|
14888
|
+
writeFileSync5(path, key.toString("base64url"), { mode: 384 });
|
|
14455
14889
|
chmodSync5(path, 384);
|
|
14456
14890
|
cached = key;
|
|
14457
14891
|
return key;
|
|
@@ -14733,7 +15167,7 @@ var init_safe_outbound_request = __esm({
|
|
|
14733
15167
|
// src/services/uploads.ts
|
|
14734
15168
|
import { randomUUID } from "node:crypto";
|
|
14735
15169
|
import { mkdir, writeFile, readFile as readFile2, unlink, rm } from "node:fs/promises";
|
|
14736
|
-
import { join as
|
|
15170
|
+
import { join as join9, resolve as resolve8 } from "node:path";
|
|
14737
15171
|
function extFor(mimeType) {
|
|
14738
15172
|
return EXT[mimeType] ?? ".bin";
|
|
14739
15173
|
}
|
|
@@ -14742,12 +15176,12 @@ function uploadDir() {
|
|
|
14742
15176
|
}
|
|
14743
15177
|
function sessionUploadDir(sessionId2) {
|
|
14744
15178
|
if (!SESSION_ID.test(sessionId2)) throw new Error(`sessionId tak sah: ${sessionId2}`);
|
|
14745
|
-
return
|
|
15179
|
+
return join9(uploadDir(), "terminal", sessionId2);
|
|
14746
15180
|
}
|
|
14747
15181
|
async function saveSessionUpload(sessionId2, buf, mimeType) {
|
|
14748
15182
|
const dir2 = sessionUploadDir(sessionId2);
|
|
14749
15183
|
await mkdir(dir2, { recursive: true, mode: 448 });
|
|
14750
|
-
const path =
|
|
15184
|
+
const path = join9(dir2, `${randomUUID()}${extFor(mimeType)}`);
|
|
14751
15185
|
await writeFile(path, buf, { mode: 384 });
|
|
14752
15186
|
return { path, size: buf.length };
|
|
14753
15187
|
}
|
|
@@ -14763,11 +15197,11 @@ async function dropSessionUploads(sessionId2) {
|
|
|
14763
15197
|
}
|
|
14764
15198
|
async function readUpload(storageKey) {
|
|
14765
15199
|
const safe = storageKey.replace(/[/\\]/g, "");
|
|
14766
|
-
return readFile2(
|
|
15200
|
+
return readFile2(join9(uploadDir(), safe));
|
|
14767
15201
|
}
|
|
14768
15202
|
async function readUploadOrFetch(storageKey) {
|
|
14769
15203
|
const safe = storageKey.replace(/[/\\]/g, "");
|
|
14770
|
-
const target2 =
|
|
15204
|
+
const target2 = join9(uploadDir(), safe);
|
|
14771
15205
|
try {
|
|
14772
15206
|
return await readFile2(target2);
|
|
14773
15207
|
} catch {
|
|
@@ -14794,7 +15228,7 @@ async function readUploadOrFetch(storageKey) {
|
|
|
14794
15228
|
}
|
|
14795
15229
|
async function deleteUpload(storageKey) {
|
|
14796
15230
|
const safe = storageKey.replace(/[/\\]/g, "");
|
|
14797
|
-
await unlink(
|
|
15231
|
+
await unlink(join9(uploadDir(), safe)).catch(() => {
|
|
14798
15232
|
});
|
|
14799
15233
|
}
|
|
14800
15234
|
var EXT, SESSION_ID;
|
|
@@ -15224,14 +15658,14 @@ var init_session_sandbox = __esm({
|
|
|
15224
15658
|
});
|
|
15225
15659
|
|
|
15226
15660
|
// src/services/session-event-spool.ts
|
|
15227
|
-
import { join as
|
|
15661
|
+
import { join as join10 } from "node:path";
|
|
15228
15662
|
var sessionEventSpoolRoot, sessionEventDir;
|
|
15229
15663
|
var init_session_event_spool = __esm({
|
|
15230
15664
|
"src/services/session-event-spool.ts"() {
|
|
15231
15665
|
"use strict";
|
|
15232
15666
|
init_src2();
|
|
15233
|
-
sessionEventSpoolRoot = (env = process.env) =>
|
|
15234
|
-
sessionEventDir = (sessionId2, env = process.env) =>
|
|
15667
|
+
sessionEventSpoolRoot = (env = process.env) => join10(resolveHome(env), "session-events");
|
|
15668
|
+
sessionEventDir = (sessionId2, env = process.env) => join10(sessionEventSpoolRoot(env), sessionId2);
|
|
15235
15669
|
}
|
|
15236
15670
|
});
|
|
15237
15671
|
|
|
@@ -15240,13 +15674,28 @@ import { spawn } from "node-pty";
|
|
|
15240
15674
|
import { execFile, execFileSync as execFileSync2 } from "node:child_process";
|
|
15241
15675
|
import { createRequire } from "node:module";
|
|
15242
15676
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
15243
|
-
import { chmodSync as chmodSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync6, rmSync as rmSync2, statSync as statSync2, writeFileSync as
|
|
15677
|
+
import { chmodSync as chmodSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync6, rmSync as rmSync2, statSync as statSync2, writeFileSync as writeFileSync6 } from "node:fs";
|
|
15244
15678
|
import { dirname as dirname7 } from "node:path";
|
|
15245
15679
|
import { tmpdir } from "node:os";
|
|
15680
|
+
function phaseView(p3, a) {
|
|
15681
|
+
const phases = readPhases(p3.phaseFile, p3.flow);
|
|
15682
|
+
const now = Date.now();
|
|
15683
|
+
trackDoneSeen(phases, a.doneSeenAt, now);
|
|
15684
|
+
const roster = (p3.agentRoster ?? []).flatMap((r) => r.phase ? [{ name: r.name, phase: r.phase, model: r.model, effort: r.effort }] : []);
|
|
15685
|
+
return roster.length ? enrichPhases(
|
|
15686
|
+
phases,
|
|
15687
|
+
roster,
|
|
15688
|
+
phaseInvocations.get(p3.id) ?? [],
|
|
15689
|
+
a.doneSeenAt,
|
|
15690
|
+
now,
|
|
15691
|
+
p3.startedAt * 1e3,
|
|
15692
|
+
new Set(p3.doneAtBirth ?? [])
|
|
15693
|
+
) : phases;
|
|
15694
|
+
}
|
|
15246
15695
|
function noTtyPromptEnv() {
|
|
15247
15696
|
const path = askpassDenyPath();
|
|
15248
15697
|
mkdirSync5(dirname7(path), { recursive: true, mode: 448 });
|
|
15249
|
-
|
|
15698
|
+
writeFileSync6(path, ASKPASS_DENY, { mode: 448 });
|
|
15250
15699
|
return { SSH_ASKPASS: path, SSH_ASKPASS_REQUIRE: "force", GIT_TERMINAL_PROMPT: "0" };
|
|
15251
15700
|
}
|
|
15252
15701
|
function tmux(...args) {
|
|
@@ -15325,7 +15774,11 @@ function parsePanes(out4) {
|
|
|
15325
15774
|
eventHook,
|
|
15326
15775
|
created,
|
|
15327
15776
|
agentRoster,
|
|
15328
|
-
launchClass
|
|
15777
|
+
launchClass,
|
|
15778
|
+
model,
|
|
15779
|
+
effort2,
|
|
15780
|
+
orchestrated,
|
|
15781
|
+
doneAtBirth
|
|
15329
15782
|
] = line.split(" ");
|
|
15330
15783
|
if (!n2?.startsWith(PREFIX)) return [];
|
|
15331
15784
|
const exited = dead === "1";
|
|
@@ -15357,7 +15810,13 @@ function parsePanes(out4) {
|
|
|
15357
15810
|
// SPEC-909 · ADR-0146 · sesi yang lahir sebelum pembaruan tak punya opsi ini → false.
|
|
15358
15811
|
eventHook: eventHook === "1",
|
|
15359
15812
|
agentRoster: parseAgentRoster(agentRoster),
|
|
15360
|
-
launchClass: launchClass === "agent" || launchClass === "terminal" ? launchClass : void 0
|
|
15813
|
+
launchClass: launchClass === "agent" || launchClass === "terminal" ? launchClass : void 0,
|
|
15814
|
+
model: model || void 0,
|
|
15815
|
+
effort: effort2 || void 0,
|
|
15816
|
+
orchestrated: orchestrated === "1",
|
|
15817
|
+
// M-2 · ADR-0164 · daftar kosong/tak ada opsi → undefined, bukan [] (sesi lama atau sesi
|
|
15818
|
+
// tanpa satu pun fase done saat lahir tak perlu membawa array kosong di DTO internal).
|
|
15819
|
+
doneAtBirth: doneAtBirth ? doneAtBirth.split(",").filter(Boolean) : void 0
|
|
15361
15820
|
}];
|
|
15362
15821
|
});
|
|
15363
15822
|
}
|
|
@@ -15374,7 +15833,9 @@ function parseAgentRoster(value) {
|
|
|
15374
15833
|
...typeof row.id === "string" ? { id: row.id } : {},
|
|
15375
15834
|
...typeof row.model === "string" ? { model: row.model } : {},
|
|
15376
15835
|
...typeof row.definitionHash === "string" && /^[a-f0-9]{64}$/.test(row.definitionHash) ? { definitionHash: row.definitionHash } : {},
|
|
15377
|
-
...typeof row.timeoutSeconds === "number" ? { timeoutSeconds: row.timeoutSeconds } : {}
|
|
15836
|
+
...typeof row.timeoutSeconds === "number" ? { timeoutSeconds: row.timeoutSeconds } : {},
|
|
15837
|
+
...typeof row.phase === "string" ? { phase: row.phase } : {},
|
|
15838
|
+
...typeof row.effort === "string" ? { effort: row.effort } : {}
|
|
15378
15839
|
}];
|
|
15379
15840
|
});
|
|
15380
15841
|
} catch {
|
|
@@ -15444,6 +15905,7 @@ function createSession(projectId, cwd, opts = {}) {
|
|
|
15444
15905
|
chmodSync6(eventDir, 448);
|
|
15445
15906
|
}
|
|
15446
15907
|
const agentForDefs = opts.agent ?? "claude";
|
|
15908
|
+
const sessionEffort = agentForDefs === "codex" && opts.model && opts.effort ? coerceCodexEffort(opts.model, opts.effort) : opts.effort;
|
|
15447
15909
|
const selectionContext = {
|
|
15448
15910
|
projectId,
|
|
15449
15911
|
runtime: agentForDefs,
|
|
@@ -15453,36 +15915,85 @@ function createSession(projectId, cwd, opts = {}) {
|
|
|
15453
15915
|
prompt: opts.prompt,
|
|
15454
15916
|
changedFiles: opts.command ? [] : collectChangedFiles(cwd, opts.env?.HANOMAN_BASE_SHA)
|
|
15455
15917
|
};
|
|
15456
|
-
const
|
|
15918
|
+
const rawCustomDefs = opts.command ? [] : customAgentsFor(selectionContext);
|
|
15919
|
+
const customDefs = rawCustomDefs.filter((def2) => !isPhaseAgentName(def2.name));
|
|
15920
|
+
for (const def2 of rawCustomDefs) {
|
|
15921
|
+
if (isPhaseAgentName(def2.name)) {
|
|
15922
|
+
process.stderr.write(
|
|
15923
|
+
`hanoman: custom agent ${def2.name} diabaikan \u2014 awalan hanoman-fase- dicadangkan (ADR-0164)
|
|
15924
|
+
`
|
|
15925
|
+
);
|
|
15926
|
+
}
|
|
15927
|
+
}
|
|
15928
|
+
const requestedPhaseDefs = opts.command ? [] : opts.phaseAgents ?? [];
|
|
15457
15929
|
let rosterBlock = "";
|
|
15458
15930
|
let codexAgentArgs = [];
|
|
15459
15931
|
let agentsFile;
|
|
15460
15932
|
let agentConfigDir;
|
|
15461
15933
|
let liveAgentDefs = [];
|
|
15462
|
-
|
|
15934
|
+
let renderedDefs = [];
|
|
15935
|
+
let orchestrated = false;
|
|
15936
|
+
let statusLineCommand;
|
|
15937
|
+
if (customDefs.length > 0 || requestedPhaseDefs.length > 0) {
|
|
15463
15938
|
const tempDir = agentTempDir(id2);
|
|
15464
15939
|
agentConfigDir = tempDir;
|
|
15465
15940
|
mkdirSync5(tempDir, { recursive: true, mode: 448 });
|
|
15466
15941
|
const readOnlyHook = customDefs.some((def2) => def2.workspacePolicy === "read-only") ? writeReadOnlyHook(tempDir) : void 0;
|
|
15467
|
-
|
|
15468
|
-
const
|
|
15469
|
-
if (
|
|
15470
|
-
|
|
15471
|
-
|
|
15942
|
+
const attempt = (phaseDefs) => {
|
|
15943
|
+
const defs = [...phaseDefs, ...customDefs];
|
|
15944
|
+
if (defs.length === 0) return [];
|
|
15945
|
+
if (agentForDefs === "claude") {
|
|
15946
|
+
const file = agentsFilePath(id2);
|
|
15947
|
+
try {
|
|
15948
|
+
writeFileSync6(file, renderAgentsJson(defs, { readOnlyHookCommand: readOnlyHook?.command }), { mode: 384 });
|
|
15949
|
+
} catch (error) {
|
|
15950
|
+
if (phaseDefs.length === 0) throw error;
|
|
15951
|
+
return [error instanceof Error ? error.message : String(error)];
|
|
15952
|
+
}
|
|
15953
|
+
agentsFile = file;
|
|
15472
15954
|
rosterBlock = agentDelegationClause(customDefs, "claude");
|
|
15473
|
-
liveAgentDefs =
|
|
15955
|
+
liveAgentDefs = defs;
|
|
15956
|
+
renderedDefs = defs;
|
|
15957
|
+
return [];
|
|
15474
15958
|
}
|
|
15475
|
-
|
|
15476
|
-
const materialized = materializeCodexAgents(customDefs, tempDir, {
|
|
15959
|
+
const materialized = materializeCodexAgents(defs, tempDir, {
|
|
15477
15960
|
readOnlyHookCommand: readOnlyHook?.command,
|
|
15478
|
-
clientVersion: codexNativeAgentSupport().version
|
|
15961
|
+
clientVersion: codexNativeAgentSupport().version,
|
|
15962
|
+
...phaseDefs.length > 0 ? { maxDepth: 3 } : {}
|
|
15479
15963
|
});
|
|
15964
|
+
const phaseFailures = materialized.warnings.filter((w) => phaseDefs.some((d) => d.name === w.agentName));
|
|
15965
|
+
if (phaseFailures.length > 0) return phaseFailures.map((w) => `${w.agentName}: ${w.reason}`);
|
|
15480
15966
|
codexAgentArgs = materialized.args;
|
|
15481
15967
|
rosterBlock = materialized.delegationClause;
|
|
15482
15968
|
liveAgentDefs = materialized.liveDefs;
|
|
15969
|
+
renderedDefs = defs;
|
|
15483
15970
|
for (const warning of materialized.warnings) {
|
|
15484
15971
|
process.stderr.write(
|
|
15485
15972
|
`hanoman: custom agent ${warning.agentName} tidak dimaterialisasi: ${warning.reason}
|
|
15973
|
+
`
|
|
15974
|
+
);
|
|
15975
|
+
}
|
|
15976
|
+
return [];
|
|
15977
|
+
};
|
|
15978
|
+
let phaseFailReasons = [];
|
|
15979
|
+
if (requestedPhaseDefs.length > 0) {
|
|
15980
|
+
phaseFailReasons = attempt(requestedPhaseDefs);
|
|
15981
|
+
orchestrated = phaseFailReasons.length === 0;
|
|
15982
|
+
}
|
|
15983
|
+
if (!orchestrated) {
|
|
15984
|
+
if (requestedPhaseDefs.length > 0)
|
|
15985
|
+
process.stderr.write(
|
|
15986
|
+
`hanoman: agen fase sesi ${id2} gagal dimaterialisasi \u2014 sesi lahir mode tunggal: ${phaseFailReasons.join("; ")}
|
|
15987
|
+
`
|
|
15988
|
+
);
|
|
15989
|
+
attempt([]);
|
|
15990
|
+
}
|
|
15991
|
+
if (orchestrated && agentForDefs === "claude") {
|
|
15992
|
+
try {
|
|
15993
|
+
statusLineCommand = writeSubagentStatusline(tempDir);
|
|
15994
|
+
} catch (error) {
|
|
15995
|
+
process.stderr.write(
|
|
15996
|
+
`hanoman: gagal menulis subagentStatusline sesi ${id2}: ${error instanceof Error ? error.message : String(error)}
|
|
15486
15997
|
`
|
|
15487
15998
|
);
|
|
15488
15999
|
}
|
|
@@ -15490,10 +16001,11 @@ function createSession(projectId, cwd, opts = {}) {
|
|
|
15490
16001
|
}
|
|
15491
16002
|
let promptArg = "";
|
|
15492
16003
|
let promptFile;
|
|
15493
|
-
|
|
16004
|
+
const sessionPrompt = orchestrated ? opts.prompt : opts.legacyPrompt ?? opts.prompt;
|
|
16005
|
+
if (!opts.command && sessionPrompt) {
|
|
15494
16006
|
promptFile = promptFilePath(id2);
|
|
15495
16007
|
mkdirSync5(dirname7(promptFile), { recursive: true, mode: 448 });
|
|
15496
|
-
|
|
16008
|
+
writeFileSync6(promptFile, sessionPrompt + rosterBlock, { mode: 384 });
|
|
15497
16009
|
promptArg = `"$(cat ${sq(promptFile)})"`;
|
|
15498
16010
|
}
|
|
15499
16011
|
const agent = opts.agent ?? "claude";
|
|
@@ -15505,7 +16017,7 @@ function createSession(projectId, cwd, opts = {}) {
|
|
|
15505
16017
|
if (agent === "codex" && opts.goal && opts.flow && opts.specId) {
|
|
15506
16018
|
goalGate = goalGatePath(id2);
|
|
15507
16019
|
mkdirSync5(dirname7(goalGate), { recursive: true, mode: 448 });
|
|
15508
|
-
|
|
16020
|
+
writeFileSync6(goalGate, codexGoalScript({
|
|
15509
16021
|
flow: opts.flow,
|
|
15510
16022
|
specId: opts.specId,
|
|
15511
16023
|
condition: opts.goal,
|
|
@@ -15514,17 +16026,17 @@ function createSession(projectId, cwd, opts = {}) {
|
|
|
15514
16026
|
stateFile: goalStatePath(id2)
|
|
15515
16027
|
}), { mode: 448 });
|
|
15516
16028
|
}
|
|
15517
|
-
const effort2 = agent === "codex" && opts.model && opts.effort ? coerceCodexEffort(opts.model, opts.effort) : opts.effort;
|
|
15518
16029
|
const flags = agentFlags({
|
|
15519
16030
|
agent,
|
|
15520
16031
|
model: opts.model,
|
|
15521
|
-
effort:
|
|
16032
|
+
effort: sessionEffort,
|
|
15522
16033
|
decisionFile: opts.decisionFile,
|
|
15523
16034
|
goal: opts.goal,
|
|
15524
16035
|
goalGate,
|
|
15525
16036
|
// SPEC-909 · ADR-0146 · sesi ber-`opts.command` tak pernah sampai ke sini (cabang shell
|
|
15526
16037
|
// mentah di atas), jadi hook event hanya pernah terpasang di sesi agen.
|
|
15527
|
-
eventHook: true
|
|
16038
|
+
eventHook: true,
|
|
16039
|
+
subagentStatusLine: statusLineCommand
|
|
15528
16040
|
}).map(sq).join(" ");
|
|
15529
16041
|
const agentsArg = agentsFile ? `--agents "$(cat ${sq(agentsFile)})"` : "";
|
|
15530
16042
|
const nativeAgentArgs = agent === "codex" ? codexAgentArgs.map(sq).join(" ") : "";
|
|
@@ -15639,22 +16151,29 @@ function createSession(projectId, cwd, opts = {}) {
|
|
|
15639
16151
|
"@hanoman_launch_class",
|
|
15640
16152
|
opts.command || kind === "terminal" ? "terminal" : "agent"
|
|
15641
16153
|
);
|
|
16154
|
+
if (!opts.command && opts.model) tmux("set-option", "-t", name(id2), "@hanoman_model", opts.model);
|
|
16155
|
+
if (!opts.command && sessionEffort) tmux("set-option", "-t", name(id2), "@hanoman_effort", sessionEffort);
|
|
16156
|
+
if (orchestrated) tmux("set-option", "-t", name(id2), "@hanoman_orchestrated", "1");
|
|
15642
16157
|
if (liveAgentDefs.length > 0) {
|
|
15643
|
-
const inherited = {
|
|
15644
|
-
model: opts.model,
|
|
15645
|
-
effort: agent === "codex" && opts.model && opts.effort ? coerceCodexEffort(opts.model, opts.effort) : opts.effort
|
|
15646
|
-
};
|
|
16158
|
+
const inherited = { model: opts.model, effort: sessionEffort };
|
|
15647
16159
|
const roster = liveAgentDefs.map((def2) => ({
|
|
15648
16160
|
...def2.id ? { id: def2.id } : {},
|
|
15649
16161
|
name: def2.name,
|
|
15650
16162
|
...def2.model ?? inherited.model ? { model: def2.model ?? inherited.model } : {},
|
|
15651
|
-
|
|
15652
|
-
|
|
16163
|
+
...def2.phase ? { phase: def2.phase } : {},
|
|
16164
|
+
...def2.effort ?? inherited.effort ? { effort: def2.effort ?? inherited.effort } : {},
|
|
16165
|
+
// Berkas native dirender dengan `renderedDefs`, walau satu berkas Codex lain gagal ditulis.
|
|
16166
|
+
definitionHash: agentDefinitionHash(def2, renderedDefs, agent, inherited),
|
|
15653
16167
|
...def2.timeoutSeconds ? { timeoutSeconds: def2.timeoutSeconds } : {}
|
|
15654
16168
|
}));
|
|
15655
16169
|
tmux("set-option", "-t", name(id2), "@hanoman_agent_roster", JSON.stringify(roster));
|
|
15656
16170
|
}
|
|
15657
16171
|
if (opts.phaseFile) tmux("set-option", "-t", name(id2), "@hanoman_phase_file", opts.phaseFile);
|
|
16172
|
+
if (opts.phaseFile && opts.flow) {
|
|
16173
|
+
const doneAtBirth = readPhases(opts.phaseFile, opts.flow).filter((p3) => p3.state === "done" || p3.state === "skipped").map((p3) => p3.name);
|
|
16174
|
+
if (doneAtBirth.length > 0)
|
|
16175
|
+
tmux("set-option", "-t", name(id2), "@hanoman_done_at_birth", doneAtBirth.join(","));
|
|
16176
|
+
}
|
|
15658
16177
|
if (opts.decisionFile) tmux("set-option", "-t", name(id2), "@hanoman_decision_file", opts.decisionFile);
|
|
15659
16178
|
if (!opts.command) tmux("set-option", "-t", name(id2), "@hanoman_event_hook", "1");
|
|
15660
16179
|
if (opts.goal && !opts.command) void armGoalInTui(id2, opts.goal, { agent }).catch(() => {
|
|
@@ -15799,7 +16318,14 @@ function flushOutput(a) {
|
|
|
15799
16318
|
}
|
|
15800
16319
|
function open(id2) {
|
|
15801
16320
|
const pty = spawnPty("attach-session", "-d", "-t", name(id2));
|
|
15802
|
-
const a = {
|
|
16321
|
+
const a = {
|
|
16322
|
+
pty,
|
|
16323
|
+
scrollback: "",
|
|
16324
|
+
clients: /* @__PURE__ */ new Set(),
|
|
16325
|
+
lastPhases: "",
|
|
16326
|
+
pending: "",
|
|
16327
|
+
doneSeenAt: /* @__PURE__ */ new Map()
|
|
16328
|
+
};
|
|
15803
16329
|
pty.onData((d) => {
|
|
15804
16330
|
a.pending += d;
|
|
15805
16331
|
if (a.pending.length >= COALESCE_MAX_BYTES) flushOutput(a);
|
|
@@ -15817,6 +16343,7 @@ function drop(id2) {
|
|
|
15817
16343
|
if (!a) return;
|
|
15818
16344
|
flushOutput(a);
|
|
15819
16345
|
attached.delete(id2);
|
|
16346
|
+
phaseInvocations.delete(id2);
|
|
15820
16347
|
a.pty.kill();
|
|
15821
16348
|
for (const c of a.clients) c.close();
|
|
15822
16349
|
a.clients.clear();
|
|
@@ -15830,13 +16357,16 @@ function end(id2, code) {
|
|
|
15830
16357
|
}
|
|
15831
16358
|
function pollPhases(p3, a) {
|
|
15832
16359
|
if (!p3.flow || !p3.phaseFile) return;
|
|
15833
|
-
const phases =
|
|
16360
|
+
const phases = phaseView(p3, a);
|
|
15834
16361
|
const complete = paneComplete(p3);
|
|
15835
16362
|
const json = phaseKey(phases, complete);
|
|
15836
16363
|
if (json === a.lastPhases) return;
|
|
15837
16364
|
a.lastPhases = json;
|
|
15838
16365
|
broadcast(a, { t: "phase", phases, complete });
|
|
15839
16366
|
}
|
|
16367
|
+
function setPhaseInvocations(sessionId2, rows) {
|
|
16368
|
+
if (attached.has(sessionId2)) phaseInvocations.set(sessionId2, rows);
|
|
16369
|
+
}
|
|
15840
16370
|
function pollAlt(p3, a) {
|
|
15841
16371
|
if (p3.altScreen === a.lastAlt) return;
|
|
15842
16372
|
a.lastAlt = p3.altScreen;
|
|
@@ -15892,7 +16422,7 @@ function attach(id2, c) {
|
|
|
15892
16422
|
a.lastAlt = p3.altScreen;
|
|
15893
16423
|
c.send(frame({ t: "alt", on: p3.altScreen }));
|
|
15894
16424
|
if (p3.flow && p3.phaseFile) {
|
|
15895
|
-
const phases =
|
|
16425
|
+
const phases = phaseView(p3, a);
|
|
15896
16426
|
const complete = paneComplete(p3);
|
|
15897
16427
|
a.lastPhases = phaseKey(phases, complete);
|
|
15898
16428
|
c.send(frame({ t: "phase", phases, complete }));
|
|
@@ -15908,6 +16438,7 @@ function resize(id2, cols, rows) {
|
|
|
15908
16438
|
attached.get(id2)?.pty.resize(cols, rows);
|
|
15909
16439
|
}
|
|
15910
16440
|
function killSession(id2) {
|
|
16441
|
+
phaseInvocations.delete(id2);
|
|
15911
16442
|
const p3 = getSession(id2);
|
|
15912
16443
|
if (!p3) return false;
|
|
15913
16444
|
const transcript = captureTranscript(id2);
|
|
@@ -15946,7 +16477,7 @@ function spawnPty(...args) {
|
|
|
15946
16477
|
);
|
|
15947
16478
|
}
|
|
15948
16479
|
}
|
|
15949
|
-
var socket, PREFIX, MAX_SCROLLBACK, SCROLLBACK_SLACK, POLL_MS, markerFilled, clearMarker, PANE_QUIET_MS, paneQuiet, markerOnset, decisionOnset, attached, claudeBin, shellBin, codexBin, agentBin, rootBypassEnv, frame, name, promptFilePath, goalGatePath, goalStatePath, agentTempDir, agentsFilePath, askpassDenyPath, ASKPASS_DENY, NO_SERVER, TmuxError, sq, idFor, FMT, toSessionInfo, listSessions, listSessionsAsync, liveDecisions, getSession, getSessionAsync, hooks, emitBirth, emitDeath, customAgentSource, codexNativeAgentSupport, customAgentsFor, runGitDiff, sleep, paneText, paneIO, DIALOG_CAPTURE_LINES, GOAL_ARMED_MARKERS, goalArmed, COALESCE_MS, COALESCE_MAX_BYTES, trimScrollback, phaseKey, paneComplete, sessionFinished, poll, polling, TERMINAL_QUERY, stripTerminalQueries, detach;
|
|
16480
|
+
var socket, PREFIX, MAX_SCROLLBACK, SCROLLBACK_SLACK, POLL_MS, markerFilled, clearMarker, PANE_QUIET_MS, paneQuiet, markerOnset, decisionOnset, attached, phaseInvocations, claudeBin, shellBin, codexBin, agentBin, rootBypassEnv, frame, name, promptFilePath, goalGatePath, goalStatePath, agentTempDir, agentsFilePath, askpassDenyPath, ASKPASS_DENY, NO_SERVER, TmuxError, sq, idFor, FMT, toSessionInfo, listSessions, listSessionsAsync, liveDecisions, 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, paneComplete, sessionFinished, poll, polling, TERMINAL_QUERY, stripTerminalQueries, detach;
|
|
15950
16481
|
var init_pty = __esm({
|
|
15951
16482
|
"src/services/pty.ts"() {
|
|
15952
16483
|
"use strict";
|
|
@@ -15977,7 +16508,7 @@ var init_pty = __esm({
|
|
|
15977
16508
|
};
|
|
15978
16509
|
clearMarker = (f) => {
|
|
15979
16510
|
try {
|
|
15980
|
-
|
|
16511
|
+
writeFileSync6(f, "");
|
|
15981
16512
|
} catch {
|
|
15982
16513
|
}
|
|
15983
16514
|
};
|
|
@@ -15998,6 +16529,7 @@ var init_pty = __esm({
|
|
|
15998
16529
|
return secs > 0 ? new Date(secs * 1e3).toISOString() : void 0;
|
|
15999
16530
|
};
|
|
16000
16531
|
attached = /* @__PURE__ */ new Map();
|
|
16532
|
+
phaseInvocations = /* @__PURE__ */ new Map();
|
|
16001
16533
|
claudeBin = () => effectiveStr("HANOMAN_CLAUDE_BIN") ?? "claude";
|
|
16002
16534
|
shellBin = () => effectiveStr("HANOMAN_SHELL") ?? process.env.SHELL ?? "/bin/bash";
|
|
16003
16535
|
codexBin = () => effectiveStr("HANOMAN_CODEX_BIN") ?? "codex";
|
|
@@ -16044,7 +16576,13 @@ exit 1
|
|
|
16044
16576
|
"#{@hanoman_event_hook}",
|
|
16045
16577
|
"#{session_created}",
|
|
16046
16578
|
"#{@hanoman_agent_roster}",
|
|
16047
|
-
"#{@hanoman_launch_class}"
|
|
16579
|
+
"#{@hanoman_launch_class}",
|
|
16580
|
+
// ADR-0164 · di UJUNG juga, alasan yang sama dengan SPEC-919.
|
|
16581
|
+
"#{@hanoman_model}",
|
|
16582
|
+
"#{@hanoman_effort}",
|
|
16583
|
+
"#{@hanoman_orchestrated}",
|
|
16584
|
+
// M-2 · ADR-0164 · nama fase done|skipped SAAT LAHIR, dipisah koma — di UJUNG juga.
|
|
16585
|
+
"#{@hanoman_done_at_birth}"
|
|
16048
16586
|
].join(" ");
|
|
16049
16587
|
toSessionInfo = ({
|
|
16050
16588
|
id: id2,
|
|
@@ -16058,7 +16596,10 @@ exit 1
|
|
|
16058
16596
|
decision,
|
|
16059
16597
|
agent,
|
|
16060
16598
|
decisionFile,
|
|
16061
|
-
activityAt
|
|
16599
|
+
activityAt,
|
|
16600
|
+
model,
|
|
16601
|
+
effort: effort2,
|
|
16602
|
+
orchestrated
|
|
16062
16603
|
}) => ({
|
|
16063
16604
|
id: id2,
|
|
16064
16605
|
projectId,
|
|
@@ -16072,7 +16613,10 @@ exit 1
|
|
|
16072
16613
|
// Hanya untuk pane mati: `pane_dead_status` kosong pada pane hidup, dan `exitCode: 0` di sana
|
|
16073
16614
|
// akan terbaca sebagai "sudah berakhir sukses".
|
|
16074
16615
|
...exited ? { exitCode: code } : {},
|
|
16075
|
-
...decision && decisionFile ? { decisionAt: decisionOnset(decisionFile, activityAt) } : {}
|
|
16616
|
+
...decision && decisionFile ? { decisionAt: decisionOnset(decisionFile, activityAt) } : {},
|
|
16617
|
+
...model ? { model } : {},
|
|
16618
|
+
...effort2 ? { effort: effort2 } : {},
|
|
16619
|
+
...orchestrated ? { orchestrated: true } : {}
|
|
16076
16620
|
});
|
|
16077
16621
|
listSessions = () => listPanes().map(toSessionInfo);
|
|
16078
16622
|
listSessionsAsync = async () => (await listPanesAsync()).map(toSessionInfo);
|
|
@@ -16103,6 +16647,7 @@ exit 1
|
|
|
16103
16647
|
};
|
|
16104
16648
|
customAgentSource = () => [];
|
|
16105
16649
|
codexNativeAgentSupport = () => ({ version: "0.151.0", ok: true });
|
|
16650
|
+
nativeAgentsAvailable = (agent) => agent === "claude" || codexNativeAgentSupport().ok;
|
|
16106
16651
|
customAgentsFor = (context) => {
|
|
16107
16652
|
try {
|
|
16108
16653
|
return customAgentSource(context);
|
|
@@ -16906,6 +17451,8 @@ var init_settings3 = __esm({
|
|
|
16906
17451
|
// SPEC-518 · agen pembuat changelog (opt-in, mati)
|
|
16907
17452
|
portalChat: PORTAL_CHAT_DEFAULTS,
|
|
16908
17453
|
// SPEC-854 · ADR-0130 · chat portal klien (opt-in, mati)
|
|
17454
|
+
orchestration: ORCHESTRATION_DEFAULTS,
|
|
17455
|
+
// ADR-0164 · orkestrasi subagent per fase (default aktif)
|
|
16909
17456
|
builtinAgents: {},
|
|
16910
17457
|
// SPEC-881 · ADR-0136 · sidik jari seed (lokal, tak disync)
|
|
16911
17458
|
builtinAgentPolicies: {}
|
|
@@ -23585,15 +24132,15 @@ var init_sync_client = __esm({
|
|
|
23585
24132
|
// src/services/agent-tool-catalog.ts
|
|
23586
24133
|
import { readFileSync as readFileSync17 } from "node:fs";
|
|
23587
24134
|
import { homedir as homedir10 } from "node:os";
|
|
23588
|
-
import { join as
|
|
24135
|
+
import { join as join33 } from "node:path";
|
|
23589
24136
|
function mcpServerNames(repoDir) {
|
|
23590
24137
|
const names = [];
|
|
23591
|
-
const claudeJson = readJson2(
|
|
24138
|
+
const claudeJson = readJson2(join33(home(), ".claude.json"));
|
|
23592
24139
|
names.push(...serversOf(claudeJson));
|
|
23593
24140
|
if (repoDir) {
|
|
23594
24141
|
const projects = claudeJson?.projects;
|
|
23595
24142
|
if (projects && typeof projects === "object") names.push(...serversOf(projects[repoDir]));
|
|
23596
|
-
names.push(...serversOf(readJson2(
|
|
24143
|
+
names.push(...serversOf(readJson2(join33(repoDir, ".mcp.json"))));
|
|
23597
24144
|
}
|
|
23598
24145
|
names.push(...codexServers());
|
|
23599
24146
|
return [...new Set(names.filter(Boolean))].sort((a, b) => a.localeCompare(b));
|
|
@@ -23622,7 +24169,7 @@ var init_agent_tool_catalog = __esm({
|
|
|
23622
24169
|
codexServers = () => {
|
|
23623
24170
|
let text;
|
|
23624
24171
|
try {
|
|
23625
|
-
text = readFileSync17(
|
|
24172
|
+
text = readFileSync17(join33(home(), ".codex", "config.toml"), "utf8");
|
|
23626
24173
|
} catch {
|
|
23627
24174
|
return [];
|
|
23628
24175
|
}
|
|
@@ -27273,9 +27820,9 @@ import { resolve as resolve10 } from "node:path";
|
|
|
27273
27820
|
// src/services/safe-repo-path.ts
|
|
27274
27821
|
import { constants as constants2 } from "node:fs";
|
|
27275
27822
|
import { lstat, mkdir as mkdir2, open as open2, realpath, rename, unlink as unlink2 } from "node:fs/promises";
|
|
27276
|
-
import { isAbsolute as isAbsolute4, join as
|
|
27823
|
+
import { isAbsolute as isAbsolute4, join as join11, relative, resolve as resolve9, sep as sep2 } from "node:path";
|
|
27277
27824
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
27278
|
-
import { closeSync, fstatSync, lstatSync, mkdirSync as mkdirSync6, openSync, readFileSync as readFileSync7, realpathSync as realpathSync2, renameSync as renameSync2, unlinkSync, writeFileSync as
|
|
27825
|
+
import { closeSync, fstatSync, lstatSync, mkdirSync as mkdirSync6, openSync, readFileSync as readFileSync7, realpathSync as realpathSync2, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync7 } from "node:fs";
|
|
27279
27826
|
var PathContainmentError = class extends Error {
|
|
27280
27827
|
code = "PATH_CONTAINMENT";
|
|
27281
27828
|
};
|
|
@@ -27303,7 +27850,7 @@ async function ensureRepoParents(root, rel) {
|
|
|
27303
27850
|
const parts = components(rel).slice(0, -1);
|
|
27304
27851
|
let current = base2;
|
|
27305
27852
|
for (const part of parts) {
|
|
27306
|
-
current =
|
|
27853
|
+
current = join11(current, part);
|
|
27307
27854
|
try {
|
|
27308
27855
|
await mkdir2(current, { mode: 448 });
|
|
27309
27856
|
} catch (error) {
|
|
@@ -27320,7 +27867,7 @@ function ensureRepoParentsSync(root, rel) {
|
|
|
27320
27867
|
const parts = components(rel).slice(0, -1);
|
|
27321
27868
|
let current = base2;
|
|
27322
27869
|
for (const part of parts) {
|
|
27323
|
-
current =
|
|
27870
|
+
current = join11(current, part);
|
|
27324
27871
|
try {
|
|
27325
27872
|
mkdirSync6(current, { mode: 448 });
|
|
27326
27873
|
} catch (error) {
|
|
@@ -27336,7 +27883,7 @@ async function resolveRepoEntry(root, rel, opts = {}) {
|
|
|
27336
27883
|
const parts = components(rel);
|
|
27337
27884
|
let current = base2;
|
|
27338
27885
|
for (let i = 0; i < parts.length; i++) {
|
|
27339
|
-
current =
|
|
27886
|
+
current = join11(current, parts[i]);
|
|
27340
27887
|
const stat4 = await lstat(current).catch((error) => {
|
|
27341
27888
|
if (error.code === "ENOENT" && opts.allowMissingTail) return null;
|
|
27342
27889
|
if (error.code === "ENOENT" && opts.allowMissingFinal && i === parts.length - 1) return null;
|
|
@@ -27357,7 +27904,7 @@ function assertSafeRepoPathSync(root, rel, allowMissingFinal = false, allowMissi
|
|
|
27357
27904
|
const parts = components(rel);
|
|
27358
27905
|
let current = base2;
|
|
27359
27906
|
for (let i = 0; i < parts.length; i++) {
|
|
27360
|
-
current =
|
|
27907
|
+
current = join11(current, parts[i]);
|
|
27361
27908
|
try {
|
|
27362
27909
|
const stat4 = lstatSync(current);
|
|
27363
27910
|
if (stat4.isSymbolicLink()) denied("symlink");
|
|
@@ -27387,10 +27934,10 @@ function writeRepoFileAtomicSync(root, rel, data) {
|
|
|
27387
27934
|
const parent = resolve9(path, "..");
|
|
27388
27935
|
const parentRel = relative(realpathSync2(root), parent);
|
|
27389
27936
|
if (parentRel) assertSafeRepoPathSync(root, parentRel);
|
|
27390
|
-
const temp =
|
|
27937
|
+
const temp = join11(parent, `.hanoman-${randomUUID3()}.tmp`);
|
|
27391
27938
|
const fd = openSync(temp, constants2.O_WRONLY | constants2.O_CREAT | constants2.O_EXCL | (constants2.O_NOFOLLOW ?? 0), 384);
|
|
27392
27939
|
try {
|
|
27393
|
-
|
|
27940
|
+
writeFileSync7(fd, data);
|
|
27394
27941
|
} finally {
|
|
27395
27942
|
closeSync(fd);
|
|
27396
27943
|
}
|
|
@@ -27434,7 +27981,7 @@ async function writeRepoFileAtomic(root, rel, data) {
|
|
|
27434
27981
|
await mkdir2(entry.parent, { recursive: false, mode: 448 }).catch((error) => {
|
|
27435
27982
|
if (error.code !== "EEXIST") throw error;
|
|
27436
27983
|
});
|
|
27437
|
-
const temp =
|
|
27984
|
+
const temp = join11(entry.parent, `.hanoman-${randomUUID3()}.tmp`);
|
|
27438
27985
|
const handle = await open2(temp, constants2.O_WRONLY | constants2.O_CREAT | constants2.O_EXCL | (constants2.O_NOFOLLOW ?? 0), 384);
|
|
27439
27986
|
try {
|
|
27440
27987
|
await handle.writeFile(data);
|
|
@@ -27876,7 +28423,7 @@ import { existsSync as existsSync9 } from "node:fs";
|
|
|
27876
28423
|
import { execFile as execFile4 } from "node:child_process";
|
|
27877
28424
|
import { promisify as promisify3 } from "node:util";
|
|
27878
28425
|
import { rmSync as rmSync4 } from "node:fs";
|
|
27879
|
-
import { join as
|
|
28426
|
+
import { join as join12 } from "node:path";
|
|
27880
28427
|
var sanitize = (id2) => id2.toLowerCase().replace(/[^a-z0-9_-]/g, "_");
|
|
27881
28428
|
var sourceBranch = (specId) => `hanoman/${sanitize(specId)}`;
|
|
27882
28429
|
var exec3 = promisify3(execFile4);
|
|
@@ -27920,7 +28467,7 @@ async function integrateBranch(repoDir, src, op, target2) {
|
|
|
27920
28467
|
const tgt = await resolveTarget(repoDir, target2);
|
|
27921
28468
|
if (!tgt) return { status: "error", code: 400, error: `target "${target2}" tidak dikenal` };
|
|
27922
28469
|
await sh(repoDir, ["fetch", "origin"]);
|
|
27923
|
-
const wt =
|
|
28470
|
+
const wt = join12(repoDir, ".worktrees", `merge-${sanitize(src.mergeId)}`);
|
|
27924
28471
|
await reclaim(repoDir, wt);
|
|
27925
28472
|
const baseRef = op === "merge" ? tgt.ref : source;
|
|
27926
28473
|
const baseSha = await out(repoDir, ["rev-parse", "--verify", "--end-of-options", `${baseRef}^{commit}`]);
|
|
@@ -27981,7 +28528,7 @@ async function mergeIntoCurrent(repoDir, source, opts = {}) {
|
|
|
27981
28528
|
const src = await resolveGraphSource(repoDir, source);
|
|
27982
28529
|
if (!src) return { status: "error", code: 400, error: `source "${source}" tak dikenal` };
|
|
27983
28530
|
await sh(repoDir, ["fetch", "origin"]);
|
|
27984
|
-
const wt =
|
|
28531
|
+
const wt = join12(repoDir, ".worktrees", `merge-${sanitize(current)}`);
|
|
27985
28532
|
await reclaim(repoDir, wt);
|
|
27986
28533
|
const baseSha = await out(repoDir, ["rev-parse", "--verify", "--end-of-options", `refs/heads/${current}^{commit}`]);
|
|
27987
28534
|
if (!await ok(repoDir, ["worktree", "add", "--detach", "-q", wt, baseSha]))
|
|
@@ -28019,7 +28566,7 @@ async function replayCurrent(repoDir, source, cmd) {
|
|
|
28019
28566
|
const current = await out(repoDir, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
28020
28567
|
if (!current || current === "HEAD")
|
|
28021
28568
|
return { status: "error", code: 409, error: "HEAD detached \u2014 checkout sebuah branch dulu" };
|
|
28022
|
-
const wt =
|
|
28569
|
+
const wt = join12(repoDir, ".worktrees", `merge-${sanitize(current)}`);
|
|
28023
28570
|
await reclaim(repoDir, wt);
|
|
28024
28571
|
const baseSha = await out(repoDir, ["rev-parse", "--verify", "--end-of-options", `refs/heads/${current}^{commit}`]);
|
|
28025
28572
|
if (!await ok(repoDir, ["worktree", "add", "--detach", "-q", wt, baseSha]))
|
|
@@ -28069,15 +28616,15 @@ import { execFile as execFile5 } from "node:child_process";
|
|
|
28069
28616
|
import { promisify as promisify4 } from "node:util";
|
|
28070
28617
|
import { mkdtemp, copyFile, rm as rm2 } from "node:fs/promises";
|
|
28071
28618
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
28072
|
-
import { join as
|
|
28619
|
+
import { join as join13, resolve as resolve11 } from "node:path";
|
|
28073
28620
|
var exec4 = promisify4(execFile5);
|
|
28074
28621
|
var GIT3 = { maxBuffer: 1 << 24 };
|
|
28075
28622
|
var MAX = 256 * 1024;
|
|
28076
|
-
var worktreeDir = (repoDir, specId) =>
|
|
28623
|
+
var worktreeDir = (repoDir, specId) => join13(repoDir, ".worktrees", specId.toLowerCase().replace(/[^a-z0-9_-]/g, "_"));
|
|
28077
28624
|
async function withTempIndex(wt, fn) {
|
|
28078
28625
|
const idx = (await exec4("git", ["rev-parse", "--git-path", "index"], { cwd: wt, ...GIT3 })).stdout.trim();
|
|
28079
|
-
const dir2 = await mkdtemp(
|
|
28080
|
-
const tmp =
|
|
28626
|
+
const dir2 = await mkdtemp(join13(tmpdir2(), "hanoman-idx-"));
|
|
28627
|
+
const tmp = join13(dir2, "index");
|
|
28081
28628
|
await copyFile(resolve11(wt, idx), tmp);
|
|
28082
28629
|
const env = { ...process.env, GIT_INDEX_FILE: tmp };
|
|
28083
28630
|
try {
|
|
@@ -28276,7 +28823,7 @@ function appendSourceHistory(current, entry) {
|
|
|
28276
28823
|
// src/services/spec-reset.ts
|
|
28277
28824
|
import { execFile as execFile8 } from "node:child_process";
|
|
28278
28825
|
import { existsSync as existsSync8 } from "node:fs";
|
|
28279
|
-
import { join as
|
|
28826
|
+
import { join as join15 } from "node:path";
|
|
28280
28827
|
import { promisify as promisify7 } from "node:util";
|
|
28281
28828
|
|
|
28282
28829
|
// src/services/stage-artifacts.ts
|
|
@@ -28325,7 +28872,7 @@ init_db();
|
|
|
28325
28872
|
import { execFile as execFile6 } from "node:child_process";
|
|
28326
28873
|
import { readdir, rm as rm3 } from "node:fs/promises";
|
|
28327
28874
|
import { promisify as promisify5 } from "node:util";
|
|
28328
|
-
import { join as
|
|
28875
|
+
import { join as join14, resolve as resolve12 } from "node:path";
|
|
28329
28876
|
init_notifications2();
|
|
28330
28877
|
var TICK_MS = 6e4;
|
|
28331
28878
|
var trashDirOf = (repoDir) => resolve12(repoDir, ".worktrees", ".trash");
|
|
@@ -28387,7 +28934,7 @@ async function sweepRepo(repoDir, projectId, deps = prodReaperDeps) {
|
|
|
28387
28934
|
}
|
|
28388
28935
|
let removed = 0;
|
|
28389
28936
|
for (const entry of entries3) {
|
|
28390
|
-
const path =
|
|
28937
|
+
const path = join14(dir2, entry);
|
|
28391
28938
|
const known = pending.get(path);
|
|
28392
28939
|
const row = known ?? { path, repoDir, projectId, entry, sessionId: sessionIdOf(entry), since: Date.now() };
|
|
28393
28940
|
pending.set(path, row);
|
|
@@ -28943,7 +29490,7 @@ async function planSpecReset(spec) {
|
|
|
28943
29490
|
const repoDir = await resolveRepoDir(spec.projectId);
|
|
28944
29491
|
if (!repoDir) return EMPTY;
|
|
28945
29492
|
const sid = sessionIdForSpec(spec.id);
|
|
28946
|
-
const wt =
|
|
29493
|
+
const wt = join15(repoDir, ".worktrees", sid);
|
|
28947
29494
|
const branch = `hanoman/${sid}`;
|
|
28948
29495
|
const hasBranch = await shaResolvable(repoDir, `refs/heads/${branch}`);
|
|
28949
29496
|
return {
|
|
@@ -29044,7 +29591,7 @@ import { extname } from "node:path";
|
|
|
29044
29591
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
29045
29592
|
import { spawn as spawn2 } from "node:child_process";
|
|
29046
29593
|
import { mkdir as mkdir3, rename as rename2, unlink as unlink3, writeFile as writeFile2 } from "node:fs/promises";
|
|
29047
|
-
import { basename as basename3, isAbsolute as isAbsolute5, join as
|
|
29594
|
+
import { basename as basename3, isAbsolute as isAbsolute5, join as join16 } from "node:path";
|
|
29048
29595
|
|
|
29049
29596
|
// ../node_modules/.pnpm/strtok3@10.3.5/node_modules/strtok3/lib/stream/Errors.js
|
|
29050
29597
|
var defaultMessages = "End-Of-Stream";
|
|
@@ -32986,16 +33533,16 @@ function safeFilename2(input, extension2) {
|
|
|
32986
33533
|
}
|
|
32987
33534
|
async function commitToStorage(buffer, extension2, deps, beforePromote) {
|
|
32988
33535
|
const storageDir = deps.storageDir ?? uploadDir();
|
|
32989
|
-
const quarantineDir =
|
|
33536
|
+
const quarantineDir = join16(storageDir, ".quarantine");
|
|
32990
33537
|
await mkdir3(quarantineDir, { recursive: true, mode: 448 });
|
|
32991
33538
|
await mkdir3(storageDir, { recursive: true, mode: 448 });
|
|
32992
|
-
const quarantine =
|
|
33539
|
+
const quarantine = join16(quarantineDir, `${randomUUID5()}.upload`);
|
|
32993
33540
|
const storageKey = `${randomUUID5()}${extension2}`;
|
|
32994
33541
|
await writeFile2(quarantine, buffer, { mode: 384, flag: "wx" });
|
|
32995
33542
|
try {
|
|
32996
33543
|
await timeout((deps.scanner ?? scannerFromEnv)(quarantine), UPLOAD_LIMITS.scanMs, "UPLOAD_SCAN");
|
|
32997
33544
|
await beforePromote?.();
|
|
32998
|
-
await rename2(quarantine,
|
|
33545
|
+
await rename2(quarantine, join16(storageDir, storageKey));
|
|
32999
33546
|
} catch (error) {
|
|
33000
33547
|
await unlink3(quarantine).catch(() => {
|
|
33001
33548
|
});
|
|
@@ -33196,10 +33743,10 @@ async function dropSpecAttachments(specId) {
|
|
|
33196
33743
|
// src/services/spec-attachment-dir.ts
|
|
33197
33744
|
init_db();
|
|
33198
33745
|
import { mkdir as mkdir4, readdir as readdir3, readFile as readFile3, rm as rm4, writeFile as writeFile3 } from "node:fs/promises";
|
|
33199
|
-
import { join as
|
|
33746
|
+
import { join as join17 } from "node:path";
|
|
33200
33747
|
init_session_id();
|
|
33201
33748
|
init_uploads();
|
|
33202
|
-
var specAttachmentsDir = (repoDir, sessionId2) =>
|
|
33749
|
+
var specAttachmentsDir = (repoDir, sessionId2) => join17(repoDir, ".worktrees", ".attachments", sessionId2);
|
|
33203
33750
|
var INDEX = "INDEX.md";
|
|
33204
33751
|
var humanSize = (n2) => n2 >= 1024 * 1024 ? `${(n2 / 1024 / 1024).toFixed(1)} MB` : n2 >= 1024 ? `${Math.round(n2 / 1024)} KB` : `${n2} B`;
|
|
33205
33752
|
function uniqueName(taken, filename) {
|
|
@@ -33249,19 +33796,19 @@ async function syncSpecAttachmentsDir(specId, projectId) {
|
|
|
33249
33796
|
for (const a of rows) {
|
|
33250
33797
|
let bytes;
|
|
33251
33798
|
try {
|
|
33252
|
-
bytes = await readFile3(
|
|
33799
|
+
bytes = await readFile3(join17(uploadDir(), a.storageKey));
|
|
33253
33800
|
} catch {
|
|
33254
33801
|
continue;
|
|
33255
33802
|
}
|
|
33256
33803
|
const filename = uniqueName(taken, a.filename);
|
|
33257
|
-
const path =
|
|
33804
|
+
const path = join17(dir2, filename);
|
|
33258
33805
|
await writeFile3(path, bytes, { mode: 384 });
|
|
33259
33806
|
items.push({ filename, mimeType: a.mimeType, size: a.size, path });
|
|
33260
33807
|
}
|
|
33261
|
-
await writeFile3(
|
|
33808
|
+
await writeFile3(join17(dir2, INDEX), renderIndex(specId, items), { mode: 384 });
|
|
33262
33809
|
const keep = /* @__PURE__ */ new Set([INDEX, ...items.map((a) => a.filename)]);
|
|
33263
33810
|
for (const name2 of await readdir3(dir2)) {
|
|
33264
|
-
if (!keep.has(name2)) await rm4(
|
|
33811
|
+
if (!keep.has(name2)) await rm4(join17(dir2, name2), { recursive: true, force: true }).catch(() => {
|
|
33265
33812
|
});
|
|
33266
33813
|
}
|
|
33267
33814
|
return items;
|
|
@@ -36805,7 +37352,7 @@ async function deleteBranches(repoDir, names, opts) {
|
|
|
36805
37352
|
import { execFile as execFile11 } from "node:child_process";
|
|
36806
37353
|
import { realpathSync as realpathSync4 } from "node:fs";
|
|
36807
37354
|
import { stat } from "node:fs/promises";
|
|
36808
|
-
import { basename as basename4, dirname as dirname9, join as
|
|
37355
|
+
import { basename as basename4, dirname as dirname9, join as join18, resolve as resolve15, sep as sep4 } from "node:path";
|
|
36809
37356
|
import { promisify as promisify10 } from "node:util";
|
|
36810
37357
|
var exec10 = promisify10(execFile11);
|
|
36811
37358
|
var GIT7 = { timeout: 6e4, maxBuffer: 1 << 24, encoding: "utf8" };
|
|
@@ -36847,7 +37394,7 @@ var real = (p3) => {
|
|
|
36847
37394
|
} catch {
|
|
36848
37395
|
const path = resolve15(p3);
|
|
36849
37396
|
const parent = dirname9(path);
|
|
36850
|
-
return parent === path ? path :
|
|
37397
|
+
return parent === path ? path : join18(real(parent), basename4(path));
|
|
36851
37398
|
}
|
|
36852
37399
|
};
|
|
36853
37400
|
function hasWorktreeSession(path, sessionId2, sessions) {
|
|
@@ -36859,7 +37406,7 @@ function hasWorktreeSession(path, sessionId2, sessions) {
|
|
|
36859
37406
|
}
|
|
36860
37407
|
async function bornAt(path) {
|
|
36861
37408
|
try {
|
|
36862
|
-
const st = await stat(
|
|
37409
|
+
const st = await stat(join18(path, ".git"));
|
|
36863
37410
|
const ms = st.birthtimeMs > 0 ? st.birthtimeMs : st.mtimeMs;
|
|
36864
37411
|
return new Date(ms).toISOString();
|
|
36865
37412
|
} catch {
|
|
@@ -37067,7 +37614,7 @@ init_config2();
|
|
|
37067
37614
|
init_src2();
|
|
37068
37615
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
37069
37616
|
import { mkdir as mkdir5, writeFile as writeFile4, readFile as readFile4, unlink as unlink4, readdir as readdir4, stat as stat2 } from "node:fs/promises";
|
|
37070
|
-
import { join as
|
|
37617
|
+
import { join as join19, resolve as resolve16, basename as basename5 } from "node:path";
|
|
37071
37618
|
var MAX_TRANSCRIPT_BYTES = 1024 * 1024;
|
|
37072
37619
|
function transcriptDir() {
|
|
37073
37620
|
return resolve16(effectiveStr("HANOMAN_TRANSCRIPT_DIR")?.trim() || resolveDataDirs().transcripts);
|
|
@@ -37088,13 +37635,13 @@ async function saveTranscript(text) {
|
|
|
37088
37635
|
const dir2 = transcriptDir();
|
|
37089
37636
|
await mkdir5(dir2, { recursive: true, mode: 448 });
|
|
37090
37637
|
const key = `${randomUUID6()}.log`;
|
|
37091
|
-
await writeFile4(
|
|
37638
|
+
await writeFile4(join19(dir2, key), body, { encoding: "utf8", mode: 384 });
|
|
37092
37639
|
return { key, bytes: Buffer.byteLength(body, "utf8"), truncated };
|
|
37093
37640
|
}
|
|
37094
37641
|
async function readTranscript(key) {
|
|
37095
37642
|
if (!key) return null;
|
|
37096
37643
|
try {
|
|
37097
|
-
return await readFile4(
|
|
37644
|
+
return await readFile4(join19(transcriptDir(), basename5(key)), "utf8");
|
|
37098
37645
|
} catch {
|
|
37099
37646
|
return null;
|
|
37100
37647
|
}
|
|
@@ -37102,7 +37649,7 @@ async function readTranscript(key) {
|
|
|
37102
37649
|
async function deleteTranscript(key) {
|
|
37103
37650
|
if (!key) return;
|
|
37104
37651
|
try {
|
|
37105
|
-
await unlink4(
|
|
37652
|
+
await unlink4(join19(transcriptDir(), basename5(key)));
|
|
37106
37653
|
} catch (e) {
|
|
37107
37654
|
if (e.code !== "ENOENT") throw e;
|
|
37108
37655
|
}
|
|
@@ -37120,7 +37667,7 @@ async function listTranscripts() {
|
|
|
37120
37667
|
for (const name2 of names) {
|
|
37121
37668
|
if (!name2.endsWith(".log")) continue;
|
|
37122
37669
|
try {
|
|
37123
|
-
rows.push({ key: name2, mtimeMs: (await stat2(
|
|
37670
|
+
rows.push({ key: name2, mtimeMs: (await stat2(join19(dir2, name2))).mtimeMs });
|
|
37124
37671
|
} catch {
|
|
37125
37672
|
}
|
|
37126
37673
|
}
|
|
@@ -37367,7 +37914,7 @@ async function detectOrphanWorktrees(deps = { repos: prodReaperDeps.repos, input
|
|
|
37367
37914
|
// src/services/repo-fs.ts
|
|
37368
37915
|
import { lstat as lstat2, rename as rename3, rm as rm5 } from "node:fs/promises";
|
|
37369
37916
|
import { createWriteStream } from "node:fs";
|
|
37370
|
-
import { join as
|
|
37917
|
+
import { join as join20 } from "node:path";
|
|
37371
37918
|
import { randomUUID as randomUUID8 } from "node:crypto";
|
|
37372
37919
|
import { pipeline } from "node:stream/promises";
|
|
37373
37920
|
var EntryExistsError = class extends Error {
|
|
@@ -37428,7 +37975,7 @@ async function saveUpload(repoDir, rel, source, opts = {}) {
|
|
|
37428
37975
|
if (current && !opts.overwrite) return { status: "exists" };
|
|
37429
37976
|
if (current && !current.isFile())
|
|
37430
37977
|
throw new PathContainmentError("repository path ditolak: target bukan file regular");
|
|
37431
|
-
const temp =
|
|
37978
|
+
const temp = join20(entry.parent, `.hanoman-${randomUUID8()}.tmp`);
|
|
37432
37979
|
try {
|
|
37433
37980
|
await pipeline(source, createWriteStream(temp, { flags: "wx", mode: 384 }));
|
|
37434
37981
|
if (opts.isTruncated?.()) {
|
|
@@ -37949,14 +38496,14 @@ async function finishGraphOp(reply, id2, repoDir, r, verb) {
|
|
|
37949
38496
|
// src/routes/fs.ts
|
|
37950
38497
|
import { readdir as readdir5 } from "node:fs/promises";
|
|
37951
38498
|
import { homedir as homedir4 } from "node:os";
|
|
37952
|
-
import { resolve as resolve18, dirname as dirname10, join as
|
|
38499
|
+
import { resolve as resolve18, dirname as dirname10, join as join21 } from "node:path";
|
|
37953
38500
|
async function fs_default(app2) {
|
|
37954
38501
|
app2.get("/fs/browse", async (req, reply) => {
|
|
37955
38502
|
const q = req.query.path;
|
|
37956
38503
|
const dir2 = q && q.trim() ? resolve18(q.trim()) : homedir4();
|
|
37957
38504
|
try {
|
|
37958
38505
|
const ents = await readdir5(dir2, { withFileTypes: true });
|
|
37959
|
-
const entries3 = ents.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => ({ name: e.name, path:
|
|
38506
|
+
const entries3 = ents.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => ({ name: e.name, path: join21(dir2, e.name) })).sort((a, b) => a.name.localeCompare(b.name));
|
|
37960
38507
|
const parent = dirname10(dir2);
|
|
37961
38508
|
return { path: dir2, parent: parent === dir2 ? null : parent, entries: entries3 };
|
|
37962
38509
|
} catch {
|
|
@@ -37981,6 +38528,21 @@ init_src();
|
|
|
37981
38528
|
init_settings3();
|
|
37982
38529
|
init_codex_trust();
|
|
37983
38530
|
init_pty();
|
|
38531
|
+
|
|
38532
|
+
// src/services/orchestration.ts
|
|
38533
|
+
init_src();
|
|
38534
|
+
init_pty();
|
|
38535
|
+
function sessionPhasePlan(setting, flow, agent, orchestrator) {
|
|
38536
|
+
return resolvePhasePlan({
|
|
38537
|
+
flow,
|
|
38538
|
+
runtime: agent,
|
|
38539
|
+
orchestration: setting.orchestration,
|
|
38540
|
+
orchestrator,
|
|
38541
|
+
nativeAgents: nativeAgentsAvailable(agent)
|
|
38542
|
+
});
|
|
38543
|
+
}
|
|
38544
|
+
|
|
38545
|
+
// src/services/session-launch.ts
|
|
37984
38546
|
init_session_phases();
|
|
37985
38547
|
init_session_launch_gate();
|
|
37986
38548
|
init_pty();
|
|
@@ -38075,22 +38637,36 @@ async function startSpecSession(spec, opts) {
|
|
|
38075
38637
|
payload: spec.payload ?? void 0
|
|
38076
38638
|
};
|
|
38077
38639
|
const resumeCtx = resume ? buildResumeCtx(repoDir, id2, opts.flow, resume.worktreeKept) : void 0;
|
|
38078
|
-
|
|
38079
|
-
|
|
38080
|
-
|
|
38081
|
-
|
|
38082
|
-
|
|
38083
|
-
|
|
38084
|
-
|
|
38085
|
-
|
|
38086
|
-
|
|
38087
|
-
|
|
38088
|
-
|
|
38089
|
-
|
|
38090
|
-
|
|
38091
|
-
|
|
38092
|
-
|
|
38093
|
-
|
|
38640
|
+
const fullPlan = sessionPhasePlan(setting, opts.flow, agent, { model, effort: effort2 });
|
|
38641
|
+
let plan = fullPlan;
|
|
38642
|
+
if (fullPlan && isContinue && !isGoalFlow) {
|
|
38643
|
+
const phases = fullPlan.phases.filter((p3) => p3.phase === "Execute");
|
|
38644
|
+
plan = phases.length ? { ...fullPlan, phases } : null;
|
|
38645
|
+
}
|
|
38646
|
+
const buildPrompt = (p3) => {
|
|
38647
|
+
if (isGoalFlow) {
|
|
38648
|
+
return startGoalPrompt(opts.flow, brief, branchTo, {
|
|
38649
|
+
autonomy: opts.autonomy,
|
|
38650
|
+
verifyScope,
|
|
38651
|
+
resume: resumeCtx,
|
|
38652
|
+
method: method.id,
|
|
38653
|
+
attachments,
|
|
38654
|
+
plan: p3
|
|
38655
|
+
});
|
|
38656
|
+
}
|
|
38657
|
+
if (isContinue) return continuePrompt(opts.flow, brief, branchTo, opts.autonomy, verifyScope, method.id, attachments, p3);
|
|
38658
|
+
if (resumeCtx) return resumePrompt(opts.flow, brief, branchTo, resumeCtx, opts.autonomy, verifyScope, method.id, attachments, p3);
|
|
38659
|
+
return startPrompt(opts.flow, brief, branchTo, opts.autonomy, verifyScope, method.id, attachments, p3);
|
|
38660
|
+
};
|
|
38661
|
+
const legacyPrompt = buildPrompt(null);
|
|
38662
|
+
const prompt = plan ? buildPrompt(plan) : legacyPrompt;
|
|
38663
|
+
const phaseAgents = plan ? buildPhaseAgents(plan, {
|
|
38664
|
+
flow: opts.flow,
|
|
38665
|
+
method,
|
|
38666
|
+
verifyScope,
|
|
38667
|
+
context: isGoalFlow ? goalContext(brief) : specContext(brief),
|
|
38668
|
+
fromAudit: fromAuditOf(spec.payload)
|
|
38669
|
+
}) : [];
|
|
38094
38670
|
const scopeEnv = { HANOMAN_BASE_SHA: baseSha, HANOMAN_VERIFY_SCOPE: verifyScope };
|
|
38095
38671
|
const s2 = createSession(spec.projectId, worktree, {
|
|
38096
38672
|
specId: spec.id,
|
|
@@ -38103,6 +38679,8 @@ async function startSpecSession(spec, opts) {
|
|
|
38103
38679
|
decisionFile: decisionFilePath(repoDir, id2),
|
|
38104
38680
|
attachmentsDir: attachments.items.length ? attachments.dir : void 0,
|
|
38105
38681
|
prompt,
|
|
38682
|
+
legacyPrompt,
|
|
38683
|
+
phaseAgents,
|
|
38106
38684
|
env: scopeEnv
|
|
38107
38685
|
});
|
|
38108
38686
|
return resume ? { id: s2.id, resumed: true } : { id: s2.id };
|
|
@@ -38444,13 +39022,13 @@ function installCommand(m, agent, shell = shellBin()) {
|
|
|
38444
39022
|
|
|
38445
39023
|
// src/services/terminal-diag.ts
|
|
38446
39024
|
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync9, statSync as statSync3, rmSync as rmSync5 } from "node:fs";
|
|
38447
|
-
import { join as
|
|
39025
|
+
import { join as join22 } from "node:path";
|
|
38448
39026
|
var DIAG_MAX_BYTES = 2 * 1024 * 1024;
|
|
38449
39027
|
var KINDS = /* @__PURE__ */ new Set(["key", "comp", "data", "ack", "pred"]);
|
|
38450
39028
|
var ID = /^[A-Za-z0-9_-]{1,64}$/;
|
|
38451
39029
|
function diagFile(home3, sessionId2) {
|
|
38452
39030
|
if (!ID.test(sessionId2)) throw new Error(`id sesi tak sah untuk diag: ${sessionId2}`);
|
|
38453
|
-
return
|
|
39031
|
+
return join22(home3, "diag", `${sessionId2}.jsonl`);
|
|
38454
39032
|
}
|
|
38455
39033
|
function usable(ev) {
|
|
38456
39034
|
if (!ev || typeof ev !== "object") return false;
|
|
@@ -38461,7 +39039,7 @@ function appendDiag(home3, sessionId2, events) {
|
|
|
38461
39039
|
const file = diagFile(home3, sessionId2);
|
|
38462
39040
|
const rows = events.filter(usable);
|
|
38463
39041
|
if (!rows.length) return;
|
|
38464
|
-
mkdirSync9(
|
|
39042
|
+
mkdirSync9(join22(home3, "diag"), { recursive: true });
|
|
38465
39043
|
try {
|
|
38466
39044
|
if (statSync3(file).size > DIAG_MAX_BYTES) rmSync5(file);
|
|
38467
39045
|
} catch {
|
|
@@ -38475,12 +39053,505 @@ init_stage_machine();
|
|
|
38475
39053
|
init_pty();
|
|
38476
39054
|
init_uploads();
|
|
38477
39055
|
|
|
39056
|
+
// src/services/agent-invocations.ts
|
|
39057
|
+
init_src();
|
|
39058
|
+
init_db();
|
|
39059
|
+
import { createHash as createHash8, randomUUID as randomUUID9 } from "node:crypto";
|
|
39060
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
39061
|
+
import { homedir as homedir5 } from "node:os";
|
|
39062
|
+
import { readFileSync as readFileSync9, realpathSync as realpathSync5, statSync as statSync4 } from "node:fs";
|
|
39063
|
+
import { isAbsolute as isAbsolute6, relative as relative2, resolve as resolve19 } from "node:path";
|
|
39064
|
+
|
|
39065
|
+
// src/services/session-event-relay.ts
|
|
39066
|
+
init_session_event_token();
|
|
39067
|
+
init_session_event_spool();
|
|
39068
|
+
import { constants as constants3 } from "node:fs";
|
|
39069
|
+
import { mkdir as mkdir6, open as open3, readdir as readdir6, rm as rm6 } from "node:fs/promises";
|
|
39070
|
+
import { join as join23 } from "node:path";
|
|
39071
|
+
var MAX_EVENT_BYTES = 1e6;
|
|
39072
|
+
var MAX_FILES_PER_DRAIN = 1e3;
|
|
39073
|
+
var SESSION_ID_RE = /^[a-z0-9_-]+$/;
|
|
39074
|
+
var observations = /* @__PURE__ */ new Map();
|
|
39075
|
+
function sessionEventRelayStatus(root = sessionEventSpoolRoot()) {
|
|
39076
|
+
return { ...observations.get(root) ?? {
|
|
39077
|
+
state: "unobserved",
|
|
39078
|
+
checkedAt: null,
|
|
39079
|
+
lastDeliveryAt: null,
|
|
39080
|
+
lastIssueAt: null,
|
|
39081
|
+
retryPending: 0,
|
|
39082
|
+
retryAttempts: 0,
|
|
39083
|
+
droppedEvents: 0
|
|
39084
|
+
} };
|
|
39085
|
+
}
|
|
39086
|
+
async function drainSessionEventSpool(app2, root = sessionEventSpoolRoot()) {
|
|
39087
|
+
const previous = sessionEventRelayStatus(root);
|
|
39088
|
+
const current = { retries: 0, dropped: 0 };
|
|
39089
|
+
let delivered = 0;
|
|
39090
|
+
let failed = false;
|
|
39091
|
+
try {
|
|
39092
|
+
delivered = await drainSpool(app2, root, current);
|
|
39093
|
+
return delivered;
|
|
39094
|
+
} catch (error) {
|
|
39095
|
+
failed = true;
|
|
39096
|
+
throw error;
|
|
39097
|
+
} finally {
|
|
39098
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
39099
|
+
observations.set(root, {
|
|
39100
|
+
state: failed || current.retries > 0 ? "degraded" : "ready",
|
|
39101
|
+
checkedAt: now,
|
|
39102
|
+
lastDeliveryAt: delivered ? now : previous.lastDeliveryAt,
|
|
39103
|
+
lastIssueAt: failed || current.retries || current.dropped ? now : previous.lastIssueAt,
|
|
39104
|
+
retryPending: current.retries,
|
|
39105
|
+
retryAttempts: previous.retryAttempts + current.retries,
|
|
39106
|
+
droppedEvents: previous.droppedEvents + current.dropped
|
|
39107
|
+
});
|
|
39108
|
+
}
|
|
39109
|
+
}
|
|
39110
|
+
async function drainSpool(app2, root, observation) {
|
|
39111
|
+
await mkdir6(root, { recursive: true, mode: 448 });
|
|
39112
|
+
let delivered = 0;
|
|
39113
|
+
let examined = 0;
|
|
39114
|
+
let readBuffer;
|
|
39115
|
+
for (const session of await readdir6(root, { withFileTypes: true })) {
|
|
39116
|
+
if (!session.isDirectory() || !SESSION_ID_RE.test(session.name)) continue;
|
|
39117
|
+
const dir2 = join23(root, session.name);
|
|
39118
|
+
let entries3;
|
|
39119
|
+
try {
|
|
39120
|
+
entries3 = await readdir6(dir2, { withFileTypes: true });
|
|
39121
|
+
} catch {
|
|
39122
|
+
continue;
|
|
39123
|
+
}
|
|
39124
|
+
for (const entry of entries3) {
|
|
39125
|
+
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
39126
|
+
if (++examined > MAX_FILES_PER_DRAIN) return delivered;
|
|
39127
|
+
const path = join23(dir2, entry.name);
|
|
39128
|
+
let payload;
|
|
39129
|
+
let handle;
|
|
39130
|
+
try {
|
|
39131
|
+
handle = await open3(path, constants3.O_RDONLY | constants3.O_NOFOLLOW);
|
|
39132
|
+
const stat4 = await handle.stat();
|
|
39133
|
+
if (!stat4.isFile() || stat4.size > MAX_EVENT_BYTES) throw new Error("payload terlalu besar");
|
|
39134
|
+
readBuffer ??= Buffer.allocUnsafe(MAX_EVENT_BYTES + 1);
|
|
39135
|
+
const { bytesRead } = await handle.read(readBuffer, 0, readBuffer.length, 0);
|
|
39136
|
+
if (bytesRead > MAX_EVENT_BYTES) throw new Error("payload terlalu besar");
|
|
39137
|
+
const parsed = JSON.parse(readBuffer.subarray(0, bytesRead).toString("utf8"));
|
|
39138
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
39139
|
+
throw new Error("payload event bukan object");
|
|
39140
|
+
}
|
|
39141
|
+
payload = parsed;
|
|
39142
|
+
} catch {
|
|
39143
|
+
observation.dropped++;
|
|
39144
|
+
await rm6(path, { force: true }).catch(() => {
|
|
39145
|
+
});
|
|
39146
|
+
continue;
|
|
39147
|
+
} finally {
|
|
39148
|
+
await handle?.close().catch(() => {
|
|
39149
|
+
});
|
|
39150
|
+
}
|
|
39151
|
+
try {
|
|
39152
|
+
const response = await app2.inject({
|
|
39153
|
+
method: "POST",
|
|
39154
|
+
url: "/api/session-events",
|
|
39155
|
+
headers: {
|
|
39156
|
+
authorization: `Bearer ${sessionEventToken(session.name)}`,
|
|
39157
|
+
"x-hanoman-session": session.name
|
|
39158
|
+
},
|
|
39159
|
+
payload
|
|
39160
|
+
});
|
|
39161
|
+
if (response.statusCode === 429 || response.statusCode >= 500) {
|
|
39162
|
+
observation.retries++;
|
|
39163
|
+
continue;
|
|
39164
|
+
}
|
|
39165
|
+
await rm6(path, { force: true }).catch(() => {
|
|
39166
|
+
});
|
|
39167
|
+
if (response.statusCode >= 200 && response.statusCode < 300) delivered++;
|
|
39168
|
+
else observation.dropped++;
|
|
39169
|
+
} catch {
|
|
39170
|
+
observation.retries++;
|
|
39171
|
+
}
|
|
39172
|
+
}
|
|
39173
|
+
}
|
|
39174
|
+
return delivered;
|
|
39175
|
+
}
|
|
39176
|
+
function startSessionEventRelay(app2, options2 = {}) {
|
|
39177
|
+
const root = options2.root ?? sessionEventSpoolRoot();
|
|
39178
|
+
let running = false;
|
|
39179
|
+
const tick6 = async () => {
|
|
39180
|
+
if (running) return;
|
|
39181
|
+
running = true;
|
|
39182
|
+
try {
|
|
39183
|
+
await drainSessionEventSpool({ inject: async (request) => app2.inject(request) }, root);
|
|
39184
|
+
} catch (error) {
|
|
39185
|
+
console.error("session event relay gagal:", error);
|
|
39186
|
+
} finally {
|
|
39187
|
+
running = false;
|
|
39188
|
+
}
|
|
39189
|
+
};
|
|
39190
|
+
const timer9 = setInterval(() => {
|
|
39191
|
+
void tick6();
|
|
39192
|
+
}, options2.intervalMs ?? 250);
|
|
39193
|
+
timer9.unref();
|
|
39194
|
+
app2.addHook("onClose", async () => {
|
|
39195
|
+
clearInterval(timer9);
|
|
39196
|
+
});
|
|
39197
|
+
void tick6();
|
|
39198
|
+
}
|
|
39199
|
+
|
|
39200
|
+
// src/services/agent-invocations.ts
|
|
39201
|
+
var MAX_EXCERPT_BYTES = 4096;
|
|
39202
|
+
var MAX_TRANSCRIPT_BYTES2 = 10 * 1024 * 1024;
|
|
39203
|
+
var ANSI2 = /[\u001b\u009b](?:\][^\u0007]*(?:\u0007|\u001b\\)|\[[0-?]*[ -/]*[@-~]|[@-_])/g;
|
|
39204
|
+
var snapshotHashes = /* @__PURE__ */ new Map();
|
|
39205
|
+
var keyOf = (x) => `${x.sessionId}\0${x.runtimeInvocationId}`;
|
|
39206
|
+
var hash2 = (value) => createHash8("sha256").update(value).digest("hex");
|
|
39207
|
+
var defaultGitStatus = (cwd) => {
|
|
39208
|
+
try {
|
|
39209
|
+
return execFileSync3("git", ["-C", cwd, "status", "--porcelain=v1", "-z"], {
|
|
39210
|
+
encoding: "utf8",
|
|
39211
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
39212
|
+
});
|
|
39213
|
+
} catch {
|
|
39214
|
+
return null;
|
|
39215
|
+
}
|
|
39216
|
+
};
|
|
39217
|
+
function snapshot2(cwd, run4 = defaultGitStatus) {
|
|
39218
|
+
try {
|
|
39219
|
+
const value = run4(cwd);
|
|
39220
|
+
return value === null ? null : hash2(value);
|
|
39221
|
+
} catch {
|
|
39222
|
+
return null;
|
|
39223
|
+
}
|
|
39224
|
+
}
|
|
39225
|
+
var stripAnsi = (value) => value.replace(ANSI2, "");
|
|
39226
|
+
var utf8Prefix = (value, maxBytes) => {
|
|
39227
|
+
let out4 = "", bytes = 0;
|
|
39228
|
+
for (const char of value) {
|
|
39229
|
+
const next = Buffer.byteLength(char, "utf8");
|
|
39230
|
+
if (bytes + next > maxBytes) break;
|
|
39231
|
+
out4 += char;
|
|
39232
|
+
bytes += next;
|
|
39233
|
+
}
|
|
39234
|
+
return out4;
|
|
39235
|
+
};
|
|
39236
|
+
var transcriptRoots = () => [
|
|
39237
|
+
resolve19(process.env.CLAUDE_CONFIG_DIR ?? `${homedir5()}/.claude`),
|
|
39238
|
+
resolve19(process.env.CODEX_HOME ?? `${homedir5()}/.codex`)
|
|
39239
|
+
];
|
|
39240
|
+
var inside = (path, root) => {
|
|
39241
|
+
const rel = relative2(root, path);
|
|
39242
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute6(rel);
|
|
39243
|
+
};
|
|
39244
|
+
var EMPTY_USAGE = { inputTokens: null, outputTokens: null, cachedTokens: null };
|
|
39245
|
+
var nonnegativeInt = (value) => Number.isSafeInteger(value) && Number(value) >= 0 ? Number(value) : null;
|
|
39246
|
+
function usageFromText(text) {
|
|
39247
|
+
const found = [];
|
|
39248
|
+
const inspect = (value) => {
|
|
39249
|
+
if (!value || typeof value !== "object") return;
|
|
39250
|
+
const record2 = value;
|
|
39251
|
+
const usage2 = record2.usage;
|
|
39252
|
+
if (usage2 && typeof usage2 === "object") {
|
|
39253
|
+
const u = usage2;
|
|
39254
|
+
found.push([
|
|
39255
|
+
nonnegativeInt(u.input_tokens ?? u.inputTokens) ?? -1,
|
|
39256
|
+
nonnegativeInt(u.output_tokens ?? u.outputTokens) ?? -1,
|
|
39257
|
+
nonnegativeInt(u.cached_tokens ?? u.cachedTokens ?? u.cache_read_input_tokens ?? u.cacheReadInputTokens) ?? -1
|
|
39258
|
+
]);
|
|
39259
|
+
}
|
|
39260
|
+
};
|
|
39261
|
+
for (const line of text.split("\n")) {
|
|
39262
|
+
if (!line.trim()) continue;
|
|
39263
|
+
try {
|
|
39264
|
+
inspect(JSON.parse(line));
|
|
39265
|
+
} catch {
|
|
39266
|
+
}
|
|
39267
|
+
}
|
|
39268
|
+
if (found.length === 0) return EMPTY_USAGE;
|
|
39269
|
+
const max = (index) => {
|
|
39270
|
+
const values = found.map((entry) => entry[index]).filter((n2) => n2 >= 0);
|
|
39271
|
+
return values.length ? Math.max(...values) : null;
|
|
39272
|
+
};
|
|
39273
|
+
return { inputTokens: max(0), outputTokens: max(1), cachedTokens: max(2) };
|
|
39274
|
+
}
|
|
39275
|
+
function transcriptUsage(path, roots = transcriptRoots()) {
|
|
39276
|
+
if (!path) return EMPTY_USAGE;
|
|
39277
|
+
try {
|
|
39278
|
+
const real2 = realpathSync5(path);
|
|
39279
|
+
const safeRoots = roots.map((root) => realpathSync5(root));
|
|
39280
|
+
if (!safeRoots.some((root) => inside(real2, root))) return EMPTY_USAGE;
|
|
39281
|
+
const info = statSync4(real2);
|
|
39282
|
+
if (!info.isFile() || info.size > MAX_TRANSCRIPT_BYTES2) return EMPTY_USAGE;
|
|
39283
|
+
return usageFromText(readFileSync9(real2, "utf8"));
|
|
39284
|
+
} catch {
|
|
39285
|
+
return EMPTY_USAGE;
|
|
39286
|
+
}
|
|
39287
|
+
}
|
|
39288
|
+
async function startAgentInvocation(input, io = {}) {
|
|
39289
|
+
const startedAt = input.startedAt ?? /* @__PURE__ */ new Date();
|
|
39290
|
+
const existing = await prisma.agentInvocation.findUnique({
|
|
39291
|
+
where: { sessionId_runtimeInvocationId: {
|
|
39292
|
+
sessionId: input.sessionId,
|
|
39293
|
+
runtimeInvocationId: input.runtimeInvocationId
|
|
39294
|
+
} }
|
|
39295
|
+
});
|
|
39296
|
+
const row = await prisma.agentInvocation.upsert({
|
|
39297
|
+
where: { sessionId_runtimeInvocationId: {
|
|
39298
|
+
sessionId: input.sessionId,
|
|
39299
|
+
runtimeInvocationId: input.runtimeInvocationId
|
|
39300
|
+
} },
|
|
39301
|
+
update: {},
|
|
39302
|
+
create: {
|
|
39303
|
+
id: randomUUID9(),
|
|
39304
|
+
sessionId: input.sessionId,
|
|
39305
|
+
projectId: input.projectId,
|
|
39306
|
+
specId: input.specId ?? null,
|
|
39307
|
+
runtime: input.runtime,
|
|
39308
|
+
runtimeInvocationId: input.runtimeInvocationId,
|
|
39309
|
+
customAgentId: input.customAgentId ?? null,
|
|
39310
|
+
agentName: input.agentName,
|
|
39311
|
+
model: input.model ?? null,
|
|
39312
|
+
definitionHash: input.definitionHash ?? null,
|
|
39313
|
+
phase: input.phase ?? null,
|
|
39314
|
+
effort: input.effort ?? null,
|
|
39315
|
+
status: "running",
|
|
39316
|
+
startedAt
|
|
39317
|
+
}
|
|
39318
|
+
});
|
|
39319
|
+
const before2 = snapshot2(input.cwd, io.gitStatus);
|
|
39320
|
+
if (before2 !== null) snapshotHashes.set(keyOf(input), before2);
|
|
39321
|
+
return { row, duplicate: existing !== null };
|
|
39322
|
+
}
|
|
39323
|
+
async function stopAgentInvocation(input, io = {}) {
|
|
39324
|
+
const unique = { sessionId_runtimeInvocationId: {
|
|
39325
|
+
sessionId: input.sessionId,
|
|
39326
|
+
runtimeInvocationId: input.runtimeInvocationId
|
|
39327
|
+
} };
|
|
39328
|
+
const existing = await prisma.agentInvocation.findUnique({ where: unique });
|
|
39329
|
+
if (existing?.endedAt) return { row: existing, duplicate: true };
|
|
39330
|
+
const endedAt = input.endedAt ?? /* @__PURE__ */ new Date();
|
|
39331
|
+
const startedAt = existing?.startedAt ?? endedAt;
|
|
39332
|
+
const cleanResult = input.result === void 0 ? null : stripAnsi(input.result);
|
|
39333
|
+
const usage2 = transcriptUsage(input.transcriptPath, io.transcriptRoots);
|
|
39334
|
+
const before2 = snapshotHashes.get(keyOf(input));
|
|
39335
|
+
const after = snapshot2(input.cwd, io.gitStatus);
|
|
39336
|
+
snapshotHashes.delete(keyOf(input));
|
|
39337
|
+
const evidence = {
|
|
39338
|
+
// Stop tanpa start lazim setelah restart server di tengah invocation. Waktu start dan status
|
|
39339
|
+
// runtime sudah hilang; simpan baris sintetis yang dapat diaudit tanpa mengarang durasi 0 ms.
|
|
39340
|
+
status: existing ? input.status ?? "completed" : "completed",
|
|
39341
|
+
endedAt,
|
|
39342
|
+
durationMs: existing ? Math.max(0, endedAt.getTime() - startedAt.getTime()) : null,
|
|
39343
|
+
...usage2,
|
|
39344
|
+
resultExcerpt: cleanResult === null ? null : utf8Prefix(cleanResult, MAX_EXCERPT_BYTES),
|
|
39345
|
+
resultHash: cleanResult === null ? null : hash2(cleanResult),
|
|
39346
|
+
workspaceChanged: before2 !== void 0 && after !== null && before2 !== after,
|
|
39347
|
+
// ADR-0164 · effort yang BENAR-BENAR dipakai runtime menang atas nilai start-time.
|
|
39348
|
+
...input.effort ? { effort: input.effort } : {}
|
|
39349
|
+
};
|
|
39350
|
+
if (existing) {
|
|
39351
|
+
const row2 = await prisma.agentInvocation.update({ where: { id: existing.id }, data: evidence });
|
|
39352
|
+
return { row: row2, duplicate: false };
|
|
39353
|
+
}
|
|
39354
|
+
const row = await prisma.agentInvocation.create({
|
|
39355
|
+
data: {
|
|
39356
|
+
id: randomUUID9(),
|
|
39357
|
+
sessionId: input.sessionId,
|
|
39358
|
+
projectId: input.projectId,
|
|
39359
|
+
specId: input.specId ?? null,
|
|
39360
|
+
runtime: input.runtime,
|
|
39361
|
+
runtimeInvocationId: input.runtimeInvocationId,
|
|
39362
|
+
customAgentId: input.customAgentId ?? null,
|
|
39363
|
+
agentName: input.agentName,
|
|
39364
|
+
model: input.model ?? null,
|
|
39365
|
+
definitionHash: input.definitionHash ?? null,
|
|
39366
|
+
phase: input.phase ?? null,
|
|
39367
|
+
startedAt,
|
|
39368
|
+
...evidence
|
|
39369
|
+
}
|
|
39370
|
+
});
|
|
39371
|
+
return { row, duplicate: false };
|
|
39372
|
+
}
|
|
39373
|
+
async function reconcileAgentInvocations(liveSessionIds) {
|
|
39374
|
+
const live = new Set(liveSessionIds);
|
|
39375
|
+
const open5 = await prisma.agentInvocation.findMany({ where: { status: "running" } });
|
|
39376
|
+
let changed = 0;
|
|
39377
|
+
const endedAt = /* @__PURE__ */ new Date();
|
|
39378
|
+
for (const row of open5) {
|
|
39379
|
+
if (live.has(row.sessionId)) continue;
|
|
39380
|
+
await prisma.agentInvocation.update({
|
|
39381
|
+
where: { id: row.id },
|
|
39382
|
+
data: {
|
|
39383
|
+
status: "abandoned",
|
|
39384
|
+
endedAt,
|
|
39385
|
+
durationMs: Math.max(0, endedAt.getTime() - row.startedAt.getTime())
|
|
39386
|
+
}
|
|
39387
|
+
});
|
|
39388
|
+
changed++;
|
|
39389
|
+
}
|
|
39390
|
+
return changed;
|
|
39391
|
+
}
|
|
39392
|
+
var agentInvocationView = (row) => ({
|
|
39393
|
+
id: row.id,
|
|
39394
|
+
sessionId: row.sessionId,
|
|
39395
|
+
projectId: row.projectId,
|
|
39396
|
+
specId: row.specId,
|
|
39397
|
+
runtime: row.runtime === "codex" ? "codex" : "claude",
|
|
39398
|
+
customAgentId: row.customAgentId,
|
|
39399
|
+
agentName: row.agentName,
|
|
39400
|
+
model: row.model,
|
|
39401
|
+
definitionHash: row.definitionHash,
|
|
39402
|
+
status: row.status,
|
|
39403
|
+
startedAt: row.startedAt.toISOString(),
|
|
39404
|
+
endedAt: row.endedAt?.toISOString() ?? null,
|
|
39405
|
+
durationMs: row.durationMs,
|
|
39406
|
+
inputTokens: row.inputTokens,
|
|
39407
|
+
outputTokens: row.outputTokens,
|
|
39408
|
+
cachedTokens: row.cachedTokens,
|
|
39409
|
+
resultExcerpt: row.resultExcerpt,
|
|
39410
|
+
resultHash: row.resultHash,
|
|
39411
|
+
workspaceChanged: row.workspaceChanged,
|
|
39412
|
+
disposition: AGENT_DISPOSITIONS.includes(row.disposition) ? row.disposition : "pending",
|
|
39413
|
+
dispositionNote: row.dispositionNote,
|
|
39414
|
+
evaluatedAt: row.evaluatedAt?.toISOString() ?? null,
|
|
39415
|
+
reworkRequired: row.reworkRequired
|
|
39416
|
+
});
|
|
39417
|
+
var median = (values) => {
|
|
39418
|
+
if (values.length === 0) return null;
|
|
39419
|
+
values.sort((a, b) => a - b);
|
|
39420
|
+
const middle = Math.floor(values.length / 2);
|
|
39421
|
+
return values.length % 2 === 1 ? values[middle] : (values[middle - 1] + values[middle]) / 2;
|
|
39422
|
+
};
|
|
39423
|
+
var availableSum = (values) => {
|
|
39424
|
+
const known = values.filter((value) => value !== null);
|
|
39425
|
+
return known.length ? known.reduce((sum, value) => sum + value, 0) : null;
|
|
39426
|
+
};
|
|
39427
|
+
async function agentMetrics(query2) {
|
|
39428
|
+
const where = {
|
|
39429
|
+
// ADR-0164 · telemetri agen fase milik terminal, bukan presisi katalog custom agent.
|
|
39430
|
+
phase: null,
|
|
39431
|
+
...query2.projectId ? { projectId: query2.projectId } : {},
|
|
39432
|
+
...query2.from || query2.to ? { startedAt: {
|
|
39433
|
+
...query2.from ? { gte: query2.from } : {},
|
|
39434
|
+
...query2.to ? { lte: query2.to } : {}
|
|
39435
|
+
} } : {}
|
|
39436
|
+
};
|
|
39437
|
+
const rows = await prisma.agentInvocation.findMany({ where, orderBy: { startedAt: "desc" } });
|
|
39438
|
+
const groups = /* @__PURE__ */ new Map();
|
|
39439
|
+
const variantGroups = /* @__PURE__ */ new Map();
|
|
39440
|
+
const add = (map, key, row) => {
|
|
39441
|
+
const group2 = map.get(key);
|
|
39442
|
+
if (group2) group2.push(row);
|
|
39443
|
+
else map.set(key, [row]);
|
|
39444
|
+
};
|
|
39445
|
+
for (const row of rows) {
|
|
39446
|
+
add(groups, row.agentName, row);
|
|
39447
|
+
add(variantGroups, JSON.stringify([row.agentName, row.runtime, row.model, row.definitionHash]), row);
|
|
39448
|
+
}
|
|
39449
|
+
const summarize = (agentName, invocations) => {
|
|
39450
|
+
const dispositions = { pending: 0, accepted: 0, partial: 0, rejected: 0, falsePositive: 0 };
|
|
39451
|
+
for (const row of invocations) {
|
|
39452
|
+
if (row.disposition === "accepted") dispositions.accepted++;
|
|
39453
|
+
else if (row.disposition === "partial") dispositions.partial++;
|
|
39454
|
+
else if (row.disposition === "rejected") dispositions.rejected++;
|
|
39455
|
+
else if (row.disposition === "false-positive") dispositions.falsePositive++;
|
|
39456
|
+
else dispositions.pending++;
|
|
39457
|
+
}
|
|
39458
|
+
const evaluated = dispositions.accepted + dispositions.partial + dispositions.rejected + dispositions.falsePositive;
|
|
39459
|
+
return {
|
|
39460
|
+
agentName,
|
|
39461
|
+
invocationCount: invocations.length,
|
|
39462
|
+
medianDurationMs: median(invocations.flatMap((row) => row.durationMs === null ? [] : [row.durationMs])),
|
|
39463
|
+
inputTokens: availableSum(invocations.map((row) => row.inputTokens)),
|
|
39464
|
+
outputTokens: availableSum(invocations.map((row) => row.outputTokens)),
|
|
39465
|
+
cachedTokens: availableSum(invocations.map((row) => row.cachedTokens)),
|
|
39466
|
+
dispositions,
|
|
39467
|
+
operationalPrecision: evaluated ? (dispositions.accepted + dispositions.partial) / evaluated : null,
|
|
39468
|
+
workspaceChanged: invocations.some((row) => row.workspaceChanged),
|
|
39469
|
+
evaluatedCount: evaluated,
|
|
39470
|
+
rework: {
|
|
39471
|
+
required: invocations.filter((row) => row.reworkRequired === true).length,
|
|
39472
|
+
notRequired: invocations.filter((row) => row.reworkRequired === false).length,
|
|
39473
|
+
unknown: invocations.filter((row) => row.reworkRequired === null).length
|
|
39474
|
+
}
|
|
39475
|
+
};
|
|
39476
|
+
};
|
|
39477
|
+
const agents = [...groups.entries()].map(([name2, group2]) => summarize(name2, group2)).sort((a, b) => a.agentName.localeCompare(b.agentName));
|
|
39478
|
+
const variants = [...variantGroups.values()].map((group2) => ({
|
|
39479
|
+
...summarize(group2[0].agentName, group2),
|
|
39480
|
+
runtime: group2[0].runtime === "codex" ? "codex" : "claude",
|
|
39481
|
+
model: group2[0].model,
|
|
39482
|
+
definitionHash: group2[0].definitionHash
|
|
39483
|
+
})).sort((a, b) => JSON.stringify([a.agentName, a.runtime, a.model, a.definitionHash]).localeCompare(JSON.stringify([b.agentName, b.runtime, b.model, b.definitionHash])));
|
|
39484
|
+
const lastEventAt = rows.reduce((latest, row) => Math.max(latest, row.startedAt.getTime(), row.endedAt?.getTime() ?? 0), 0);
|
|
39485
|
+
const sampleRows = /* @__PURE__ */ new Map();
|
|
39486
|
+
for (const group2 of groups.values()) {
|
|
39487
|
+
for (const category of [
|
|
39488
|
+
(row) => row.disposition === "pending",
|
|
39489
|
+
(row) => row.disposition !== "pending",
|
|
39490
|
+
(row) => row.reworkRequired === true
|
|
39491
|
+
]) {
|
|
39492
|
+
for (const row of group2.filter(category).slice(0, 2)) sampleRows.set(row.id, row);
|
|
39493
|
+
}
|
|
39494
|
+
}
|
|
39495
|
+
return {
|
|
39496
|
+
agents,
|
|
39497
|
+
variants,
|
|
39498
|
+
recent: rows.slice(0, 100).map(agentInvocationView),
|
|
39499
|
+
samples: [...sampleRows.values()].map(agentInvocationView),
|
|
39500
|
+
telemetry: {
|
|
39501
|
+
state: rows.length ? "observed" : "unobserved",
|
|
39502
|
+
lastEventAt: rows.length ? new Date(lastEventAt).toISOString() : null,
|
|
39503
|
+
incompleteCount: rows.filter((row) => !row.endedAt || row.resultHash === null).length,
|
|
39504
|
+
relay: sessionEventRelayStatus()
|
|
39505
|
+
}
|
|
39506
|
+
};
|
|
39507
|
+
}
|
|
39508
|
+
async function updateAgentInvocationDisposition(id2, disposition, note, reworkRequired) {
|
|
39509
|
+
const exists = await prisma.agentInvocation.findUnique({ where: { id: id2 } });
|
|
39510
|
+
if (!exists) return null;
|
|
39511
|
+
const row = await prisma.agentInvocation.update({
|
|
39512
|
+
where: { id: id2 },
|
|
39513
|
+
data: {
|
|
39514
|
+
disposition,
|
|
39515
|
+
dispositionNote: note?.trim() || null,
|
|
39516
|
+
evaluatedAt: /* @__PURE__ */ new Date(),
|
|
39517
|
+
...reworkRequired !== void 0 ? { reworkRequired } : {}
|
|
39518
|
+
}
|
|
39519
|
+
});
|
|
39520
|
+
return agentInvocationView(row);
|
|
39521
|
+
}
|
|
39522
|
+
async function listPhaseInvocations(sessionId2) {
|
|
39523
|
+
const rows = await prisma.agentInvocation.findMany({
|
|
39524
|
+
where: { sessionId: sessionId2, phase: { not: null } },
|
|
39525
|
+
orderBy: { startedAt: "asc" }
|
|
39526
|
+
});
|
|
39527
|
+
return rows.map((row) => ({
|
|
39528
|
+
phase: row.phase,
|
|
39529
|
+
runtimeInvocationId: row.runtimeInvocationId,
|
|
39530
|
+
status: row.status,
|
|
39531
|
+
startedAt: row.startedAt.toISOString(),
|
|
39532
|
+
durationMs: row.durationMs,
|
|
39533
|
+
inputTokens: row.inputTokens,
|
|
39534
|
+
outputTokens: row.outputTokens,
|
|
39535
|
+
cachedTokens: row.cachedTokens,
|
|
39536
|
+
resultExcerpt: row.resultExcerpt
|
|
39537
|
+
}));
|
|
39538
|
+
}
|
|
39539
|
+
|
|
39540
|
+
// src/services/phase-invocations.ts
|
|
39541
|
+
init_pty();
|
|
39542
|
+
async function refreshPhaseInvocations(sessionId2) {
|
|
39543
|
+
try {
|
|
39544
|
+
setPhaseInvocations(sessionId2, await listPhaseInvocations(sessionId2));
|
|
39545
|
+
} catch {
|
|
39546
|
+
}
|
|
39547
|
+
}
|
|
39548
|
+
|
|
38478
39549
|
// src/services/session-dialog.ts
|
|
38479
39550
|
init_tui_dialog();
|
|
38480
39551
|
init_pty();
|
|
38481
|
-
import { createHash as
|
|
39552
|
+
import { createHash as createHash9 } from "node:crypto";
|
|
38482
39553
|
var DIALOG_CHUNK_MS = 50;
|
|
38483
|
-
var screenHashOf = (paneText2) =>
|
|
39554
|
+
var screenHashOf = (paneText2) => createHash9("sha256").update(dialogKey(paneText2)).digest("hex").slice(0, 16);
|
|
38484
39555
|
var answerable = (paneText2) => {
|
|
38485
39556
|
const s2 = readDialogScreen(paneText2);
|
|
38486
39557
|
if (s2?.kind !== "question") return null;
|
|
@@ -38843,7 +39914,7 @@ function leadPrompt(q, c) {
|
|
|
38843
39914
|
// src/services/lead/verdict.ts
|
|
38844
39915
|
init_src();
|
|
38845
39916
|
import { existsSync as existsSync11 } from "node:fs";
|
|
38846
|
-
import { isAbsolute as
|
|
39917
|
+
import { isAbsolute as isAbsolute7, resolve as resolvePath, relative as relative3 } from "node:path";
|
|
38847
39918
|
function extractJsonBlock(raw) {
|
|
38848
39919
|
const re = /```json\s*\n([\s\S]*?)```/g;
|
|
38849
39920
|
let last = null;
|
|
@@ -38876,9 +39947,9 @@ function keepExistingRefs(refs2, repoDir) {
|
|
|
38876
39947
|
continue;
|
|
38877
39948
|
}
|
|
38878
39949
|
if (!repoDir) continue;
|
|
38879
|
-
if (
|
|
39950
|
+
if (isAbsolute7(ref)) continue;
|
|
38880
39951
|
const full = resolvePath(repoDir, ref);
|
|
38881
|
-
const rel =
|
|
39952
|
+
const rel = relative3(repoDir, full);
|
|
38882
39953
|
if (rel.startsWith("..")) continue;
|
|
38883
39954
|
if (existsSync11(full)) out4.push(ref);
|
|
38884
39955
|
}
|
|
@@ -39017,10 +40088,10 @@ init_config2();
|
|
|
39017
40088
|
init_pty();
|
|
39018
40089
|
init_session_sandbox();
|
|
39019
40090
|
import { execFile as execFile13 } from "node:child_process";
|
|
39020
|
-
import { randomUUID as
|
|
39021
|
-
import { mkdirSync as mkdirSync10, rmSync as rmSync6, writeFileSync as
|
|
40091
|
+
import { randomUUID as randomUUID10 } from "node:crypto";
|
|
40092
|
+
import { mkdirSync as mkdirSync10, rmSync as rmSync6, writeFileSync as writeFileSync8 } from "node:fs";
|
|
39022
40093
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
39023
|
-
import { join as
|
|
40094
|
+
import { join as join24 } from "node:path";
|
|
39024
40095
|
var binFor = (agent) => agent === "codex" ? effectiveStr("HANOMAN_CODEX_BIN") ?? "codex" : effectiveStr("HANOMAN_CLAUDE_BIN") ?? "claude";
|
|
39025
40096
|
function leadArgv(o) {
|
|
39026
40097
|
if (o.agent === "codex") {
|
|
@@ -39048,11 +40119,11 @@ function leadProcess(prompt, o, env = process.env) {
|
|
|
39048
40119
|
const mode = env.HANOMAN_SESSION_SANDBOX ?? (resolveHardening(env) ? "required" : "off");
|
|
39049
40120
|
if (mode === "off") return { file, args: directArgs, cwd: o.cwd, cleanup: () => {
|
|
39050
40121
|
} };
|
|
39051
|
-
const promptDir =
|
|
40122
|
+
const promptDir = join24(tmpdir3(), "hanoman-prompts");
|
|
39052
40123
|
mkdirSync10(promptDir, { recursive: true, mode: 448 });
|
|
39053
|
-
const promptFile =
|
|
39054
|
-
|
|
39055
|
-
const workspace = o.cwd ??
|
|
40124
|
+
const promptFile = join24(promptDir, `oneshot-${randomUUID10()}`);
|
|
40125
|
+
writeFileSync8(promptFile, prompt, { flag: "wx", mode: 384 });
|
|
40126
|
+
const workspace = o.cwd ?? join24(tmpdir3(), `hanoman-oneshot-${randomUUID10()}`);
|
|
39056
40127
|
if (!o.cwd) mkdirSync10(workspace, { recursive: false, mode: 448 });
|
|
39057
40128
|
try {
|
|
39058
40129
|
const argsWithoutPrompt = directArgs.slice(0, -1);
|
|
@@ -39935,6 +41006,9 @@ async function terminal_default(app2, opts) {
|
|
|
39935
41006
|
} catch (e) {
|
|
39936
41007
|
return { code: 422, body: { error: `gagal membuat worktree: ${e.message}` } };
|
|
39937
41008
|
}
|
|
41009
|
+
const brief = { id: project.id, name: project.name, desc: project.desc, stack: project.stack };
|
|
41010
|
+
const plan = sessionPhasePlan(await getSetting(), "reverse", agent2, { model: model2, effort: effort3 });
|
|
41011
|
+
const legacyPrompt = startProjectPrompt("reverse", brief, "reverse-docs") + resumeNote(reused);
|
|
39938
41012
|
const s3 = createSession(project.id, wt, {
|
|
39939
41013
|
id: id2,
|
|
39940
41014
|
flow: "reverse",
|
|
@@ -39943,12 +41017,9 @@ async function terminal_default(app2, opts) {
|
|
|
39943
41017
|
agent: agent2,
|
|
39944
41018
|
phaseFile: phaseFilePath(repoDir, id2),
|
|
39945
41019
|
decisionFile: decisionFilePath(repoDir, id2),
|
|
39946
|
-
prompt: startProjectPrompt("reverse",
|
|
39947
|
-
|
|
39948
|
-
|
|
39949
|
-
desc: project.desc,
|
|
39950
|
-
stack: project.stack
|
|
39951
|
-
}, "reverse-docs") + resumeNote(reused)
|
|
41020
|
+
prompt: plan ? startProjectPrompt("reverse", brief, "reverse-docs", plan) + resumeNote(reused) : legacyPrompt,
|
|
41021
|
+
legacyPrompt,
|
|
41022
|
+
phaseAgents: plan ? buildPhaseAgents(plan, { flow: "reverse", method: PROJECT_METHOD, context: projectContext(brief) }) : []
|
|
39952
41023
|
});
|
|
39953
41024
|
return { code: 201, body: { id: s3.id } };
|
|
39954
41025
|
}, (pane) => ({ code: 201, body: { id: pane.id } }));
|
|
@@ -39967,6 +41038,9 @@ async function terminal_default(app2, opts) {
|
|
|
39967
41038
|
} catch (e) {
|
|
39968
41039
|
return { code: 422, body: { error: `gagal membuat worktree: ${e.message}` } };
|
|
39969
41040
|
}
|
|
41041
|
+
const brief = { id: project.id, name: project.name, desc: project.desc, stack: project.stack };
|
|
41042
|
+
const plan = sessionPhasePlan(await getSetting(), "scaffold", agent2, { model: model2, effort: effort3 });
|
|
41043
|
+
const legacyPrompt = startScaffoldPrompt(brief, "scaffold-docs") + resumeNote(reused);
|
|
39970
41044
|
const s3 = createSession(project.id, wt, {
|
|
39971
41045
|
id: id2,
|
|
39972
41046
|
flow: "scaffold",
|
|
@@ -39975,10 +41049,9 @@ async function terminal_default(app2, opts) {
|
|
|
39975
41049
|
agent: agent2,
|
|
39976
41050
|
phaseFile: phaseFilePath(repoDir, id2),
|
|
39977
41051
|
decisionFile: decisionFilePath(repoDir, id2),
|
|
39978
|
-
prompt: startScaffoldPrompt(
|
|
39979
|
-
|
|
39980
|
-
|
|
39981
|
-
) + resumeNote(reused)
|
|
41052
|
+
prompt: plan ? startScaffoldPrompt(brief, "scaffold-docs", plan) + resumeNote(reused) : legacyPrompt,
|
|
41053
|
+
legacyPrompt,
|
|
41054
|
+
phaseAgents: plan ? buildPhaseAgents(plan, { flow: "scaffold", method: PROJECT_METHOD, context: scaffoldContext(brief) }) : []
|
|
39982
41055
|
});
|
|
39983
41056
|
return { code: 201, body: { id: s3.id } };
|
|
39984
41057
|
}, (pane) => ({ code: 201, body: { id: pane.id } }));
|
|
@@ -40000,6 +41073,10 @@ async function terminal_default(app2, opts) {
|
|
|
40000
41073
|
return { code: 422, body: { error: `gagal membuat worktree: ${e.message}` } };
|
|
40001
41074
|
}
|
|
40002
41075
|
const auditDoc = fromAudit ? await readAuditDoc(fromAudit) : null;
|
|
41076
|
+
const project_ = { id: project.id, name: project.name, desc: project.desc, stack: project.stack };
|
|
41077
|
+
const audit = auditDoc ? { id: fromAudit, path: auditDoc.path, content: auditDoc.content } : void 0;
|
|
41078
|
+
const plan = sessionPhasePlan(await getSetting(), "prd", agent2, { model: model2, effort: effort3 });
|
|
41079
|
+
const legacyPrompt = startPrdPrompt(project_, brief, `prd/${slug}`, audit) + resumeNote(reused);
|
|
40003
41080
|
const s3 = createSession(project.id, wt, {
|
|
40004
41081
|
id: id2,
|
|
40005
41082
|
flow: "prd",
|
|
@@ -40009,12 +41086,14 @@ async function terminal_default(app2, opts) {
|
|
|
40009
41086
|
agent: agent2,
|
|
40010
41087
|
phaseFile: phaseFilePath(repoDir, id2),
|
|
40011
41088
|
decisionFile: decisionFilePath(repoDir, id2),
|
|
40012
|
-
prompt: startPrdPrompt(
|
|
40013
|
-
|
|
40014
|
-
|
|
40015
|
-
|
|
40016
|
-
|
|
40017
|
-
|
|
41089
|
+
prompt: plan ? startPrdPrompt(project_, brief, `prd/${slug}`, audit, plan) + resumeNote(reused) : legacyPrompt,
|
|
41090
|
+
legacyPrompt,
|
|
41091
|
+
phaseAgents: plan ? buildPhaseAgents(plan, {
|
|
41092
|
+
flow: "prd",
|
|
41093
|
+
method: PROJECT_METHOD,
|
|
41094
|
+
context: prdContext(project_, brief, audit),
|
|
41095
|
+
prd: { slug }
|
|
41096
|
+
}) : []
|
|
40018
41097
|
});
|
|
40019
41098
|
return { code: 201, body: { id: s3.id } };
|
|
40020
41099
|
}, (pane) => ({ code: 201, body: { id: pane.id } }));
|
|
@@ -40040,6 +41119,10 @@ async function terminal_default(app2, opts) {
|
|
|
40040
41119
|
}
|
|
40041
41120
|
const titleM = content.match(/^#\s+(.+)$/m);
|
|
40042
41121
|
const title = titleM ? titleM[1].trim() : slug;
|
|
41122
|
+
const project_ = { id: project.id, name: project.name, desc: project.desc, stack: project.stack };
|
|
41123
|
+
const prd = { title, path: prdPath, content };
|
|
41124
|
+
const plan = sessionPhasePlan(await getSetting(), "breakdown", agent2, { model: model2, effort: effort3 });
|
|
41125
|
+
const legacyPrompt = startBreakdownPrompt(project_, prd, `breakdown/${slug}`) + resumeNote(reused);
|
|
40043
41126
|
const s3 = createSession(project.id, wt, {
|
|
40044
41127
|
id: id2,
|
|
40045
41128
|
flow: "breakdown",
|
|
@@ -40049,11 +41132,14 @@ async function terminal_default(app2, opts) {
|
|
|
40049
41132
|
agent: agent2,
|
|
40050
41133
|
phaseFile: phaseFilePath(repoDir, id2),
|
|
40051
41134
|
decisionFile: decisionFilePath(repoDir, id2),
|
|
40052
|
-
prompt: startBreakdownPrompt(
|
|
40053
|
-
|
|
40054
|
-
|
|
40055
|
-
|
|
40056
|
-
|
|
41135
|
+
prompt: plan ? startBreakdownPrompt(project_, prd, `breakdown/${slug}`, plan) + resumeNote(reused) : legacyPrompt,
|
|
41136
|
+
legacyPrompt,
|
|
41137
|
+
phaseAgents: plan ? buildPhaseAgents(plan, {
|
|
41138
|
+
flow: "breakdown",
|
|
41139
|
+
method: PROJECT_METHOD,
|
|
41140
|
+
context: breakdownContext(project_, prd),
|
|
41141
|
+
prd: { slug, title }
|
|
41142
|
+
}) : []
|
|
40057
41143
|
});
|
|
40058
41144
|
return { code: 201, body: { id: s3.id } };
|
|
40059
41145
|
}, (pane) => ({ code: 201, body: { id: pane.id } }));
|
|
@@ -40218,6 +41304,7 @@ async function terminal_default(app2, opts) {
|
|
|
40218
41304
|
const guard = new WsMessageGuard({ perWindow: TERMINAL_WS_MESSAGES_PER_MINUTE });
|
|
40219
41305
|
const client = { send: (m) => socket2.send(m), close: () => socket2.close() };
|
|
40220
41306
|
attach(id2, client);
|
|
41307
|
+
void refreshPhaseInvocations(id2);
|
|
40221
41308
|
const watch = createPrincipalWatch({
|
|
40222
41309
|
check: () => revalidateWsPrincipal(req, principal),
|
|
40223
41310
|
onRevoked: () => socket2.close(1008, "session revoked")
|
|
@@ -40347,8 +41434,8 @@ async function terminalWorkspaceRoutes(app2) {
|
|
|
40347
41434
|
// src/routes/vps.ts
|
|
40348
41435
|
init_db();
|
|
40349
41436
|
init_src();
|
|
40350
|
-
import { readFileSync as
|
|
40351
|
-
import { homedir as
|
|
41437
|
+
import { readFileSync as readFileSync14, existsSync as existsSync16 } from "node:fs";
|
|
41438
|
+
import { homedir as homedir7 } from "node:os";
|
|
40352
41439
|
|
|
40353
41440
|
// src/vps/catalog/catalog.data.ts
|
|
40354
41441
|
var RAW_SECTIONS = [{ "id": "ssh", "title": "SSH Hardening", "icon": "\u{1F511}", "count": 14 }, { "id": "firewall", "title": "Firewall & Network", "icon": "\u{1F525}", "count": 14 }, { "id": "aapanel", "title": "aaPanel Security", "icon": "\u{1F5A5}\uFE0F", "count": 14 }, { "id": "user", "title": "User & Permission Management", "icon": "\u{1F464}", "count": 14 }, { "id": "system", "title": "System Hardening (OS Level)", "icon": "\u2699\uFE0F", "count": 16 }, { "id": "webserver", "title": "Web Server Hardening", "icon": "\u{1F310}", "count": 14 }, { "id": "database", "title": "Database Security", "icon": "\u{1F5C4}\uFE0F", "count": 15 }, { "id": "ssl", "title": "SSL/TLS & Enkripsi", "icon": "\u{1F512}", "count": 13 }, { "id": "ids", "title": "Intrusion Detection & Prevention", "icon": "\u{1F6A8}", "count": 9 }, { "id": "logging", "title": "Logging & Monitoring", "icon": "\u{1F4CA}", "count": 13 }, { "id": "backup", "title": "Backup & Disaster Recovery", "icon": "\u{1F4BE}", "count": 12 }, { "id": "ddos", "title": "DDoS Protection", "icon": "\u{1F30A}", "count": 11 }, { "id": "kernel", "title": "Kernel Hardening", "icon": "\u{1F9E0}", "count": 12 }, { "id": "malware", "title": "File Integrity & Malware Detection", "icon": "\u{1F9A0}", "count": 7 }, { "id": "enterprise", "title": "Advanced Enterprise & Government Level", "icon": "\u{1F3E2}", "count": 31 }, { "id": "checklist", "title": "Checklist Rutin", "icon": "\u2705", "count": 23 }];
|
|
@@ -40545,14 +41632,14 @@ function resolveComponents(ids, profile) {
|
|
|
40545
41632
|
}
|
|
40546
41633
|
|
|
40547
41634
|
// src/services/vps-provision.ts
|
|
40548
|
-
import { readFileSync as
|
|
41635
|
+
import { readFileSync as readFileSync11 } from "node:fs";
|
|
40549
41636
|
|
|
40550
41637
|
// src/services/vps-ssh.ts
|
|
40551
41638
|
init_config2();
|
|
40552
41639
|
import { spawn as spawn4 } from "node:child_process";
|
|
40553
|
-
import { mkdtempSync, rmSync as rmSync7, writeFileSync as
|
|
41640
|
+
import { mkdtempSync, rmSync as rmSync7, writeFileSync as writeFileSync9 } from "node:fs";
|
|
40554
41641
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
40555
|
-
import { dirname as dirname11, join as
|
|
41642
|
+
import { dirname as dirname11, join as join25 } from "node:path";
|
|
40556
41643
|
var sshBin = () => effectiveStr("HANOMAN_SSH_BIN") ?? "ssh";
|
|
40557
41644
|
function consoleArgv(t) {
|
|
40558
41645
|
return [
|
|
@@ -40567,9 +41654,9 @@ function consoleArgv(t) {
|
|
|
40567
41654
|
];
|
|
40568
41655
|
}
|
|
40569
41656
|
function askpassScript() {
|
|
40570
|
-
const dir2 = mkdtempSync(
|
|
40571
|
-
const path =
|
|
40572
|
-
|
|
41657
|
+
const dir2 = mkdtempSync(join25(tmpdir4(), "hanoman-askpass-"));
|
|
41658
|
+
const path = join25(dir2, "askpass.sh");
|
|
41659
|
+
writeFileSync9(path, `#!/bin/sh
|
|
40573
41660
|
printf '%s' "$HANOMAN_SSH_PASSWORD"
|
|
40574
41661
|
`, { mode: 448 });
|
|
40575
41662
|
return path;
|
|
@@ -40628,17 +41715,17 @@ function sshExec(t, remoteCmd, opts = {}) {
|
|
|
40628
41715
|
|
|
40629
41716
|
// src/services/vps-audit.ts
|
|
40630
41717
|
init_db();
|
|
40631
|
-
import { existsSync as existsSync14, readFileSync as
|
|
40632
|
-
import { join as
|
|
41718
|
+
import { existsSync as existsSync14, readFileSync as readFileSync10 } from "node:fs";
|
|
41719
|
+
import { join as join27 } from "node:path";
|
|
40633
41720
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
40634
41721
|
|
|
40635
41722
|
// src/runner/deps.ts
|
|
40636
41723
|
import { existsSync as existsSync13 } from "node:fs";
|
|
40637
|
-
import { dirname as dirname12, join as
|
|
41724
|
+
import { dirname as dirname12, join as join26 } from "node:path";
|
|
40638
41725
|
function repoRootFrom(startDir) {
|
|
40639
41726
|
let dir2 = startDir;
|
|
40640
41727
|
for (let i = 0; i < 8; i++) {
|
|
40641
|
-
if (existsSync13(
|
|
41728
|
+
if (existsSync13(join26(dir2, "pnpm-workspace.yaml"))) return dir2;
|
|
40642
41729
|
const parent = dirname12(dir2);
|
|
40643
41730
|
if (parent === dir2) break;
|
|
40644
41731
|
dir2 = parent;
|
|
@@ -40751,7 +41838,7 @@ var packagedScript = (f) => fileURLToPath3(new URL(`../scripts/vps/${f}`, import
|
|
|
40751
41838
|
var moduleDir = () => fileURLToPath3(new URL(".", import.meta.url));
|
|
40752
41839
|
var scriptPath = (f) => {
|
|
40753
41840
|
const packed = packagedScript(f);
|
|
40754
|
-
return existsSync14(packed) ? packed :
|
|
41841
|
+
return existsSync14(packed) ? packed : join27(repoRoot(moduleDir()), "server", "scripts", "vps", f);
|
|
40755
41842
|
};
|
|
40756
41843
|
async function itemStatesOf(vpsId) {
|
|
40757
41844
|
const rows = await prisma.vpsItemState.findMany({ where: { vpsId } });
|
|
@@ -40761,7 +41848,7 @@ async function runAudit(v) {
|
|
|
40761
41848
|
const r = await sshExec(
|
|
40762
41849
|
target(v),
|
|
40763
41850
|
"sudo -n bash -s",
|
|
40764
|
-
{ stdin:
|
|
41851
|
+
{ stdin: readFileSync10(scriptPath("audit.sh"), "utf8"), timeoutMs: 6e4 }
|
|
40765
41852
|
);
|
|
40766
41853
|
const audit = parseAudit(r.out);
|
|
40767
41854
|
if (r.code !== 0 || audit.length === 0) return { ok: false, out: r.out };
|
|
@@ -40841,7 +41928,7 @@ function parseProvisionSteps(out4) {
|
|
|
40841
41928
|
return m ? [{ item: m[1], status: m[2], detail: (m[3] ?? "").trim() }] : [];
|
|
40842
41929
|
});
|
|
40843
41930
|
}
|
|
40844
|
-
var script = () =>
|
|
41931
|
+
var script = () => readFileSync11(scriptPath("provision.sh"), "utf8");
|
|
40845
41932
|
async function probeComponents(v) {
|
|
40846
41933
|
const r = await sshExec(v, "env MODE=probe bash -s", { stdin: script(), timeoutMs: PROBE_TIMEOUT_MS });
|
|
40847
41934
|
const components2 = parseComponents(r.out);
|
|
@@ -40868,7 +41955,7 @@ async function readSetupToken(v, home3 = "/var/lib/hanoman") {
|
|
|
40868
41955
|
}
|
|
40869
41956
|
|
|
40870
41957
|
// src/services/vps-remediate.ts
|
|
40871
|
-
import { readFileSync as
|
|
41958
|
+
import { readFileSync as readFileSync12 } from "node:fs";
|
|
40872
41959
|
function parseSteps(out4) {
|
|
40873
41960
|
return out4.split("\n").flatMap((line) => {
|
|
40874
41961
|
const m = line.match(/^STEP (\S+) (would|ok|fail)(?: (.*))?$/);
|
|
@@ -40879,7 +41966,7 @@ async function remediate(v, items, dryRun) {
|
|
|
40879
41966
|
const r = await sshExec(
|
|
40880
41967
|
v,
|
|
40881
41968
|
`sudo -n env ITEMS=${items.join(",")} SSH_PORT=${v.port} DRY_RUN=${dryRun ? "1" : ""} bash -s`,
|
|
40882
|
-
{ stdin:
|
|
41969
|
+
{ stdin: readFileSync12(scriptPath("remediate.sh"), "utf8"), timeoutMs: 3e5 }
|
|
40883
41970
|
);
|
|
40884
41971
|
return { ok: r.code === 0, steps: parseSteps(r.out), out: r.out };
|
|
40885
41972
|
}
|
|
@@ -40958,39 +42045,39 @@ async function buildChecklist(vpsId) {
|
|
|
40958
42045
|
// src/services/vps-key.ts
|
|
40959
42046
|
init_src2();
|
|
40960
42047
|
init_config2();
|
|
40961
|
-
import { execFileSync as
|
|
40962
|
-
import { chmodSync as chmodSync7, copyFileSync, existsSync as existsSync15, mkdirSync as mkdirSync11, readFileSync as
|
|
40963
|
-
import { homedir as
|
|
40964
|
-
import { join as
|
|
42048
|
+
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
42049
|
+
import { chmodSync as chmodSync7, copyFileSync, existsSync as existsSync15, mkdirSync as mkdirSync11, readFileSync as readFileSync13, unlinkSync as unlinkSync2 } from "node:fs";
|
|
42050
|
+
import { homedir as homedir6 } from "node:os";
|
|
42051
|
+
import { join as join28 } from "node:path";
|
|
40965
42052
|
var keyDir = () => effectiveStr("HANOMAN_SSH_KEY_DIR") ?? resolveDataDirs().sshKeys;
|
|
40966
42053
|
var KEY_FILES = ["id_ed25519", "id_ed25519.pub"];
|
|
40967
42054
|
function adoptLegacyKey(dir2) {
|
|
40968
42055
|
if (effectiveStr("HANOMAN_SSH_KEY_DIR")) return;
|
|
40969
|
-
const legacy =
|
|
42056
|
+
const legacy = join28(homedir6(), ".hanoman");
|
|
40970
42057
|
if (legacy === dir2) return;
|
|
40971
|
-
if (!KEY_FILES.every((f) => existsSync15(
|
|
42058
|
+
if (!KEY_FILES.every((f) => existsSync15(join28(legacy, f)))) return;
|
|
40972
42059
|
mkdirSync11(dir2, { recursive: true, mode: 448 });
|
|
40973
42060
|
for (const f of KEY_FILES) {
|
|
40974
|
-
copyFileSync(
|
|
40975
|
-
chmodSync7(
|
|
42061
|
+
copyFileSync(join28(legacy, f), join28(dir2, f));
|
|
42062
|
+
chmodSync7(join28(dir2, f), 384);
|
|
40976
42063
|
}
|
|
40977
|
-
for (const f of KEY_FILES) unlinkSync2(
|
|
42064
|
+
for (const f of KEY_FILES) unlinkSync2(join28(legacy, f));
|
|
40978
42065
|
console.log(`vps: key SSH dipindah dari ${legacy} ke ${dir2} (SPEC-846 \u2014 satu batas backup)`);
|
|
40979
42066
|
}
|
|
40980
42067
|
function ensureHanomanKey() {
|
|
40981
42068
|
const dir2 = keyDir();
|
|
40982
|
-
const privPath =
|
|
42069
|
+
const privPath = join28(dir2, KEY_FILES[0]);
|
|
40983
42070
|
const pubPath = `${privPath}.pub`;
|
|
40984
42071
|
if (!existsSync15(privPath)) adoptLegacyKey(dir2);
|
|
40985
42072
|
if (!existsSync15(privPath)) {
|
|
40986
42073
|
mkdirSync11(dir2, { recursive: true, mode: 448 });
|
|
40987
|
-
|
|
42074
|
+
execFileSync4(
|
|
40988
42075
|
"ssh-keygen",
|
|
40989
42076
|
["-t", "ed25519", "-N", "", "-C", "hanoman", "-f", privPath],
|
|
40990
42077
|
{ stdio: ["ignore", "ignore", "pipe"] }
|
|
40991
42078
|
);
|
|
40992
42079
|
}
|
|
40993
|
-
return { privPath, pubPath, pub:
|
|
42080
|
+
return { privPath, pubPath, pub: readFileSync13(pubPath, "utf8").trim() };
|
|
40994
42081
|
}
|
|
40995
42082
|
|
|
40996
42083
|
// src/services/vps-bootstrap.ts
|
|
@@ -41238,7 +42325,7 @@ async function vps_default(app2) {
|
|
|
41238
42325
|
const r = await sshExec(
|
|
41239
42326
|
v,
|
|
41240
42327
|
`sudo -n env SSH_PORT=${v.port} SSH_USER=${v.user} bash -s`,
|
|
41241
|
-
{ stdin:
|
|
42328
|
+
{ stdin: readFileSync14(scriptPath("harden.sh"), "utf8"), timeoutMs: 3e5 }
|
|
41242
42329
|
);
|
|
41243
42330
|
if (r.code !== 0) return reply.code(502).send({ error: "harden gagal lewat ssh", transcript: r.out });
|
|
41244
42331
|
const verify = await sshExec(v, "true", { timeoutMs: 3e4 });
|
|
@@ -41263,7 +42350,7 @@ async function vps_default(app2) {
|
|
|
41263
42350
|
const v = await prisma.vps.findUnique({ where: { id: req.params.id } });
|
|
41264
42351
|
if (!v) return reply.code(404).send({ error: "not found" });
|
|
41265
42352
|
if (keyMissing(v)) return reply.code(409).send({ error: "key VPS tidak ada di mesin ini", keyMissing: true });
|
|
41266
|
-
const s2 = await createOperatorSession(`vps-console:${v.id}`,
|
|
42353
|
+
const s2 = await createOperatorSession(`vps-console:${v.id}`, homedir7(), { id: `vpsc-${v.id}`, command: consoleArgv(v) });
|
|
41267
42354
|
return reply.code(201).send({ id: s2.id });
|
|
41268
42355
|
});
|
|
41269
42356
|
app2.post("/vps/:id/session", async (req, reply) => {
|
|
@@ -41272,7 +42359,7 @@ async function vps_default(app2) {
|
|
|
41272
42359
|
if (keyMissing(v)) return reply.code(409).send({ error: "key VPS tidak ada di mesin ini", keyMissing: true });
|
|
41273
42360
|
const checks = v.audit ?? [];
|
|
41274
42361
|
const { model, effort: effort2 } = await sessionModel();
|
|
41275
|
-
const s2 = await createAgentSession(`vps:${v.id}`,
|
|
42362
|
+
const s2 = await createAgentSession(`vps:${v.id}`, homedir7(), {
|
|
41276
42363
|
model,
|
|
41277
42364
|
effort: effort2,
|
|
41278
42365
|
prompt: [
|
|
@@ -41289,10 +42376,10 @@ async function vps_default(app2) {
|
|
|
41289
42376
|
|
|
41290
42377
|
// src/services/limits.ts
|
|
41291
42378
|
init_config2();
|
|
41292
|
-
import { execFileSync as
|
|
41293
|
-
import { readFileSync as
|
|
41294
|
-
import { homedir as
|
|
41295
|
-
import { join as
|
|
42379
|
+
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
42380
|
+
import { readFileSync as readFileSync15 } from "node:fs";
|
|
42381
|
+
import { homedir as homedir8 } from "node:os";
|
|
42382
|
+
import { join as join29 } from "node:path";
|
|
41296
42383
|
var USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
|
|
41297
42384
|
var TTL_MS2 = 3e4;
|
|
41298
42385
|
var lastOk = null;
|
|
@@ -41305,12 +42392,12 @@ var LABELS = {
|
|
|
41305
42392
|
var humanize = (k) => k.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
41306
42393
|
var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
41307
42394
|
function credsFile() {
|
|
41308
|
-
return
|
|
42395
|
+
return join29(effectiveStr("CLAUDE_CONFIG_DIR") ?? join29(homedir8(), ".claude"), ".credentials.json");
|
|
41309
42396
|
}
|
|
41310
42397
|
function readAccessToken() {
|
|
41311
42398
|
if (process.platform === "darwin" && !effectiveStr("CLAUDE_CONFIG_DIR")) {
|
|
41312
42399
|
try {
|
|
41313
|
-
const blob =
|
|
42400
|
+
const blob = execFileSync5(
|
|
41314
42401
|
"security",
|
|
41315
42402
|
["find-generic-password", "-s", "Claude Code-credentials", "-w"],
|
|
41316
42403
|
{ encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }
|
|
@@ -41321,7 +42408,7 @@ function readAccessToken() {
|
|
|
41321
42408
|
}
|
|
41322
42409
|
}
|
|
41323
42410
|
try {
|
|
41324
|
-
return JSON.parse(
|
|
42411
|
+
return JSON.parse(readFileSync15(credsFile(), "utf8"))?.claudeAiOauth?.accessToken ?? null;
|
|
41325
42412
|
} catch {
|
|
41326
42413
|
return null;
|
|
41327
42414
|
}
|
|
@@ -41377,16 +42464,16 @@ async function getLimits() {
|
|
|
41377
42464
|
}
|
|
41378
42465
|
|
|
41379
42466
|
// src/services/codex-limits.ts
|
|
41380
|
-
import { open as
|
|
41381
|
-
import { homedir as
|
|
41382
|
-
import { join as
|
|
42467
|
+
import { open as open4, readdir as readdir7, stat as stat3 } from "node:fs/promises";
|
|
42468
|
+
import { homedir as homedir9 } from "node:os";
|
|
42469
|
+
import { join as join30 } from "node:path";
|
|
41383
42470
|
var TTL_MS3 = 3e4;
|
|
41384
42471
|
var STALE_AFTER_MS = 12 * 36e5;
|
|
41385
42472
|
var TAIL_BYTES = 512 * 1024;
|
|
41386
42473
|
var MAX_FILES = 8;
|
|
41387
42474
|
var cache2 = null;
|
|
41388
42475
|
var freshUntil2 = 0;
|
|
41389
|
-
var codexSessionsDir = () =>
|
|
42476
|
+
var codexSessionsDir = () => join30(process.env.CODEX_HOME ?? join30(homedir9(), ".codex"), "sessions");
|
|
41390
42477
|
var UNAVAILABLE = { status: "unavailable", windows: [], fetchedAt: null, plan: null };
|
|
41391
42478
|
function windowLabel(minutes) {
|
|
41392
42479
|
if (minutes === 300) return "Sesi 5 jam";
|
|
@@ -41414,12 +42501,12 @@ function toWindow(slot, raw, reached) {
|
|
|
41414
42501
|
async function recentRollouts(dir2) {
|
|
41415
42502
|
let entries3;
|
|
41416
42503
|
try {
|
|
41417
|
-
entries3 = (await
|
|
42504
|
+
entries3 = (await readdir7(dir2, { recursive: true })).filter((f) => f.endsWith(".jsonl"));
|
|
41418
42505
|
} catch {
|
|
41419
42506
|
return [];
|
|
41420
42507
|
}
|
|
41421
42508
|
const stamped = await Promise.all(entries3.map(async (rel) => {
|
|
41422
|
-
const full =
|
|
42509
|
+
const full = join30(dir2, rel);
|
|
41423
42510
|
try {
|
|
41424
42511
|
return { full, mtime: (await stat3(full)).mtimeMs };
|
|
41425
42512
|
} catch {
|
|
@@ -41429,7 +42516,7 @@ async function recentRollouts(dir2) {
|
|
|
41429
42516
|
return stamped.filter((x) => x !== null).sort((a, b) => b.mtime - a.mtime).slice(0, MAX_FILES).map((x) => x.full);
|
|
41430
42517
|
}
|
|
41431
42518
|
async function readTail(file) {
|
|
41432
|
-
const fh = await
|
|
42519
|
+
const fh = await open4(file, "r");
|
|
41433
42520
|
try {
|
|
41434
42521
|
const { size } = await fh.stat();
|
|
41435
42522
|
const len = Math.min(size, TAIL_BYTES);
|
|
@@ -41505,8 +42592,8 @@ async function codex(app2) {
|
|
|
41505
42592
|
// src/services/model-catalog.ts
|
|
41506
42593
|
init_src2();
|
|
41507
42594
|
init_src();
|
|
41508
|
-
import { readFile as readFile5, writeFile as writeFile5, rename as rename4, mkdir as
|
|
41509
|
-
import { dirname as dirname13, join as
|
|
42595
|
+
import { readFile as readFile5, writeFile as writeFile5, rename as rename4, mkdir as mkdir7 } from "node:fs/promises";
|
|
42596
|
+
import { dirname as dirname13, join as join32 } from "node:path";
|
|
41510
42597
|
|
|
41511
42598
|
// src/services/model-catalog-parser.ts
|
|
41512
42599
|
init_zod();
|
|
@@ -41581,9 +42668,9 @@ function parseCachedCatalog(raw) {
|
|
|
41581
42668
|
init_config2();
|
|
41582
42669
|
init_session_sandbox();
|
|
41583
42670
|
import { spawn as spawn5 } from "node:child_process";
|
|
41584
|
-
import { mkdtemp as mkdtemp2, rm as
|
|
42671
|
+
import { mkdtemp as mkdtemp2, rm as rm7 } from "node:fs/promises";
|
|
41585
42672
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
41586
|
-
import { join as
|
|
42673
|
+
import { join as join31 } from "node:path";
|
|
41587
42674
|
var MAX_BYTES = 4 * 1024 * 1024;
|
|
41588
42675
|
var TIMEOUT_MS = 2e4;
|
|
41589
42676
|
var quote2 = (s2) => "'" + s2.replaceAll("'", "'\\''") + "'";
|
|
@@ -41679,7 +42766,7 @@ function runCatalogCommand(command, agent, cwd, env, timeoutMs = TIMEOUT_MS) {
|
|
|
41679
42766
|
});
|
|
41680
42767
|
}
|
|
41681
42768
|
async function probeModelCatalog(agent) {
|
|
41682
|
-
const cwd = await mkdtemp2(
|
|
42769
|
+
const cwd = await mkdtemp2(join31(tmpdir5(), "hanoman-models-"));
|
|
41683
42770
|
const env = { ...process.env };
|
|
41684
42771
|
for (const key of ["HANOMAN_CLAUDE_BIN", "HANOMAN_CODEX_BIN", "HANOMAN_PODMAN_BIN"]) {
|
|
41685
42772
|
const value = effectiveStr(key);
|
|
@@ -41692,7 +42779,7 @@ async function probeModelCatalog(agent) {
|
|
|
41692
42779
|
const raw = await runCatalogCommand(catalogCommand(agent, cwd, env), agent, cwd, env);
|
|
41693
42780
|
return agent === "claude" ? parseClaudeModels(raw) : parseCodexModels(raw);
|
|
41694
42781
|
} finally {
|
|
41695
|
-
await
|
|
42782
|
+
await rm7(cwd, { recursive: true, force: true });
|
|
41696
42783
|
}
|
|
41697
42784
|
}
|
|
41698
42785
|
|
|
@@ -41756,7 +42843,7 @@ function createModelCatalogService(deps) {
|
|
|
41756
42843
|
}
|
|
41757
42844
|
return { snapshot: () => state, refresh };
|
|
41758
42845
|
}
|
|
41759
|
-
var cacheFile = () =>
|
|
42846
|
+
var cacheFile = () => join32(resolveHome(), "model-catalog.json");
|
|
41760
42847
|
var modelCatalogService = createModelCatalogService({
|
|
41761
42848
|
probe: probeModelCatalog,
|
|
41762
42849
|
read: async () => {
|
|
@@ -41766,7 +42853,7 @@ var modelCatalogService = createModelCatalogService({
|
|
|
41766
42853
|
},
|
|
41767
42854
|
write: async (catalog) => {
|
|
41768
42855
|
const file = cacheFile();
|
|
41769
|
-
await
|
|
42856
|
+
await mkdir7(dirname13(file), { recursive: true });
|
|
41770
42857
|
const temp = file + "." + process.pid + ".tmp";
|
|
41771
42858
|
await writeFile5(temp, JSON.stringify(catalog), { mode: 384 });
|
|
41772
42859
|
await rename4(temp, file);
|
|
@@ -41799,8 +42886,8 @@ init_src();
|
|
|
41799
42886
|
// src/services/update.ts
|
|
41800
42887
|
init_src();
|
|
41801
42888
|
init_config2();
|
|
41802
|
-
import { readFileSync as
|
|
41803
|
-
import { resolve as
|
|
42889
|
+
import { readFileSync as readFileSync16 } from "node:fs";
|
|
42890
|
+
import { resolve as resolve20, dirname as dirname14 } from "node:path";
|
|
41804
42891
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
41805
42892
|
var UPDATE_COMMAND = "npm i -g hanoman@latest --prefer-online";
|
|
41806
42893
|
function composeUpdate(x) {
|
|
@@ -41826,9 +42913,9 @@ var lastLatest = null;
|
|
|
41826
42913
|
var lastStatus = "unavailable";
|
|
41827
42914
|
function runningVersion() {
|
|
41828
42915
|
const here = dirname14(fileURLToPath4(import.meta.url));
|
|
41829
|
-
for (const p3 of [
|
|
42916
|
+
for (const p3 of [resolve20(here, "build-info.json"), resolve20(here, "../package.json")]) {
|
|
41830
42917
|
try {
|
|
41831
|
-
const v = JSON.parse(
|
|
42918
|
+
const v = JSON.parse(readFileSync16(p3, "utf8")).version;
|
|
41832
42919
|
if (typeof v === "string" && v) return v;
|
|
41833
42920
|
} catch {
|
|
41834
42921
|
}
|
|
@@ -43443,468 +44530,6 @@ async function session_history_default(app2) {
|
|
|
43443
44530
|
init_src();
|
|
43444
44531
|
init_session_event_token();
|
|
43445
44532
|
init_pty();
|
|
43446
|
-
|
|
43447
|
-
// src/services/agent-invocations.ts
|
|
43448
|
-
init_src();
|
|
43449
|
-
init_db();
|
|
43450
|
-
import { createHash as createHash9, randomUUID as randomUUID10 } from "node:crypto";
|
|
43451
|
-
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
43452
|
-
import { homedir as homedir9 } from "node:os";
|
|
43453
|
-
import { readFileSync as readFileSync16, realpathSync as realpathSync5, statSync as statSync4 } from "node:fs";
|
|
43454
|
-
import { isAbsolute as isAbsolute7, relative as relative3, resolve as resolve20 } from "node:path";
|
|
43455
|
-
|
|
43456
|
-
// src/services/session-event-relay.ts
|
|
43457
|
-
init_session_event_token();
|
|
43458
|
-
init_session_event_spool();
|
|
43459
|
-
import { constants as constants3 } from "node:fs";
|
|
43460
|
-
import { mkdir as mkdir7, open as open4, readdir as readdir7, rm as rm7 } from "node:fs/promises";
|
|
43461
|
-
import { join as join31 } from "node:path";
|
|
43462
|
-
var MAX_EVENT_BYTES = 1e6;
|
|
43463
|
-
var MAX_FILES_PER_DRAIN = 1e3;
|
|
43464
|
-
var SESSION_ID_RE = /^[a-z0-9_-]+$/;
|
|
43465
|
-
var observations = /* @__PURE__ */ new Map();
|
|
43466
|
-
function sessionEventRelayStatus(root = sessionEventSpoolRoot()) {
|
|
43467
|
-
return { ...observations.get(root) ?? {
|
|
43468
|
-
state: "unobserved",
|
|
43469
|
-
checkedAt: null,
|
|
43470
|
-
lastDeliveryAt: null,
|
|
43471
|
-
lastIssueAt: null,
|
|
43472
|
-
retryPending: 0,
|
|
43473
|
-
retryAttempts: 0,
|
|
43474
|
-
droppedEvents: 0
|
|
43475
|
-
} };
|
|
43476
|
-
}
|
|
43477
|
-
async function drainSessionEventSpool(app2, root = sessionEventSpoolRoot()) {
|
|
43478
|
-
const previous = sessionEventRelayStatus(root);
|
|
43479
|
-
const current = { retries: 0, dropped: 0 };
|
|
43480
|
-
let delivered = 0;
|
|
43481
|
-
let failed = false;
|
|
43482
|
-
try {
|
|
43483
|
-
delivered = await drainSpool(app2, root, current);
|
|
43484
|
-
return delivered;
|
|
43485
|
-
} catch (error) {
|
|
43486
|
-
failed = true;
|
|
43487
|
-
throw error;
|
|
43488
|
-
} finally {
|
|
43489
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
43490
|
-
observations.set(root, {
|
|
43491
|
-
state: failed || current.retries > 0 ? "degraded" : "ready",
|
|
43492
|
-
checkedAt: now,
|
|
43493
|
-
lastDeliveryAt: delivered ? now : previous.lastDeliveryAt,
|
|
43494
|
-
lastIssueAt: failed || current.retries || current.dropped ? now : previous.lastIssueAt,
|
|
43495
|
-
retryPending: current.retries,
|
|
43496
|
-
retryAttempts: previous.retryAttempts + current.retries,
|
|
43497
|
-
droppedEvents: previous.droppedEvents + current.dropped
|
|
43498
|
-
});
|
|
43499
|
-
}
|
|
43500
|
-
}
|
|
43501
|
-
async function drainSpool(app2, root, observation) {
|
|
43502
|
-
await mkdir7(root, { recursive: true, mode: 448 });
|
|
43503
|
-
let delivered = 0;
|
|
43504
|
-
let examined = 0;
|
|
43505
|
-
let readBuffer;
|
|
43506
|
-
for (const session of await readdir7(root, { withFileTypes: true })) {
|
|
43507
|
-
if (!session.isDirectory() || !SESSION_ID_RE.test(session.name)) continue;
|
|
43508
|
-
const dir2 = join31(root, session.name);
|
|
43509
|
-
let entries3;
|
|
43510
|
-
try {
|
|
43511
|
-
entries3 = await readdir7(dir2, { withFileTypes: true });
|
|
43512
|
-
} catch {
|
|
43513
|
-
continue;
|
|
43514
|
-
}
|
|
43515
|
-
for (const entry of entries3) {
|
|
43516
|
-
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
43517
|
-
if (++examined > MAX_FILES_PER_DRAIN) return delivered;
|
|
43518
|
-
const path = join31(dir2, entry.name);
|
|
43519
|
-
let payload;
|
|
43520
|
-
let handle;
|
|
43521
|
-
try {
|
|
43522
|
-
handle = await open4(path, constants3.O_RDONLY | constants3.O_NOFOLLOW);
|
|
43523
|
-
const stat4 = await handle.stat();
|
|
43524
|
-
if (!stat4.isFile() || stat4.size > MAX_EVENT_BYTES) throw new Error("payload terlalu besar");
|
|
43525
|
-
readBuffer ??= Buffer.allocUnsafe(MAX_EVENT_BYTES + 1);
|
|
43526
|
-
const { bytesRead } = await handle.read(readBuffer, 0, readBuffer.length, 0);
|
|
43527
|
-
if (bytesRead > MAX_EVENT_BYTES) throw new Error("payload terlalu besar");
|
|
43528
|
-
const parsed = JSON.parse(readBuffer.subarray(0, bytesRead).toString("utf8"));
|
|
43529
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
43530
|
-
throw new Error("payload event bukan object");
|
|
43531
|
-
}
|
|
43532
|
-
payload = parsed;
|
|
43533
|
-
} catch {
|
|
43534
|
-
observation.dropped++;
|
|
43535
|
-
await rm7(path, { force: true }).catch(() => {
|
|
43536
|
-
});
|
|
43537
|
-
continue;
|
|
43538
|
-
} finally {
|
|
43539
|
-
await handle?.close().catch(() => {
|
|
43540
|
-
});
|
|
43541
|
-
}
|
|
43542
|
-
try {
|
|
43543
|
-
const response = await app2.inject({
|
|
43544
|
-
method: "POST",
|
|
43545
|
-
url: "/api/session-events",
|
|
43546
|
-
headers: {
|
|
43547
|
-
authorization: `Bearer ${sessionEventToken(session.name)}`,
|
|
43548
|
-
"x-hanoman-session": session.name
|
|
43549
|
-
},
|
|
43550
|
-
payload
|
|
43551
|
-
});
|
|
43552
|
-
if (response.statusCode === 429 || response.statusCode >= 500) {
|
|
43553
|
-
observation.retries++;
|
|
43554
|
-
continue;
|
|
43555
|
-
}
|
|
43556
|
-
await rm7(path, { force: true }).catch(() => {
|
|
43557
|
-
});
|
|
43558
|
-
if (response.statusCode >= 200 && response.statusCode < 300) delivered++;
|
|
43559
|
-
else observation.dropped++;
|
|
43560
|
-
} catch {
|
|
43561
|
-
observation.retries++;
|
|
43562
|
-
}
|
|
43563
|
-
}
|
|
43564
|
-
}
|
|
43565
|
-
return delivered;
|
|
43566
|
-
}
|
|
43567
|
-
function startSessionEventRelay(app2, options2 = {}) {
|
|
43568
|
-
const root = options2.root ?? sessionEventSpoolRoot();
|
|
43569
|
-
let running = false;
|
|
43570
|
-
const tick6 = async () => {
|
|
43571
|
-
if (running) return;
|
|
43572
|
-
running = true;
|
|
43573
|
-
try {
|
|
43574
|
-
await drainSessionEventSpool({ inject: async (request) => app2.inject(request) }, root);
|
|
43575
|
-
} catch (error) {
|
|
43576
|
-
console.error("session event relay gagal:", error);
|
|
43577
|
-
} finally {
|
|
43578
|
-
running = false;
|
|
43579
|
-
}
|
|
43580
|
-
};
|
|
43581
|
-
const timer9 = setInterval(() => {
|
|
43582
|
-
void tick6();
|
|
43583
|
-
}, options2.intervalMs ?? 250);
|
|
43584
|
-
timer9.unref();
|
|
43585
|
-
app2.addHook("onClose", async () => {
|
|
43586
|
-
clearInterval(timer9);
|
|
43587
|
-
});
|
|
43588
|
-
void tick6();
|
|
43589
|
-
}
|
|
43590
|
-
|
|
43591
|
-
// src/services/agent-invocations.ts
|
|
43592
|
-
var MAX_EXCERPT_BYTES = 4096;
|
|
43593
|
-
var MAX_TRANSCRIPT_BYTES2 = 10 * 1024 * 1024;
|
|
43594
|
-
var ANSI2 = /[\u001b\u009b](?:\][^\u0007]*(?:\u0007|\u001b\\)|\[[0-?]*[ -/]*[@-~]|[@-_])/g;
|
|
43595
|
-
var snapshotHashes = /* @__PURE__ */ new Map();
|
|
43596
|
-
var keyOf = (x) => `${x.sessionId}\0${x.runtimeInvocationId}`;
|
|
43597
|
-
var hash2 = (value) => createHash9("sha256").update(value).digest("hex");
|
|
43598
|
-
var defaultGitStatus = (cwd) => {
|
|
43599
|
-
try {
|
|
43600
|
-
return execFileSync5("git", ["-C", cwd, "status", "--porcelain=v1", "-z"], {
|
|
43601
|
-
encoding: "utf8",
|
|
43602
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
43603
|
-
});
|
|
43604
|
-
} catch {
|
|
43605
|
-
return null;
|
|
43606
|
-
}
|
|
43607
|
-
};
|
|
43608
|
-
function snapshot2(cwd, run4 = defaultGitStatus) {
|
|
43609
|
-
try {
|
|
43610
|
-
const value = run4(cwd);
|
|
43611
|
-
return value === null ? null : hash2(value);
|
|
43612
|
-
} catch {
|
|
43613
|
-
return null;
|
|
43614
|
-
}
|
|
43615
|
-
}
|
|
43616
|
-
var stripAnsi = (value) => value.replace(ANSI2, "");
|
|
43617
|
-
var utf8Prefix = (value, maxBytes) => {
|
|
43618
|
-
let out4 = "", bytes = 0;
|
|
43619
|
-
for (const char of value) {
|
|
43620
|
-
const next = Buffer.byteLength(char, "utf8");
|
|
43621
|
-
if (bytes + next > maxBytes) break;
|
|
43622
|
-
out4 += char;
|
|
43623
|
-
bytes += next;
|
|
43624
|
-
}
|
|
43625
|
-
return out4;
|
|
43626
|
-
};
|
|
43627
|
-
var transcriptRoots = () => [
|
|
43628
|
-
resolve20(process.env.CLAUDE_CONFIG_DIR ?? `${homedir9()}/.claude`),
|
|
43629
|
-
resolve20(process.env.CODEX_HOME ?? `${homedir9()}/.codex`)
|
|
43630
|
-
];
|
|
43631
|
-
var inside = (path, root) => {
|
|
43632
|
-
const rel = relative3(root, path);
|
|
43633
|
-
return rel === "" || !rel.startsWith("..") && !isAbsolute7(rel);
|
|
43634
|
-
};
|
|
43635
|
-
var EMPTY_USAGE = { inputTokens: null, outputTokens: null, cachedTokens: null };
|
|
43636
|
-
var nonnegativeInt = (value) => Number.isSafeInteger(value) && Number(value) >= 0 ? Number(value) : null;
|
|
43637
|
-
function usageFromText(text) {
|
|
43638
|
-
const found = [];
|
|
43639
|
-
const inspect = (value) => {
|
|
43640
|
-
if (!value || typeof value !== "object") return;
|
|
43641
|
-
const record2 = value;
|
|
43642
|
-
const usage2 = record2.usage;
|
|
43643
|
-
if (usage2 && typeof usage2 === "object") {
|
|
43644
|
-
const u = usage2;
|
|
43645
|
-
found.push([
|
|
43646
|
-
nonnegativeInt(u.input_tokens ?? u.inputTokens) ?? -1,
|
|
43647
|
-
nonnegativeInt(u.output_tokens ?? u.outputTokens) ?? -1,
|
|
43648
|
-
nonnegativeInt(u.cached_tokens ?? u.cachedTokens ?? u.cache_read_input_tokens ?? u.cacheReadInputTokens) ?? -1
|
|
43649
|
-
]);
|
|
43650
|
-
}
|
|
43651
|
-
};
|
|
43652
|
-
for (const line of text.split("\n")) {
|
|
43653
|
-
if (!line.trim()) continue;
|
|
43654
|
-
try {
|
|
43655
|
-
inspect(JSON.parse(line));
|
|
43656
|
-
} catch {
|
|
43657
|
-
}
|
|
43658
|
-
}
|
|
43659
|
-
if (found.length === 0) return EMPTY_USAGE;
|
|
43660
|
-
const max = (index) => {
|
|
43661
|
-
const values = found.map((entry) => entry[index]).filter((n2) => n2 >= 0);
|
|
43662
|
-
return values.length ? Math.max(...values) : null;
|
|
43663
|
-
};
|
|
43664
|
-
return { inputTokens: max(0), outputTokens: max(1), cachedTokens: max(2) };
|
|
43665
|
-
}
|
|
43666
|
-
function transcriptUsage(path, roots = transcriptRoots()) {
|
|
43667
|
-
if (!path) return EMPTY_USAGE;
|
|
43668
|
-
try {
|
|
43669
|
-
const real2 = realpathSync5(path);
|
|
43670
|
-
const safeRoots = roots.map((root) => realpathSync5(root));
|
|
43671
|
-
if (!safeRoots.some((root) => inside(real2, root))) return EMPTY_USAGE;
|
|
43672
|
-
const info = statSync4(real2);
|
|
43673
|
-
if (!info.isFile() || info.size > MAX_TRANSCRIPT_BYTES2) return EMPTY_USAGE;
|
|
43674
|
-
return usageFromText(readFileSync16(real2, "utf8"));
|
|
43675
|
-
} catch {
|
|
43676
|
-
return EMPTY_USAGE;
|
|
43677
|
-
}
|
|
43678
|
-
}
|
|
43679
|
-
async function startAgentInvocation(input, io = {}) {
|
|
43680
|
-
const startedAt = input.startedAt ?? /* @__PURE__ */ new Date();
|
|
43681
|
-
const existing = await prisma.agentInvocation.findUnique({
|
|
43682
|
-
where: { sessionId_runtimeInvocationId: {
|
|
43683
|
-
sessionId: input.sessionId,
|
|
43684
|
-
runtimeInvocationId: input.runtimeInvocationId
|
|
43685
|
-
} }
|
|
43686
|
-
});
|
|
43687
|
-
const row = await prisma.agentInvocation.upsert({
|
|
43688
|
-
where: { sessionId_runtimeInvocationId: {
|
|
43689
|
-
sessionId: input.sessionId,
|
|
43690
|
-
runtimeInvocationId: input.runtimeInvocationId
|
|
43691
|
-
} },
|
|
43692
|
-
update: {},
|
|
43693
|
-
create: {
|
|
43694
|
-
id: randomUUID10(),
|
|
43695
|
-
sessionId: input.sessionId,
|
|
43696
|
-
projectId: input.projectId,
|
|
43697
|
-
specId: input.specId ?? null,
|
|
43698
|
-
runtime: input.runtime,
|
|
43699
|
-
runtimeInvocationId: input.runtimeInvocationId,
|
|
43700
|
-
customAgentId: input.customAgentId ?? null,
|
|
43701
|
-
agentName: input.agentName,
|
|
43702
|
-
model: input.model ?? null,
|
|
43703
|
-
definitionHash: input.definitionHash ?? null,
|
|
43704
|
-
status: "running",
|
|
43705
|
-
startedAt
|
|
43706
|
-
}
|
|
43707
|
-
});
|
|
43708
|
-
const before2 = snapshot2(input.cwd, io.gitStatus);
|
|
43709
|
-
if (before2 !== null) snapshotHashes.set(keyOf(input), before2);
|
|
43710
|
-
return { row, duplicate: existing !== null };
|
|
43711
|
-
}
|
|
43712
|
-
async function stopAgentInvocation(input, io = {}) {
|
|
43713
|
-
const unique = { sessionId_runtimeInvocationId: {
|
|
43714
|
-
sessionId: input.sessionId,
|
|
43715
|
-
runtimeInvocationId: input.runtimeInvocationId
|
|
43716
|
-
} };
|
|
43717
|
-
const existing = await prisma.agentInvocation.findUnique({ where: unique });
|
|
43718
|
-
if (existing?.endedAt) return { row: existing, duplicate: true };
|
|
43719
|
-
const endedAt = input.endedAt ?? /* @__PURE__ */ new Date();
|
|
43720
|
-
const startedAt = existing?.startedAt ?? endedAt;
|
|
43721
|
-
const cleanResult = input.result === void 0 ? null : stripAnsi(input.result);
|
|
43722
|
-
const usage2 = transcriptUsage(input.transcriptPath, io.transcriptRoots);
|
|
43723
|
-
const before2 = snapshotHashes.get(keyOf(input));
|
|
43724
|
-
const after = snapshot2(input.cwd, io.gitStatus);
|
|
43725
|
-
snapshotHashes.delete(keyOf(input));
|
|
43726
|
-
const evidence = {
|
|
43727
|
-
// Stop tanpa start lazim setelah restart server di tengah invocation. Waktu start dan status
|
|
43728
|
-
// runtime sudah hilang; simpan baris sintetis yang dapat diaudit tanpa mengarang durasi 0 ms.
|
|
43729
|
-
status: existing ? input.status ?? "completed" : "completed",
|
|
43730
|
-
endedAt,
|
|
43731
|
-
durationMs: existing ? Math.max(0, endedAt.getTime() - startedAt.getTime()) : null,
|
|
43732
|
-
...usage2,
|
|
43733
|
-
resultExcerpt: cleanResult === null ? null : utf8Prefix(cleanResult, MAX_EXCERPT_BYTES),
|
|
43734
|
-
resultHash: cleanResult === null ? null : hash2(cleanResult),
|
|
43735
|
-
workspaceChanged: before2 !== void 0 && after !== null && before2 !== after
|
|
43736
|
-
};
|
|
43737
|
-
if (existing) {
|
|
43738
|
-
const row2 = await prisma.agentInvocation.update({ where: { id: existing.id }, data: evidence });
|
|
43739
|
-
return { row: row2, duplicate: false };
|
|
43740
|
-
}
|
|
43741
|
-
const row = await prisma.agentInvocation.create({
|
|
43742
|
-
data: {
|
|
43743
|
-
id: randomUUID10(),
|
|
43744
|
-
sessionId: input.sessionId,
|
|
43745
|
-
projectId: input.projectId,
|
|
43746
|
-
specId: input.specId ?? null,
|
|
43747
|
-
runtime: input.runtime,
|
|
43748
|
-
runtimeInvocationId: input.runtimeInvocationId,
|
|
43749
|
-
customAgentId: input.customAgentId ?? null,
|
|
43750
|
-
agentName: input.agentName,
|
|
43751
|
-
model: input.model ?? null,
|
|
43752
|
-
definitionHash: input.definitionHash ?? null,
|
|
43753
|
-
startedAt,
|
|
43754
|
-
...evidence
|
|
43755
|
-
}
|
|
43756
|
-
});
|
|
43757
|
-
return { row, duplicate: false };
|
|
43758
|
-
}
|
|
43759
|
-
async function reconcileAgentInvocations(liveSessionIds) {
|
|
43760
|
-
const live = new Set(liveSessionIds);
|
|
43761
|
-
const open5 = await prisma.agentInvocation.findMany({ where: { status: "running" } });
|
|
43762
|
-
let changed = 0;
|
|
43763
|
-
const endedAt = /* @__PURE__ */ new Date();
|
|
43764
|
-
for (const row of open5) {
|
|
43765
|
-
if (live.has(row.sessionId)) continue;
|
|
43766
|
-
await prisma.agentInvocation.update({
|
|
43767
|
-
where: { id: row.id },
|
|
43768
|
-
data: {
|
|
43769
|
-
status: "abandoned",
|
|
43770
|
-
endedAt,
|
|
43771
|
-
durationMs: Math.max(0, endedAt.getTime() - row.startedAt.getTime())
|
|
43772
|
-
}
|
|
43773
|
-
});
|
|
43774
|
-
changed++;
|
|
43775
|
-
}
|
|
43776
|
-
return changed;
|
|
43777
|
-
}
|
|
43778
|
-
var agentInvocationView = (row) => ({
|
|
43779
|
-
id: row.id,
|
|
43780
|
-
sessionId: row.sessionId,
|
|
43781
|
-
projectId: row.projectId,
|
|
43782
|
-
specId: row.specId,
|
|
43783
|
-
runtime: row.runtime === "codex" ? "codex" : "claude",
|
|
43784
|
-
customAgentId: row.customAgentId,
|
|
43785
|
-
agentName: row.agentName,
|
|
43786
|
-
model: row.model,
|
|
43787
|
-
definitionHash: row.definitionHash,
|
|
43788
|
-
status: row.status,
|
|
43789
|
-
startedAt: row.startedAt.toISOString(),
|
|
43790
|
-
endedAt: row.endedAt?.toISOString() ?? null,
|
|
43791
|
-
durationMs: row.durationMs,
|
|
43792
|
-
inputTokens: row.inputTokens,
|
|
43793
|
-
outputTokens: row.outputTokens,
|
|
43794
|
-
cachedTokens: row.cachedTokens,
|
|
43795
|
-
resultExcerpt: row.resultExcerpt,
|
|
43796
|
-
resultHash: row.resultHash,
|
|
43797
|
-
workspaceChanged: row.workspaceChanged,
|
|
43798
|
-
disposition: AGENT_DISPOSITIONS.includes(row.disposition) ? row.disposition : "pending",
|
|
43799
|
-
dispositionNote: row.dispositionNote,
|
|
43800
|
-
evaluatedAt: row.evaluatedAt?.toISOString() ?? null,
|
|
43801
|
-
reworkRequired: row.reworkRequired
|
|
43802
|
-
});
|
|
43803
|
-
var median = (values) => {
|
|
43804
|
-
if (values.length === 0) return null;
|
|
43805
|
-
values.sort((a, b) => a - b);
|
|
43806
|
-
const middle = Math.floor(values.length / 2);
|
|
43807
|
-
return values.length % 2 === 1 ? values[middle] : (values[middle - 1] + values[middle]) / 2;
|
|
43808
|
-
};
|
|
43809
|
-
var availableSum = (values) => {
|
|
43810
|
-
const known = values.filter((value) => value !== null);
|
|
43811
|
-
return known.length ? known.reduce((sum, value) => sum + value, 0) : null;
|
|
43812
|
-
};
|
|
43813
|
-
async function agentMetrics(query2) {
|
|
43814
|
-
const where = {
|
|
43815
|
-
...query2.projectId ? { projectId: query2.projectId } : {},
|
|
43816
|
-
...query2.from || query2.to ? { startedAt: {
|
|
43817
|
-
...query2.from ? { gte: query2.from } : {},
|
|
43818
|
-
...query2.to ? { lte: query2.to } : {}
|
|
43819
|
-
} } : {}
|
|
43820
|
-
};
|
|
43821
|
-
const rows = await prisma.agentInvocation.findMany({ where, orderBy: { startedAt: "desc" } });
|
|
43822
|
-
const groups = /* @__PURE__ */ new Map();
|
|
43823
|
-
const variantGroups = /* @__PURE__ */ new Map();
|
|
43824
|
-
const add = (map, key, row) => {
|
|
43825
|
-
const group2 = map.get(key);
|
|
43826
|
-
if (group2) group2.push(row);
|
|
43827
|
-
else map.set(key, [row]);
|
|
43828
|
-
};
|
|
43829
|
-
for (const row of rows) {
|
|
43830
|
-
add(groups, row.agentName, row);
|
|
43831
|
-
add(variantGroups, JSON.stringify([row.agentName, row.runtime, row.model, row.definitionHash]), row);
|
|
43832
|
-
}
|
|
43833
|
-
const summarize = (agentName, invocations) => {
|
|
43834
|
-
const dispositions = { pending: 0, accepted: 0, partial: 0, rejected: 0, falsePositive: 0 };
|
|
43835
|
-
for (const row of invocations) {
|
|
43836
|
-
if (row.disposition === "accepted") dispositions.accepted++;
|
|
43837
|
-
else if (row.disposition === "partial") dispositions.partial++;
|
|
43838
|
-
else if (row.disposition === "rejected") dispositions.rejected++;
|
|
43839
|
-
else if (row.disposition === "false-positive") dispositions.falsePositive++;
|
|
43840
|
-
else dispositions.pending++;
|
|
43841
|
-
}
|
|
43842
|
-
const evaluated = dispositions.accepted + dispositions.partial + dispositions.rejected + dispositions.falsePositive;
|
|
43843
|
-
return {
|
|
43844
|
-
agentName,
|
|
43845
|
-
invocationCount: invocations.length,
|
|
43846
|
-
medianDurationMs: median(invocations.flatMap((row) => row.durationMs === null ? [] : [row.durationMs])),
|
|
43847
|
-
inputTokens: availableSum(invocations.map((row) => row.inputTokens)),
|
|
43848
|
-
outputTokens: availableSum(invocations.map((row) => row.outputTokens)),
|
|
43849
|
-
cachedTokens: availableSum(invocations.map((row) => row.cachedTokens)),
|
|
43850
|
-
dispositions,
|
|
43851
|
-
operationalPrecision: evaluated ? (dispositions.accepted + dispositions.partial) / evaluated : null,
|
|
43852
|
-
workspaceChanged: invocations.some((row) => row.workspaceChanged),
|
|
43853
|
-
evaluatedCount: evaluated,
|
|
43854
|
-
rework: {
|
|
43855
|
-
required: invocations.filter((row) => row.reworkRequired === true).length,
|
|
43856
|
-
notRequired: invocations.filter((row) => row.reworkRequired === false).length,
|
|
43857
|
-
unknown: invocations.filter((row) => row.reworkRequired === null).length
|
|
43858
|
-
}
|
|
43859
|
-
};
|
|
43860
|
-
};
|
|
43861
|
-
const agents = [...groups.entries()].map(([name2, group2]) => summarize(name2, group2)).sort((a, b) => a.agentName.localeCompare(b.agentName));
|
|
43862
|
-
const variants = [...variantGroups.values()].map((group2) => ({
|
|
43863
|
-
...summarize(group2[0].agentName, group2),
|
|
43864
|
-
runtime: group2[0].runtime === "codex" ? "codex" : "claude",
|
|
43865
|
-
model: group2[0].model,
|
|
43866
|
-
definitionHash: group2[0].definitionHash
|
|
43867
|
-
})).sort((a, b) => JSON.stringify([a.agentName, a.runtime, a.model, a.definitionHash]).localeCompare(JSON.stringify([b.agentName, b.runtime, b.model, b.definitionHash])));
|
|
43868
|
-
const lastEventAt = rows.reduce((latest, row) => Math.max(latest, row.startedAt.getTime(), row.endedAt?.getTime() ?? 0), 0);
|
|
43869
|
-
const sampleRows = /* @__PURE__ */ new Map();
|
|
43870
|
-
for (const group2 of groups.values()) {
|
|
43871
|
-
for (const category of [
|
|
43872
|
-
(row) => row.disposition === "pending",
|
|
43873
|
-
(row) => row.disposition !== "pending",
|
|
43874
|
-
(row) => row.reworkRequired === true
|
|
43875
|
-
]) {
|
|
43876
|
-
for (const row of group2.filter(category).slice(0, 2)) sampleRows.set(row.id, row);
|
|
43877
|
-
}
|
|
43878
|
-
}
|
|
43879
|
-
return {
|
|
43880
|
-
agents,
|
|
43881
|
-
variants,
|
|
43882
|
-
recent: rows.slice(0, 100).map(agentInvocationView),
|
|
43883
|
-
samples: [...sampleRows.values()].map(agentInvocationView),
|
|
43884
|
-
telemetry: {
|
|
43885
|
-
state: rows.length ? "observed" : "unobserved",
|
|
43886
|
-
lastEventAt: rows.length ? new Date(lastEventAt).toISOString() : null,
|
|
43887
|
-
incompleteCount: rows.filter((row) => !row.endedAt || row.resultHash === null).length,
|
|
43888
|
-
relay: sessionEventRelayStatus()
|
|
43889
|
-
}
|
|
43890
|
-
};
|
|
43891
|
-
}
|
|
43892
|
-
async function updateAgentInvocationDisposition(id2, disposition, note, reworkRequired) {
|
|
43893
|
-
const exists = await prisma.agentInvocation.findUnique({ where: { id: id2 } });
|
|
43894
|
-
if (!exists) return null;
|
|
43895
|
-
const row = await prisma.agentInvocation.update({
|
|
43896
|
-
where: { id: id2 },
|
|
43897
|
-
data: {
|
|
43898
|
-
disposition,
|
|
43899
|
-
dispositionNote: note?.trim() || null,
|
|
43900
|
-
evaluatedAt: /* @__PURE__ */ new Date(),
|
|
43901
|
-
...reworkRequired !== void 0 ? { reworkRequired } : {}
|
|
43902
|
-
}
|
|
43903
|
-
});
|
|
43904
|
-
return agentInvocationView(row);
|
|
43905
|
-
}
|
|
43906
|
-
|
|
43907
|
-
// src/routes/session-events.ts
|
|
43908
44533
|
var bearer = (h) => /^Bearer (.+)$/.exec(h ?? "")?.[1] ?? "";
|
|
43909
44534
|
var recordOf = (value) => value && typeof value === "object" ? value : null;
|
|
43910
44535
|
var boundedString = (value, max) => typeof value === "string" && value.length > 0 && value.length <= max ? value : void 0;
|
|
@@ -43936,6 +44561,8 @@ async function session_events_default(app2) {
|
|
|
43936
44561
|
agentName: meta.name,
|
|
43937
44562
|
model: meta.model,
|
|
43938
44563
|
definitionHash: meta.definitionHash,
|
|
44564
|
+
...meta.phase ? { phase: meta.phase } : {},
|
|
44565
|
+
...meta.effort ? { effort: meta.effort } : {},
|
|
43939
44566
|
cwd: s2.cwd
|
|
43940
44567
|
};
|
|
43941
44568
|
const outcome = lifecycle === "SubagentStart" ? await startAgentInvocation(identity) : await stopAgentInvocation({
|
|
@@ -43945,8 +44572,17 @@ async function session_events_default(app2) {
|
|
|
43945
44572
|
transcriptPath: boundedString(
|
|
43946
44573
|
body.agent_transcript_path ?? body.transcript_path,
|
|
43947
44574
|
4096
|
|
43948
|
-
)
|
|
44575
|
+
),
|
|
44576
|
+
// ADR-0164 · effort yang BENAR-BENAR dipakai runtime (claude: `effort.level` di
|
|
44577
|
+
// SubagentStop) — HANYA untuk agen fase. Review Task 9: custom agent bisa membawa
|
|
44578
|
+
// `effort` roster sendiri (mis. katalog method), dan payload runtime bukan miliknya
|
|
44579
|
+
// untuk ditimpa — pertahankan perilaku effort roster custom agent apa adanya.
|
|
44580
|
+
// M-4 · payload hook tak tepercaya: `boundedString` menolak nilai bukan-string, kosong,
|
|
44581
|
+
// atau raksasa — melebihi batas berarti diabaikan, effort roster dipertahankan APA
|
|
44582
|
+
// ADANYA (bukan ditimpa potongan yang menyesatkan).
|
|
44583
|
+
...meta.phase && boundedString(recordOf(body.effort)?.level, 64) ? { effort: boundedString(recordOf(body.effort)?.level, 64) } : {}
|
|
43949
44584
|
});
|
|
44585
|
+
if (meta.phase) void refreshPhaseInvocations(sessionId2);
|
|
43950
44586
|
return reply.code(202).send(outcome.duplicate ? { duplicate: true } : { accepted: true });
|
|
43951
44587
|
}
|
|
43952
44588
|
const event = parseHookEvent(req.body);
|
|
@@ -46964,9 +47600,9 @@ ${baru}`;
|
|
|
46964
47600
|
// src/services/portal-chat/workspace.ts
|
|
46965
47601
|
init_src();
|
|
46966
47602
|
init_db();
|
|
46967
|
-
import { mkdirSync as mkdirSync12, mkdtempSync as mkdtempSync2, rmSync as rmSync8, writeFileSync as
|
|
47603
|
+
import { mkdirSync as mkdirSync12, mkdtempSync as mkdtempSync2, rmSync as rmSync8, writeFileSync as writeFileSync10 } from "node:fs";
|
|
46968
47604
|
import { tmpdir as tmpdir6 } from "node:os";
|
|
46969
|
-
import { join as
|
|
47605
|
+
import { join as join34 } from "node:path";
|
|
46970
47606
|
var STAGE_LABEL = {
|
|
46971
47607
|
brainstorming: "Dirumuskan",
|
|
46972
47608
|
objective: "Dirumuskan",
|
|
@@ -47021,7 +47657,7 @@ ${c.body}
|
|
|
47021
47657
|
${baris.join("\n")}`;
|
|
47022
47658
|
}
|
|
47023
47659
|
async function buildChatWorkspace(projectId) {
|
|
47024
|
-
const dir2 = mkdtempSync2(
|
|
47660
|
+
const dir2 = mkdtempSync2(join34(tmpdir6(), "hanoman-portal-chat-"));
|
|
47025
47661
|
try {
|
|
47026
47662
|
const project = await prisma.project.findUnique({
|
|
47027
47663
|
where: { id: projectId },
|
|
@@ -47042,7 +47678,7 @@ async function buildChatWorkspace(projectId) {
|
|
|
47042
47678
|
});
|
|
47043
47679
|
const files = [];
|
|
47044
47680
|
const tulis = (rel, isi) => {
|
|
47045
|
-
|
|
47681
|
+
writeFileSync10(join34(dir2, rel), isi, { mode: 384 });
|
|
47046
47682
|
files.push(rel);
|
|
47047
47683
|
};
|
|
47048
47684
|
tulis("project.md", renderProjectDoc(project));
|
|
@@ -47051,11 +47687,11 @@ async function buildChatWorkspace(projectId) {
|
|
|
47051
47687
|
tulis("catatan-rilis.md", renderChangelogDoc(changelogs));
|
|
47052
47688
|
const prds = await listPrds(projectId);
|
|
47053
47689
|
if (prds.length) {
|
|
47054
|
-
mkdirSync12(
|
|
47690
|
+
mkdirSync12(join34(dir2, "dokumen"), { mode: 448 });
|
|
47055
47691
|
for (const prd of prds) {
|
|
47056
47692
|
const isi = await readPrd(projectId, prd.path);
|
|
47057
47693
|
if (!isi) continue;
|
|
47058
|
-
tulis(
|
|
47694
|
+
tulis(join34("dokumen", `${prd.slug.replaceAll("/", "-")}.md`), isi);
|
|
47059
47695
|
}
|
|
47060
47696
|
}
|
|
47061
47697
|
return { dir: dir2, files, cleanup: () => rmSync8(dir2, { recursive: true, force: true }) };
|
|
@@ -47568,13 +48204,13 @@ init_db();
|
|
|
47568
48204
|
// src/services/bootstrap.ts
|
|
47569
48205
|
import { createHash as createHash12, randomBytes as randomBytes10, timingSafeEqual as timingSafeEqual4 } from "node:crypto";
|
|
47570
48206
|
import { chmod, lstat as lstat3, mkdir as mkdir8, readFile as readFile6, unlink as unlink5, writeFile as writeFile6 } from "node:fs/promises";
|
|
47571
|
-
import { join as
|
|
48207
|
+
import { join as join35 } from "node:path";
|
|
47572
48208
|
var TTL_MS5 = 15 * 6e4;
|
|
47573
48209
|
var SETUP_TOKEN_FILE = "setup.token";
|
|
47574
48210
|
var BootstrapError = class extends Error {
|
|
47575
48211
|
code = "BOOTSTRAP_PROOF";
|
|
47576
48212
|
};
|
|
47577
|
-
var tokenPath = (home3) =>
|
|
48213
|
+
var tokenPath = (home3) => join35(home3, SETUP_TOKEN_FILE);
|
|
47578
48214
|
async function readStored(home3) {
|
|
47579
48215
|
try {
|
|
47580
48216
|
const info = await lstat3(tokenPath(home3));
|