hanoman 0.2.2 → 0.2.4
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 +224 -34
- package/dist/server.js +1919 -591
- package/package.json +1 -1
- package/prisma/migrations/20260831120000_custom_agent_execution_profiles/migration.sql +5 -0
- package/prisma/migrations/20260831121000_agent_invocations/migration.sql +32 -0
- package/prisma/schema.prisma +36 -0
- package/web/assets/{index-BllpAcXg.js → index-CzV5atfY.js} +1557 -1557
- package/web/index.html +1 -1
package/dist/server.js
CHANGED
|
@@ -4834,7 +4834,10 @@ var init_entities = __esm({
|
|
|
4834
4834
|
//
|
|
4835
4835
|
// LOKAL per mesin: `setting` TIDAK ada di FIELDS sync (server/src/services/sync.ts), jadi dua
|
|
4836
4836
|
// mesin dengan versi hanoman berbeda tak bisa saling menimpa definisi bolak-balik.
|
|
4837
|
-
builtinAgents: external_exports.record(external_exports.string(), external_exports.string()).default({})
|
|
4837
|
+
builtinAgents: external_exports.record(external_exports.string(), external_exports.string()).default({}),
|
|
4838
|
+
// SPEC-950 · marker safety policy lokal. Terpisah dari fingerprint konten agar policy sekali
|
|
4839
|
+
// jalan tidak membuat seed memperlakukan konfigurasi operator sebagai bawaan.
|
|
4840
|
+
builtinAgentPolicies: external_exports.record(external_exports.string(), external_exports.string()).default({})
|
|
4838
4841
|
});
|
|
4839
4842
|
zNotification = external_exports.object({
|
|
4840
4843
|
id: external_exports.string(),
|
|
@@ -4996,6 +4999,15 @@ var init_agent = __esm({
|
|
|
4996
4999
|
});
|
|
4997
5000
|
|
|
4998
5001
|
// ../shared/src/custom-agent.ts
|
|
5002
|
+
function validateCreateWorkspacePolicy(value, ctx) {
|
|
5003
|
+
if (value.workspacePolicy === "isolated-worktree" && value.runtime !== "claude") {
|
|
5004
|
+
ctx.addIssue({
|
|
5005
|
+
code: external_exports.ZodIssueCode.custom,
|
|
5006
|
+
path: ["workspacePolicy"],
|
|
5007
|
+
message: "isolated-worktree hanya tersedia untuk Claude Code"
|
|
5008
|
+
});
|
|
5009
|
+
}
|
|
5010
|
+
}
|
|
4999
5011
|
function mentionsOf(v) {
|
|
5000
5012
|
if (!Array.isArray(v)) return [];
|
|
5001
5013
|
const out4 = [];
|
|
@@ -5005,6 +5017,21 @@ function mentionsOf(v) {
|
|
|
5005
5017
|
function runtimeOf(v) {
|
|
5006
5018
|
return typeof v === "string" && AGENT_RUNTIMES.includes(v) ? v : null;
|
|
5007
5019
|
}
|
|
5020
|
+
function activationOf(v) {
|
|
5021
|
+
return typeof v === "string" && AGENT_ACTIVATIONS.includes(v) ? v : "always";
|
|
5022
|
+
}
|
|
5023
|
+
function workspacePolicyOf(v) {
|
|
5024
|
+
return typeof v === "string" && AGENT_WORKSPACE_POLICIES.includes(v) ? v : "inherit";
|
|
5025
|
+
}
|
|
5026
|
+
function effortOf(v) {
|
|
5027
|
+
return typeof v === "string" && AGENT_EFFORTS.includes(v) ? v : null;
|
|
5028
|
+
}
|
|
5029
|
+
function maxTurnsOf(v) {
|
|
5030
|
+
return typeof v === "number" && Number.isInteger(v) && v >= 1 && v <= 200 ? v : null;
|
|
5031
|
+
}
|
|
5032
|
+
function timeoutSecondsOf(v) {
|
|
5033
|
+
return typeof v === "number" && Number.isInteger(v) && v >= 30 && v <= 3600 ? v : null;
|
|
5034
|
+
}
|
|
5008
5035
|
function toolsOf(v) {
|
|
5009
5036
|
if (!Array.isArray(v)) return null;
|
|
5010
5037
|
const out4 = [];
|
|
@@ -5048,7 +5075,7 @@ function effectiveAgents(globals, project) {
|
|
|
5048
5075
|
for (const a of project) byName.set(a.name, a);
|
|
5049
5076
|
return [...byName.values()].filter((a) => a.enabled).sort((x, y) => x.name.localeCompare(y.name));
|
|
5050
5077
|
}
|
|
5051
|
-
var AGENT_RUNTIMES, zAgentRuntime, AGENT_RUNTIME_LABELS, AGENT_NAME_RE, DEFAULT_AGENT_TOOLS, MENTION_TOOL, MENTION_MAX_HOPS, GLOBAL_SCOPE, zCustomAgent, zCreateCustomAgent, zUpdateCustomAgent, customAgentId;
|
|
5078
|
+
var AGENT_RUNTIMES, zAgentRuntime, AGENT_RUNTIME_LABELS, AGENT_ACTIVATIONS, zAgentActivation, AGENT_EFFORTS, zAgentEffort, AGENT_WORKSPACE_POLICIES, zAgentWorkspacePolicy, AGENT_NAME_RE, DEFAULT_AGENT_TOOLS, MENTION_TOOL, MENTION_MAX_HOPS, GLOBAL_SCOPE, zCustomAgent, zCreateCustomAgentFields, zCreateCustomAgent, zUpdateCustomAgent, customAgentId, AGENT_DISPOSITIONS;
|
|
5052
5079
|
var init_custom_agent = __esm({
|
|
5053
5080
|
"../shared/src/custom-agent.ts"() {
|
|
5054
5081
|
"use strict";
|
|
@@ -5059,6 +5086,12 @@ var init_custom_agent = __esm({
|
|
|
5059
5086
|
claude: "Claude Code",
|
|
5060
5087
|
codex: "Codex CLI"
|
|
5061
5088
|
};
|
|
5089
|
+
AGENT_ACTIVATIONS = ["always", "smart"];
|
|
5090
|
+
zAgentActivation = external_exports.enum(AGENT_ACTIVATIONS);
|
|
5091
|
+
AGENT_EFFORTS = ["ultra", "max", "xhigh", "high", "medium", "low", "ultracode"];
|
|
5092
|
+
zAgentEffort = external_exports.enum(AGENT_EFFORTS);
|
|
5093
|
+
AGENT_WORKSPACE_POLICIES = ["inherit", "read-only", "isolated-worktree"];
|
|
5094
|
+
zAgentWorkspacePolicy = external_exports.enum(AGENT_WORKSPACE_POLICIES);
|
|
5062
5095
|
AGENT_NAME_RE = /^[a-z][a-z0-9-]{1,39}$/;
|
|
5063
5096
|
DEFAULT_AGENT_TOOLS = [
|
|
5064
5097
|
"Read",
|
|
@@ -5083,11 +5116,16 @@ var init_custom_agent = __esm({
|
|
|
5083
5116
|
model: external_exports.string().nullable(),
|
|
5084
5117
|
mentions: external_exports.array(external_exports.string()).nullable(),
|
|
5085
5118
|
runtime: external_exports.enum(AGENT_RUNTIMES).nullable(),
|
|
5119
|
+
activation: zAgentActivation.default("always"),
|
|
5120
|
+
effort: zAgentEffort.nullable().default(null),
|
|
5121
|
+
workspacePolicy: zAgentWorkspacePolicy.default("inherit"),
|
|
5122
|
+
maxTurns: external_exports.number().int().min(1).max(200).nullable().default(null),
|
|
5123
|
+
timeoutSeconds: external_exports.number().int().min(30).max(3600).nullable().default(null),
|
|
5086
5124
|
enabled: external_exports.boolean(),
|
|
5087
5125
|
createdAt: external_exports.string(),
|
|
5088
5126
|
updatedAt: external_exports.string()
|
|
5089
5127
|
});
|
|
5090
|
-
|
|
5128
|
+
zCreateCustomAgentFields = external_exports.object({
|
|
5091
5129
|
projectId: external_exports.string().nullable().optional(),
|
|
5092
5130
|
name: external_exports.string().regex(AGENT_NAME_RE),
|
|
5093
5131
|
description: external_exports.string().trim().min(1).max(500),
|
|
@@ -5097,10 +5135,31 @@ var init_custom_agent = __esm({
|
|
|
5097
5135
|
mentions: external_exports.array(external_exports.string()).nullable().optional(),
|
|
5098
5136
|
// SPEC-484 · ADR-0101 · PENYARING mesin sesi, bukan pemilih proses. null/absen = ikut sesi induk.
|
|
5099
5137
|
runtime: external_exports.enum(AGENT_RUNTIMES).nullable().optional(),
|
|
5138
|
+
activation: zAgentActivation.optional(),
|
|
5139
|
+
effort: zAgentEffort.nullable().optional(),
|
|
5140
|
+
workspacePolicy: zAgentWorkspacePolicy.optional(),
|
|
5141
|
+
maxTurns: external_exports.number().int().min(1).max(200).nullable().optional(),
|
|
5142
|
+
timeoutSeconds: external_exports.number().int().min(30).max(3600).nullable().optional(),
|
|
5100
5143
|
enabled: external_exports.boolean().optional()
|
|
5101
5144
|
});
|
|
5102
|
-
|
|
5145
|
+
zCreateCustomAgent = zCreateCustomAgentFields.superRefine(validateCreateWorkspacePolicy);
|
|
5146
|
+
zUpdateCustomAgent = zCreateCustomAgentFields.omit({ name: true, projectId: true }).partial().superRefine((value, ctx) => {
|
|
5147
|
+
if (value.workspacePolicy === "isolated-worktree" && value.runtime !== void 0 && value.runtime !== "claude") {
|
|
5148
|
+
ctx.addIssue({
|
|
5149
|
+
code: external_exports.ZodIssueCode.custom,
|
|
5150
|
+
path: ["workspacePolicy"],
|
|
5151
|
+
message: "isolated-worktree hanya tersedia untuk Claude Code"
|
|
5152
|
+
});
|
|
5153
|
+
}
|
|
5154
|
+
});
|
|
5103
5155
|
customAgentId = (projectId, name2) => `${projectId ?? GLOBAL_SCOPE}:${name2}`;
|
|
5156
|
+
AGENT_DISPOSITIONS = [
|
|
5157
|
+
"pending",
|
|
5158
|
+
"accepted",
|
|
5159
|
+
"partial",
|
|
5160
|
+
"rejected",
|
|
5161
|
+
"false-positive"
|
|
5162
|
+
];
|
|
5104
5163
|
}
|
|
5105
5164
|
});
|
|
5106
5165
|
|
|
@@ -5115,6 +5174,12 @@ var init_builtin_agents = __esm({
|
|
|
5115
5174
|
description: "Gunakan saat perlu tahu DI MANA sesuatu dikerjakan di basis kode, atau bagaimana sebuah alur data mengalir, sebelum menyentuh kode. Ia menyapu banyak berkas dan mengembalikan peta ringkas berisi jangkar path:baris \u2014 bukan isi berkas. Panggil dia alih-alih membaca belasan berkas sendiri.",
|
|
5116
5175
|
tools: ["Read", "Glob", "Grep"],
|
|
5117
5176
|
enabledByDefault: true,
|
|
5177
|
+
activation: "smart",
|
|
5178
|
+
effort: "low",
|
|
5179
|
+
workspacePolicy: "read-only",
|
|
5180
|
+
maxTurns: null,
|
|
5181
|
+
timeoutSeconds: null,
|
|
5182
|
+
models: { claude: "haiku", codex: "gpt-5.6-terra" },
|
|
5118
5183
|
instructions: [
|
|
5119
5184
|
"Kamu navigator basis kode. Tugasmu MENJAWAB, bukan menyalin.",
|
|
5120
5185
|
"",
|
|
@@ -5141,6 +5206,12 @@ var init_builtin_agents = __esm({
|
|
|
5141
5206
|
description: "Gunakan saat ada bug, test merah, atau perilaku tak terduga yang belum jelas sebabnya. Ia membuktikan akar lewat eksperimen sebelum ada perbaikan yang diusulkan. Jangan panggil dia untuk memperbaiki \u2014 dia mendiagnosis.",
|
|
5142
5207
|
tools: ["Read", "Glob", "Grep", "Bash"],
|
|
5143
5208
|
enabledByDefault: false,
|
|
5209
|
+
activation: "smart",
|
|
5210
|
+
effort: "high",
|
|
5211
|
+
workspacePolicy: "read-only",
|
|
5212
|
+
maxTurns: null,
|
|
5213
|
+
timeoutSeconds: null,
|
|
5214
|
+
models: { claude: "sonnet", codex: "gpt-5.6" },
|
|
5144
5215
|
instructions: [
|
|
5145
5216
|
"Kamu diagnostikus. Kamu TIDAK memperbaiki kode \u2014 kamu membuktikan sebabnya.",
|
|
5146
5217
|
"",
|
|
@@ -5170,7 +5241,13 @@ var init_builtin_agents = __esm({
|
|
|
5170
5241
|
name: "qa-verifier",
|
|
5171
5242
|
description: "Gunakan SEBELUM menyatakan pekerjaan selesai atau test hijau. Ia menjalankan test yang tersentuh perubahan, memisahkan gagal palsu dari regresi, dan membuktikan bahwa test yang lulus itu benar-benar menguji perubahannya.",
|
|
5172
5243
|
tools: ["Read", "Glob", "Grep", "Bash"],
|
|
5173
|
-
enabledByDefault:
|
|
5244
|
+
enabledByDefault: false,
|
|
5245
|
+
activation: "smart",
|
|
5246
|
+
effort: "medium",
|
|
5247
|
+
workspacePolicy: "isolated-worktree",
|
|
5248
|
+
maxTurns: 40,
|
|
5249
|
+
timeoutSeconds: 900,
|
|
5250
|
+
models: { claude: "sonnet", codex: "gpt-5.6-terra" },
|
|
5174
5251
|
instructions: [
|
|
5175
5252
|
"Kamu gerbang terakhir sebelum sesuatu diumumkan hijau. Tugasmu MERAGUKAN kehijauan itu.",
|
|
5176
5253
|
"",
|
|
@@ -5182,18 +5259,18 @@ var init_builtin_agents = __esm({
|
|
|
5182
5259
|
" paralelisme antar-berkas test, sisa proses/soket/port dari run sebelumnya, variabel",
|
|
5183
5260
|
" lingkungan yang bocor dari shell, dan test yang memang sudah merah SEBELUM perubahan.",
|
|
5184
5261
|
" Cara memutuskannya: jalankan ulang test itu SENDIRIAN, dengan state yang bersih.",
|
|
5185
|
-
"4. UJI RELEVANSI
|
|
5186
|
-
"
|
|
5187
|
-
"
|
|
5188
|
-
"
|
|
5189
|
-
"
|
|
5190
|
-
"5. Bersihkan pohon kerja sementara (`git worktree remove`).",
|
|
5262
|
+
"4. UJI RELEVANSI hanya di worktree sementara dari `baseSha`, tidak pernah di worktree",
|
|
5263
|
+
" parent: buat worktree terpisah, pasang patch test di sana, jalankan, dan tuntut test",
|
|
5264
|
+
" itu MERAH. Bila `baseSha` atau patch test tidak tersedia, laporkan `belum terbukti`;",
|
|
5265
|
+
" jangan mencoba eksperimen kontrol di source parent.",
|
|
5266
|
+
"5. Bersihkan worktree sementara milikmu. Laporkan secara eksplisit bila cleanup gagal.",
|
|
5191
5267
|
"",
|
|
5192
5268
|
"Larangan keras:",
|
|
5193
5269
|
"- JANGAN `git stash` untuk apa pun. Tumpukan stash milik REPO, bukan pohon kerja \u2014 sesi lain",
|
|
5194
5270
|
" bisa mem-pop stash milikmu, dan kamu bisa mem-pop milik mereka. Isolasi memakai",
|
|
5195
5271
|
" `git worktree add`, titik.",
|
|
5196
5272
|
"- JANGAN mengubah test agar lulus. Bila test-nya yang salah, itu temuan, bukan pekerjaan.",
|
|
5273
|
+
"- JANGAN mengubah satu byte pun di worktree parent, termasuk berkas probe sementara.",
|
|
5197
5274
|
"",
|
|
5198
5275
|
"Gerbang bukti: setiap klaim membawa perintah DAN potongan keluarannya. Tanpa keluaran, tanpa",
|
|
5199
5276
|
"klaim. 'Semua test lulus' tanpa keluaran adalah kegagalanmu, bukan laporan.",
|
|
@@ -5208,6 +5285,12 @@ var init_builtin_agents = __esm({
|
|
|
5208
5285
|
description: "Gunakan saat test yang ada hanya menguji jalur mulus dan kamu ingin batas-batas kontrak benar-benar tertutup. Ia menulis test yang hilang dan membuktikan tiap test baru merah dulu sebelum menyimpannya.",
|
|
5209
5286
|
tools: ["Read", "Glob", "Grep", "Bash", "Write", "Edit"],
|
|
5210
5287
|
enabledByDefault: false,
|
|
5288
|
+
activation: "smart",
|
|
5289
|
+
effort: "high",
|
|
5290
|
+
workspacePolicy: "isolated-worktree",
|
|
5291
|
+
maxTurns: null,
|
|
5292
|
+
timeoutSeconds: null,
|
|
5293
|
+
models: { claude: "sonnet", codex: "gpt-5.6" },
|
|
5211
5294
|
instructions: [
|
|
5212
5295
|
"Kamu penambal jalur bahagia. Cakupan yang terlihat baik bukan urusanmu \u2014 kontrak yang tak",
|
|
5213
5296
|
"pernah diuji itu urusanmu.",
|
|
@@ -5240,6 +5323,12 @@ var init_builtin_agents = __esm({
|
|
|
5240
5323
|
description: "Gunakan sesudah perubahan selesai untuk menemukan tempat LAIN yang seharusnya ikut berubah tapi tidak: daftar kolom, cermin tipe antar-paket, enum kembar, dokumen kontrak, tabel konstanta. Ia mencari kegagalan senyap \u2014 yang tak memunculkan satu pun error.",
|
|
5241
5324
|
tools: ["Read", "Glob", "Grep", "Bash"],
|
|
5242
5325
|
enabledByDefault: true,
|
|
5326
|
+
activation: "smart",
|
|
5327
|
+
effort: "medium",
|
|
5328
|
+
workspacePolicy: "read-only",
|
|
5329
|
+
maxTurns: null,
|
|
5330
|
+
timeoutSeconds: null,
|
|
5331
|
+
models: { claude: "sonnet", codex: "gpt-5.6-terra" },
|
|
5243
5332
|
instructions: [
|
|
5244
5333
|
"Kamu pencari cermin yang hanyut. Kelas bug yang kamu buru punya satu ciri: TIDAK ADA yang",
|
|
5245
5334
|
"error. Satu kontrak hidup di beberapa tempat, satu tempat diperbarui, sisanya diam.",
|
|
@@ -5272,6 +5361,12 @@ var init_builtin_agents = __esm({
|
|
|
5272
5361
|
description: "Gunakan sebelum menutup pekerjaan untuk mengadu apa yang DIMINTA dengan apa yang benar-benar ada di diff. Ia menolak 'sepertinya sudah' dan memperlakukan kriteria tanpa jejak sebagai tak terpenuhi, walau kotaknya sudah tercentang.",
|
|
5273
5362
|
tools: ["Read", "Glob", "Grep", "Bash"],
|
|
5274
5363
|
enabledByDefault: false,
|
|
5364
|
+
activation: "smart",
|
|
5365
|
+
effort: "high",
|
|
5366
|
+
workspacePolicy: "read-only",
|
|
5367
|
+
maxTurns: null,
|
|
5368
|
+
timeoutSeconds: null,
|
|
5369
|
+
models: { claude: "sonnet", codex: "gpt-5.6-terra" },
|
|
5275
5370
|
instructions: [
|
|
5276
5371
|
"Kamu pengadu janji. Kamu tak menilai bagus atau tidaknya kode \u2014 kamu menilai apakah yang",
|
|
5277
5372
|
"diminta benar-benar ada.",
|
|
@@ -5300,6 +5395,12 @@ var init_builtin_agents = __esm({
|
|
|
5300
5395
|
description: "Gunakan sebelum menggabungkan perubahan yang menyentuh route, handler, job, CLI, atau apa pun yang menerima input dari luar. Ia menelusuri jalur konkret dari input tak terpercaya sampai ke tempat ia melukai, dan menolak melaporkan kekhawatiran yang tak bisa ia buktikan jalurnya.",
|
|
5301
5396
|
tools: ["Read", "Glob", "Grep", "Bash"],
|
|
5302
5397
|
enabledByDefault: true,
|
|
5398
|
+
activation: "smart",
|
|
5399
|
+
effort: "high",
|
|
5400
|
+
workspacePolicy: "read-only",
|
|
5401
|
+
maxTurns: null,
|
|
5402
|
+
timeoutSeconds: null,
|
|
5403
|
+
models: { claude: "sonnet", codex: "gpt-5.6" },
|
|
5303
5404
|
instructions: [
|
|
5304
5405
|
"Kamu penelusur sumber-ke-sink. Daftar kekhawatiran umum tak mengubah apa pun; yang mengubah",
|
|
5305
5406
|
"adalah satu jalur konkret dari input yang tak dipercaya sampai ke tempat ia melukai.",
|
|
@@ -5334,6 +5435,12 @@ var init_builtin_agents = __esm({
|
|
|
5334
5435
|
description: "Gunakan saat diff menambah atau menaikkan versi dependensi. Ia memeriksa advisory, lisensi, tanda pemeliharaan, dan \u2014 yang paling sering terlewat \u2014 apakah fungsinya sudah tersedia tanpa dependensi baru itu.",
|
|
5335
5436
|
tools: ["Read", "Glob", "Grep", "Bash", "WebSearch", "WebFetch"],
|
|
5336
5437
|
enabledByDefault: false,
|
|
5438
|
+
activation: "smart",
|
|
5439
|
+
effort: "medium",
|
|
5440
|
+
workspacePolicy: "read-only",
|
|
5441
|
+
maxTurns: null,
|
|
5442
|
+
timeoutSeconds: null,
|
|
5443
|
+
models: { claude: "haiku", codex: "gpt-5.6-terra" },
|
|
5337
5444
|
instructions: [
|
|
5338
5445
|
"Kamu gerbang rantai pasok. Satu dependensi masuk lewat satu baris diff dan tak pernah",
|
|
5339
5446
|
"diperiksa lagi seumur hidup proyek \u2014 pemeriksaan itu terjadi sekarang atau tidak sama sekali.",
|
|
@@ -5372,12 +5479,23 @@ function modelsForRuntime(rt) {
|
|
|
5372
5479
|
if (rt === "codex") return codex2;
|
|
5373
5480
|
return [...claude, ...codex2];
|
|
5374
5481
|
}
|
|
5482
|
+
function effortsForRuntimeModel(runtime, model) {
|
|
5483
|
+
if (runtime === "claude" || !runtime && model && MODELS.some((entry) => entry.id === model)) {
|
|
5484
|
+
return [...EFFORTS];
|
|
5485
|
+
}
|
|
5486
|
+
const codexModel2 = model ? CODEX_MODELS.find((entry) => entry.id === model) : void 0;
|
|
5487
|
+
if (runtime === "codex" || codexModel2) {
|
|
5488
|
+
return [...codexModel2?.efforts ?? codexCommonEfforts()];
|
|
5489
|
+
}
|
|
5490
|
+
const codex2 = new Set(codexCommonEfforts());
|
|
5491
|
+
return EFFORTS.filter((effort) => codex2.has(effort));
|
|
5492
|
+
}
|
|
5375
5493
|
function expandTools(tools, catalogIds) {
|
|
5376
5494
|
if (tools === null) return null;
|
|
5377
5495
|
if (!tools.includes(ALL_TOOLS)) return tools;
|
|
5378
5496
|
return catalogIds.filter((id) => id !== ALL_TOOLS);
|
|
5379
5497
|
}
|
|
5380
|
-
var ALL_TOOLS, ALL_TOOLS_ENTRY, BUILTIN_AGENT_TOOLS, mcpToolEntry;
|
|
5498
|
+
var ALL_TOOLS, ALL_TOOLS_ENTRY, BUILTIN_AGENT_TOOLS, mcpToolEntry, codexCommonEfforts;
|
|
5381
5499
|
var init_agent_catalog = __esm({
|
|
5382
5500
|
"../shared/src/agent-catalog.ts"() {
|
|
5383
5501
|
"use strict";
|
|
@@ -5399,6 +5517,10 @@ var init_agent_catalog = __esm({
|
|
|
5399
5517
|
label: `${server} \u2014 semua tool`,
|
|
5400
5518
|
group: "mcp"
|
|
5401
5519
|
});
|
|
5520
|
+
codexCommonEfforts = () => {
|
|
5521
|
+
const first = CODEX_MODELS[0]?.efforts ?? [];
|
|
5522
|
+
return first.filter((effort) => CODEX_MODELS.every((model) => model.efforts.includes(effort)));
|
|
5523
|
+
};
|
|
5402
5524
|
}
|
|
5403
5525
|
});
|
|
5404
5526
|
|
|
@@ -6679,6 +6801,8 @@ var init_api = __esm({
|
|
|
6679
6801
|
leadFlowCancel: (id) => `${API}/lead/flows/${encodeURIComponent(id)}/cancel`,
|
|
6680
6802
|
// SPEC-450 · ADR-0094 · katalog custom agent. `?projectId=` → himpunan EFEKTIF (global+project).
|
|
6681
6803
|
customAgents: `${API}/custom-agents`,
|
|
6804
|
+
customAgentMetrics: `${API}/custom-agents/metrics`,
|
|
6805
|
+
customAgentInvocation: (id) => `${API}/custom-agents/invocations/${encodeURIComponent(id)}`,
|
|
6682
6806
|
// SPEC-484 · ADR-0101 · sumber daftar tools/model/runtime untuk form (mention dari `customAgents`).
|
|
6683
6807
|
customAgentCatalog: `${API}/custom-agents/catalog`,
|
|
6684
6808
|
// SPEC-481 · ADR-0100 · webhook keluar (cookie-only)
|
|
@@ -12321,8 +12445,8 @@ var init_git = __esm({
|
|
|
12321
12445
|
// di dalam repo induk, yang menjawab "true" untuk pertanyaan pertama. cwd yang tak ada membuat
|
|
12322
12446
|
// spawnSync gagal (`status` null), dan itu sudah tertangkap `!== 0`.
|
|
12323
12447
|
worktreeAlive: (path) => {
|
|
12324
|
-
const
|
|
12325
|
-
if (
|
|
12448
|
+
const inside2 = spawnSync("git", ["rev-parse", "--is-inside-work-tree"], { cwd: path, encoding: "utf8" });
|
|
12449
|
+
if (inside2.status !== 0 || inside2.stdout.trim() !== "true") return false;
|
|
12326
12450
|
const top = spawnSync("git", ["rev-parse", "--show-toplevel"], { cwd: path, encoding: "utf8" });
|
|
12327
12451
|
return top.status === 0 && samePath(top.stdout.trim(), path);
|
|
12328
12452
|
},
|
|
@@ -12353,17 +12477,27 @@ var init_git = __esm({
|
|
|
12353
12477
|
});
|
|
12354
12478
|
|
|
12355
12479
|
// ../runner/src/settings.ts
|
|
12356
|
-
var EVENT_HOOK_COMMAND, guardSettings;
|
|
12480
|
+
var EVENT_SPOOL_SCRIPT, EVENT_HOOK_COMMAND, guardSettings;
|
|
12357
12481
|
var init_settings2 = __esm({
|
|
12358
12482
|
"../runner/src/settings.ts"() {
|
|
12359
12483
|
"use strict";
|
|
12484
|
+
EVENT_SPOOL_SCRIPT = [
|
|
12485
|
+
'const fs=require("node:fs"),p=require("node:path"),c=require("node:crypto")',
|
|
12486
|
+
'let d=""',
|
|
12487
|
+
'process.stdin.setEncoding("utf8")',
|
|
12488
|
+
'process.stdin.on("data",x=>d+=x)',
|
|
12489
|
+
'process.stdin.on("end",()=>{let t="";try{const n=Date.now()+"-"+process.pid+"-"+c.randomUUID();t=p.join(process.env.HANOMAN_EVENT_DIR,n+".tmp");const f=p.join(process.env.HANOMAN_EVENT_DIR,n+".json");fs.writeFileSync(t,d,{flag:"wx",mode:0o600});fs.renameSync(t,f)}catch{if(t)try{fs.rmSync(t,{force:true})}catch{}}})'
|
|
12490
|
+
].join(";");
|
|
12360
12491
|
EVENT_HOOK_COMMAND = [
|
|
12492
|
+
'if [ -n "${HANOMAN_EVENT_DIR:-}" ]; then',
|
|
12493
|
+
`node -e '${EVENT_SPOOL_SCRIPT}' >/dev/null 2>&1;`,
|
|
12494
|
+
"else",
|
|
12361
12495
|
'curl -sS -m 2 -X POST "$HANOMAN_EVENT_URL"',
|
|
12362
12496
|
"-H 'content-type: application/json'",
|
|
12363
12497
|
'-H "authorization: Bearer $HANOMAN_EVENT_TOKEN"',
|
|
12364
12498
|
'-H "x-hanoman-session: $HANOMAN_SESSION_ID"',
|
|
12365
12499
|
'${HANOMAN_EVENT_HOST:+-H "host: $HANOMAN_EVENT_HOST"}',
|
|
12366
|
-
"--data-binary @- >/dev/null 2>&1; exit 0"
|
|
12500
|
+
"--data-binary @- >/dev/null 2>&1; fi; exit 0"
|
|
12367
12501
|
].join(" ");
|
|
12368
12502
|
guardSettings = (decisionFile, goal, eventHook) => {
|
|
12369
12503
|
const hooks2 = {};
|
|
@@ -12380,6 +12514,8 @@ var init_settings2 = __esm({
|
|
|
12380
12514
|
matcher: "AskUserQuestion",
|
|
12381
12515
|
hooks: [{ type: "command", command: EVENT_HOOK_COMMAND }]
|
|
12382
12516
|
}];
|
|
12517
|
+
hooks2.SubagentStart = [{ hooks: [{ type: "command", command: EVENT_HOOK_COMMAND }] }];
|
|
12518
|
+
hooks2.SubagentStop = [{ hooks: [{ type: "command", command: EVENT_HOOK_COMMAND }] }];
|
|
12383
12519
|
}
|
|
12384
12520
|
if (goal) hooks2.Stop = [{ hooks: [{ type: "prompt", prompt: goal }] }];
|
|
12385
12521
|
return { hooks: hooks2 };
|
|
@@ -12458,6 +12594,10 @@ function codexHookArgs(o) {
|
|
|
12458
12594
|
const args = [];
|
|
12459
12595
|
if (stop.length) args.push("-c", `hooks.Stop=${group(stop)}`);
|
|
12460
12596
|
if (submit.length) args.push("-c", `hooks.UserPromptSubmit=${group(submit)}`);
|
|
12597
|
+
if (o.eventHook) {
|
|
12598
|
+
args.push("-c", `hooks.SubagentStart=${group([EVENT_HOOK_COMMAND])}`);
|
|
12599
|
+
args.push("-c", `hooks.SubagentStop=${group([EVENT_HOOK_COMMAND])}`);
|
|
12600
|
+
}
|
|
12461
12601
|
return args;
|
|
12462
12602
|
}
|
|
12463
12603
|
function codexGoalScript(o) {
|
|
@@ -12554,9 +12694,12 @@ var init_agent_cli = __esm({
|
|
|
12554
12694
|
// ../runner/src/custom-agents.ts
|
|
12555
12695
|
function agentPromptOf(def2, roster) {
|
|
12556
12696
|
const can = liveMentions(def2, roster);
|
|
12697
|
+
const instructions = def2.timeoutSeconds ? `${def2.instructions}
|
|
12698
|
+
|
|
12699
|
+
Batas waktu Hanoman untuk pekerjaan ini ${def2.timeoutSeconds} detik. Prioritaskan putusan dan bukti sebelum batas itu.` : def2.instructions;
|
|
12557
12700
|
if (can.length === 0) {
|
|
12558
12701
|
return [
|
|
12559
|
-
|
|
12702
|
+
instructions,
|
|
12560
12703
|
"",
|
|
12561
12704
|
"---",
|
|
12562
12705
|
"Kamu TIDAK boleh mendelegasikan ke agen lain. Selesaikan sendiri lalu laporkan hasilnya.",
|
|
@@ -12566,7 +12709,7 @@ function agentPromptOf(def2, roster) {
|
|
|
12566
12709
|
}
|
|
12567
12710
|
const list2 = can.map((m) => `@${m}`).join(", ");
|
|
12568
12711
|
return [
|
|
12569
|
-
|
|
12712
|
+
instructions,
|
|
12570
12713
|
"",
|
|
12571
12714
|
"---",
|
|
12572
12715
|
`Kamu boleh mendelegasikan HANYA ke: ${list2}. Panggil lewat ${MENTION_TOOL} dengan nama agennya.`,
|
|
@@ -12576,44 +12719,37 @@ function agentPromptOf(def2, roster) {
|
|
|
12576
12719
|
CODE_STYLE_CLAUSE
|
|
12577
12720
|
].join("\n");
|
|
12578
12721
|
}
|
|
12579
|
-
function renderAgentsJson(defs) {
|
|
12722
|
+
function renderAgentsJson(defs, options2 = {}) {
|
|
12580
12723
|
if (defs.length === 0) return "";
|
|
12581
12724
|
const out4 = {};
|
|
12582
12725
|
for (const d of defs) {
|
|
12726
|
+
const resolvedTools = resolveTools({ tools: d.tools, mentions: d.mentions });
|
|
12727
|
+
const readOnly = d.workspacePolicy === "read-only";
|
|
12583
12728
|
out4[d.name] = {
|
|
12584
12729
|
description: d.description,
|
|
12585
12730
|
prompt: agentPromptOf(d, defs),
|
|
12586
|
-
tools:
|
|
12587
|
-
...d.model ? { model: d.model } : {}
|
|
12731
|
+
tools: readOnly ? resolvedTools.filter((tool) => READ_ONLY_TOOLS.has(tool)) : resolvedTools,
|
|
12732
|
+
...d.model ? { model: d.model } : {},
|
|
12733
|
+
...d.effort ? { effort: d.effort } : {},
|
|
12734
|
+
...typeof d.maxTurns === "number" ? { maxTurns: d.maxTurns } : {},
|
|
12735
|
+
...d.workspacePolicy === "isolated-worktree" ? { isolation: "worktree" } : {},
|
|
12736
|
+
...readOnly ? { permissionMode: "plan" } : {},
|
|
12737
|
+
...readOnly && options2.readOnlyHookCommand ? {
|
|
12738
|
+
hooks: {
|
|
12739
|
+
PreToolUse: [{
|
|
12740
|
+
hooks: [{
|
|
12741
|
+
type: "command",
|
|
12742
|
+
command: options2.readOnlyHookCommand,
|
|
12743
|
+
timeout: 5
|
|
12744
|
+
}]
|
|
12745
|
+
}]
|
|
12746
|
+
}
|
|
12747
|
+
} : {}
|
|
12588
12748
|
};
|
|
12589
12749
|
}
|
|
12590
12750
|
return JSON.stringify(out4);
|
|
12591
12751
|
}
|
|
12592
|
-
function
|
|
12593
|
-
if (defs.length === 0) return "";
|
|
12594
|
-
const lines2 = [
|
|
12595
|
-
"",
|
|
12596
|
-
"## Custom agent hanoman",
|
|
12597
|
-
"",
|
|
12598
|
-
"Peran berikut tersedia untuk sesi ini. Saat sebuah tugas cocok dengan salah satunya, ADOPSI",
|
|
12599
|
-
"perannya (baca instruksinya, kerjakan dengan sudut pandang itu) lalu kembali ke peranmu sendiri.",
|
|
12600
|
-
"Jangan melahirkan proses agen baru.",
|
|
12601
|
-
""
|
|
12602
|
-
];
|
|
12603
|
-
for (const d of defs) {
|
|
12604
|
-
const can = liveMentions(d, defs);
|
|
12605
|
-
lines2.push(`### @${d.name} \u2014 ${d.description}`);
|
|
12606
|
-
lines2.push("");
|
|
12607
|
-
lines2.push(d.instructions);
|
|
12608
|
-
lines2.push("");
|
|
12609
|
-
lines2.push(
|
|
12610
|
-
can.length ? `Boleh berkonsultasi ke: ${can.map((m) => `@${m}`).join(", ")} (maks ${MENTION_MAX_HOPS} hop berantai).` : "Tidak boleh berkonsultasi ke peran lain."
|
|
12611
|
-
);
|
|
12612
|
-
lines2.push("");
|
|
12613
|
-
}
|
|
12614
|
-
return lines2.join("\n");
|
|
12615
|
-
}
|
|
12616
|
-
function agentDelegationClause(defs) {
|
|
12752
|
+
function agentDelegationClause(defs, runtime = "claude") {
|
|
12617
12753
|
if (defs.length === 0) return "";
|
|
12618
12754
|
return [
|
|
12619
12755
|
"",
|
|
@@ -12625,35 +12761,343 @@ function agentDelegationClause(defs) {
|
|
|
12625
12761
|
"",
|
|
12626
12762
|
...defs.map((d) => `- **${d.name}** \u2014 ${d.description}`),
|
|
12627
12763
|
"",
|
|
12628
|
-
`Panggil lewat tool ${MENTION_TOOL} dengan nama agennya
|
|
12764
|
+
runtime === "codex" ? "Panggil target bernama persis lewat `spawn_agent`." : `Panggil lewat tool ${MENTION_TOOL} dengan nama agennya.`,
|
|
12765
|
+
"Mereka tak bisa mendelegasikan lagi,",
|
|
12629
12766
|
"jadi tak ada rantai panggilan yang perlu kamu jaga. Laporan mereka adalah MASUKAN \u2014 kamu yang",
|
|
12630
12767
|
"memutuskan, dan kamu yang bertanggung jawab atas hasilnya.",
|
|
12631
12768
|
""
|
|
12632
12769
|
].join("\n");
|
|
12633
12770
|
}
|
|
12634
|
-
var liveMentions;
|
|
12771
|
+
var liveMentions, READ_ONLY_TOOLS;
|
|
12635
12772
|
var init_custom_agents = __esm({
|
|
12636
12773
|
"../runner/src/custom-agents.ts"() {
|
|
12637
12774
|
"use strict";
|
|
12638
12775
|
init_src();
|
|
12639
12776
|
init_code_style();
|
|
12640
12777
|
liveMentions = (def2, roster) => {
|
|
12778
|
+
if (def2.workspacePolicy === "read-only") return [];
|
|
12641
12779
|
const names = new Set(roster.map((r) => r.name));
|
|
12642
12780
|
return def2.mentions.filter((m) => names.has(m) && m !== def2.name);
|
|
12643
12781
|
};
|
|
12782
|
+
READ_ONLY_TOOLS = /* @__PURE__ */ new Set(["Read", "Glob", "Grep", "Bash", "WebFetch", "WebSearch"]);
|
|
12783
|
+
}
|
|
12784
|
+
});
|
|
12785
|
+
|
|
12786
|
+
// ../runner/src/agent-readonly.ts
|
|
12787
|
+
import { chmodSync, writeFileSync } from "node:fs";
|
|
12788
|
+
import { join } from "node:path";
|
|
12789
|
+
function denyReadOnly(detail) {
|
|
12790
|
+
return { allowed: false, reason: `Hanoman read-only policy: ${detail}` };
|
|
12791
|
+
}
|
|
12792
|
+
function tokenizeReadOnlyCommand(command) {
|
|
12793
|
+
const tokens = [];
|
|
12794
|
+
let token = "";
|
|
12795
|
+
let quote2 = "";
|
|
12796
|
+
let escaped = false;
|
|
12797
|
+
let active = false;
|
|
12798
|
+
for (let index = 0; index < command.length; index++) {
|
|
12799
|
+
const char = command[index];
|
|
12800
|
+
if (escaped) {
|
|
12801
|
+
token += char;
|
|
12802
|
+
escaped = false;
|
|
12803
|
+
active = true;
|
|
12804
|
+
continue;
|
|
12805
|
+
}
|
|
12806
|
+
if (quote2) {
|
|
12807
|
+
if (char === quote2) quote2 = "";
|
|
12808
|
+
else if (char === "\\" && quote2 === '"') escaped = true;
|
|
12809
|
+
else token += char;
|
|
12810
|
+
active = true;
|
|
12811
|
+
continue;
|
|
12812
|
+
}
|
|
12813
|
+
if (char === "'" || char === '"') {
|
|
12814
|
+
quote2 = char;
|
|
12815
|
+
active = true;
|
|
12816
|
+
} else if (char === "\\") {
|
|
12817
|
+
escaped = true;
|
|
12818
|
+
active = true;
|
|
12819
|
+
} else if (/\s/.test(char)) {
|
|
12820
|
+
if (active) {
|
|
12821
|
+
tokens.push(token);
|
|
12822
|
+
token = "";
|
|
12823
|
+
active = false;
|
|
12824
|
+
}
|
|
12825
|
+
} else {
|
|
12826
|
+
token += char;
|
|
12827
|
+
active = true;
|
|
12828
|
+
}
|
|
12829
|
+
}
|
|
12830
|
+
if (quote2 || escaped) return null;
|
|
12831
|
+
if (active) tokens.push(token);
|
|
12832
|
+
return tokens;
|
|
12833
|
+
}
|
|
12834
|
+
function evaluateReadOnlyPayload(payload, policy, environment) {
|
|
12835
|
+
if (!payload || typeof payload !== "object") return denyReadOnly("payload hook tidak sah");
|
|
12836
|
+
const event = payload;
|
|
12837
|
+
const tool = typeof event.tool_name === "string" ? event.tool_name : typeof event.toolName === "string" ? event.toolName : "";
|
|
12838
|
+
if (!tool) return denyReadOnly("nama tool tidak tersedia");
|
|
12839
|
+
if (policy.directTools.includes(tool)) return { allowed: true };
|
|
12840
|
+
if (policy.deniedTools.includes(tool) || tool.startsWith("mcp__")) {
|
|
12841
|
+
return denyReadOnly(`tool ${tool} dapat mengubah state`);
|
|
12842
|
+
}
|
|
12843
|
+
if (!policy.shellTools.includes(tool)) return denyReadOnly(`tool ${tool} tidak terbukti read-only`);
|
|
12844
|
+
const rawInput = event.tool_input;
|
|
12845
|
+
const input = rawInput && typeof rawInput === "object" ? rawInput : {};
|
|
12846
|
+
const command = typeof input.command === "string" ? input.command : typeof input.cmd === "string" ? input.cmd : "";
|
|
12847
|
+
const trimmed = command.trim();
|
|
12848
|
+
if (!trimmed) return denyReadOnly("perintah shell kosong atau tidak dikenal");
|
|
12849
|
+
if (/\r|\n|;|&&|\|\||\||[<>]|\$|`/.test(trimmed)) {
|
|
12850
|
+
return denyReadOnly("operator shell yang dapat merangkai atau menulis dilarang");
|
|
12851
|
+
}
|
|
12852
|
+
const tokens = tokenizeReadOnlyCommand(trimmed);
|
|
12853
|
+
if (!tokens?.length) return denyReadOnly("perintah shell tidak dapat diparse dengan aman");
|
|
12854
|
+
const first = tokens[0] ?? "";
|
|
12855
|
+
const commandName = first.split("/").pop() ?? "";
|
|
12856
|
+
if (first !== commandName) {
|
|
12857
|
+
return denyReadOnly("executable ber-path tidak diizinkan; gunakan command allowlist dari PATH");
|
|
12858
|
+
}
|
|
12859
|
+
if (commandName === "git") {
|
|
12860
|
+
const subcommand = tokens[1] ?? "";
|
|
12861
|
+
if (!policy.gitCommands.includes(subcommand)) {
|
|
12862
|
+
return denyReadOnly(`git ${subcommand || "<kosong>"} bukan operasi baca yang diizinkan`);
|
|
12863
|
+
}
|
|
12864
|
+
const args = tokens.slice(2);
|
|
12865
|
+
if (args.some((arg) => arg === "--output" || arg.startsWith("--output=") || arg === "--ext-diff" || arg === "--textconv")) {
|
|
12866
|
+
return denyReadOnly("opsi git dapat menulis atau menjalankan helper eksternal");
|
|
12867
|
+
}
|
|
12868
|
+
if (subcommand !== "status" && (!args.includes("--no-ext-diff") || !args.includes("--no-textconv"))) {
|
|
12869
|
+
return denyReadOnly("git diff/show/log wajib menonaktifkan helper eksternal dan textconv");
|
|
12870
|
+
}
|
|
12871
|
+
return { allowed: true };
|
|
12872
|
+
}
|
|
12873
|
+
if (!policy.shellCommands.includes(commandName)) {
|
|
12874
|
+
return denyReadOnly(`perintah ${commandName || "<kosong>"} tidak terbukti read-only`);
|
|
12875
|
+
}
|
|
12876
|
+
if (commandName === "rg" && environment.RIPGREP_CONFIG_PATH?.trim()) {
|
|
12877
|
+
return denyReadOnly("RIPGREP_CONFIG_PATH dapat menyuntikkan preprocessor eksternal");
|
|
12878
|
+
}
|
|
12879
|
+
if (commandName === "rg" && tokens.slice(1).some((arg) => arg === "--pre" || arg.startsWith("--pre=") || arg === "--pre-glob" || arg.startsWith("--pre-glob=") || arg === "--hostname-bin" || arg.startsWith("--hostname-bin="))) {
|
|
12880
|
+
return denyReadOnly("opsi rg dapat menjalankan helper eksternal");
|
|
12881
|
+
}
|
|
12882
|
+
if (commandName === "sed") {
|
|
12883
|
+
const quiet = tokens[1] === "-n" || tokens[1] === "--quiet" || tokens[1] === "--silent";
|
|
12884
|
+
const printOnly = /^\d+(?:,\d+)?p$/.test(tokens[2] ?? "");
|
|
12885
|
+
const files = tokens.slice(3);
|
|
12886
|
+
if (!quiet || !printOnly || files.length === 0 || files.some((arg) => arg.startsWith("-"))) {
|
|
12887
|
+
return denyReadOnly("hanya sed -n '<baris>[,<baris>]p' <berkas> yang diizinkan");
|
|
12888
|
+
}
|
|
12889
|
+
}
|
|
12890
|
+
return { allowed: true };
|
|
12891
|
+
}
|
|
12892
|
+
function readOnlyHookSource() {
|
|
12893
|
+
return [
|
|
12894
|
+
'"use strict";',
|
|
12895
|
+
`const denyReadOnly = ${denyReadOnly.toString()};`,
|
|
12896
|
+
`const tokenizeReadOnlyCommand = ${tokenizeReadOnlyCommand.toString()};`,
|
|
12897
|
+
`const evaluate = ${evaluateReadOnlyPayload.toString()};`,
|
|
12898
|
+
`const policy = ${JSON.stringify(POLICY)};`,
|
|
12899
|
+
"let input = '';",
|
|
12900
|
+
"process.stdin.setEncoding('utf8');",
|
|
12901
|
+
"process.stdin.on('data', chunk => { input += chunk; });",
|
|
12902
|
+
"process.stdin.on('end', () => {",
|
|
12903
|
+
" let payload;",
|
|
12904
|
+
" try { payload = JSON.parse(input); } catch { payload = null; }",
|
|
12905
|
+
" const decision = evaluate(payload, policy, process.env);",
|
|
12906
|
+
" if (!decision.allowed) { process.stderr.write(decision.reason + '\\n'); process.exitCode = 2; }",
|
|
12907
|
+
"});",
|
|
12908
|
+
"process.stdin.resume();",
|
|
12909
|
+
""
|
|
12910
|
+
].join("\n");
|
|
12911
|
+
}
|
|
12912
|
+
function writeReadOnlyHook(dir2) {
|
|
12913
|
+
const path = join(dir2, "custom-agent-readonly.cjs");
|
|
12914
|
+
writeFileSync(path, readOnlyHookSource(), { mode: 384 });
|
|
12915
|
+
chmodSync(path, 384);
|
|
12916
|
+
return { path, command: `node ${shellQuote(path)}` };
|
|
12917
|
+
}
|
|
12918
|
+
var POLICY, shellQuote;
|
|
12919
|
+
var init_agent_readonly = __esm({
|
|
12920
|
+
"../runner/src/agent-readonly.ts"() {
|
|
12921
|
+
"use strict";
|
|
12922
|
+
POLICY = {
|
|
12923
|
+
directTools: ["Read", "Glob", "Grep", "WebFetch", "WebSearch"],
|
|
12924
|
+
shellTools: ["Bash", "local_shell", "exec_command"],
|
|
12925
|
+
deniedTools: ["Write", "Edit", "Task", "apply_patch", "spawn_agent"],
|
|
12926
|
+
shellCommands: ["rg", "sed", "head", "tail", "wc", "ls"],
|
|
12927
|
+
gitCommands: ["diff", "show", "status", "log"]
|
|
12928
|
+
};
|
|
12929
|
+
shellQuote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
|
|
12930
|
+
}
|
|
12931
|
+
});
|
|
12932
|
+
|
|
12933
|
+
// ../runner/src/runtime-profile.ts
|
|
12934
|
+
function resolveHardening(env) {
|
|
12935
|
+
if (env.HANOMAN_HARDENING === "1") return true;
|
|
12936
|
+
return env.HANOMAN_SESSION_SANDBOX === "podman" || filled(env.HANOMAN_PUBLIC_ORIGINS) || filled(env.HANOMAN_TRUST_PROXY);
|
|
12937
|
+
}
|
|
12938
|
+
function resolveDeployment(env) {
|
|
12939
|
+
if (resolveHardening(env)) return "public";
|
|
12940
|
+
return env.HANOMAN_DEPLOYMENT === "public" ? "public" : "local";
|
|
12941
|
+
}
|
|
12942
|
+
var filled;
|
|
12943
|
+
var init_runtime_profile = __esm({
|
|
12944
|
+
"../runner/src/runtime-profile.ts"() {
|
|
12945
|
+
"use strict";
|
|
12946
|
+
filled = (v) => !!v && v.trim() !== "";
|
|
12947
|
+
}
|
|
12948
|
+
});
|
|
12949
|
+
|
|
12950
|
+
// ../runner/src/codex-agent-config.ts
|
|
12951
|
+
import { chmodSync as chmodSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
12952
|
+
import { join as join2 } from "node:path";
|
|
12953
|
+
function codexNativeAgentsSupported(version) {
|
|
12954
|
+
const parsed = version ? /(\d+)\.(\d+)\.(\d+)/.exec(version)?.[0] : null;
|
|
12955
|
+
return parsed ? cmpVersion(parsed, CODEX_NATIVE_AGENTS_MIN_CLIENT) >= 0 : false;
|
|
12956
|
+
}
|
|
12957
|
+
function codexNativeVersionProbe(env, codexBin3 = env.HANOMAN_CODEX_BIN ?? "codex") {
|
|
12958
|
+
const sandbox = env.HANOMAN_SESSION_SANDBOX ?? (resolveHardening(env) ? "required" : "off");
|
|
12959
|
+
if (sandbox !== "podman") return { bin: codexBin3, args: ["--version"] };
|
|
12960
|
+
return {
|
|
12961
|
+
bin: env.HANOMAN_PODMAN_BIN ?? "podman",
|
|
12962
|
+
args: [
|
|
12963
|
+
"run",
|
|
12964
|
+
"--rm",
|
|
12965
|
+
"--read-only",
|
|
12966
|
+
"--cap-drop=ALL",
|
|
12967
|
+
"--userns=keep-id",
|
|
12968
|
+
"--network",
|
|
12969
|
+
"none",
|
|
12970
|
+
env.HANOMAN_SESSION_IMAGE ?? "hanoman-agent:latest",
|
|
12971
|
+
"/bin/sh",
|
|
12972
|
+
"-lc",
|
|
12973
|
+
`${shellQuote2(codexBin3)} --version`
|
|
12974
|
+
]
|
|
12975
|
+
};
|
|
12976
|
+
}
|
|
12977
|
+
function renderCodexAgentToml(def2, roster, options2 = {}) {
|
|
12978
|
+
const lines2 = [
|
|
12979
|
+
`name = ${tomlString(def2.name)}`,
|
|
12980
|
+
`description = ${tomlString(def2.description)}`,
|
|
12981
|
+
`developer_instructions = ${tomlString(agentPromptOf(def2, roster))}`,
|
|
12982
|
+
...def2.model ? [`model = ${tomlString(def2.model)}`] : [],
|
|
12983
|
+
...def2.effort ? [`model_reasoning_effort = ${tomlString(def2.effort)}`] : [],
|
|
12984
|
+
...def2.workspacePolicy === "read-only" ? ['sandbox_mode = "read-only"'] : []
|
|
12985
|
+
];
|
|
12986
|
+
if (def2.workspacePolicy === "read-only" && options2.readOnlyHookCommand) {
|
|
12987
|
+
lines2.push(
|
|
12988
|
+
"",
|
|
12989
|
+
"[[hooks.PreToolUse]]",
|
|
12990
|
+
"",
|
|
12991
|
+
"[[hooks.PreToolUse.hooks]]",
|
|
12992
|
+
'type = "command"',
|
|
12993
|
+
`command = ${tomlString(options2.readOnlyHookCommand)}`,
|
|
12994
|
+
"timeout = 5"
|
|
12995
|
+
);
|
|
12996
|
+
}
|
|
12997
|
+
return `${lines2.join("\n")}
|
|
12998
|
+
`;
|
|
12999
|
+
}
|
|
13000
|
+
function materializeCodexAgents(defs, tempDir, options2 = {}) {
|
|
13001
|
+
if (defs.length === 0) {
|
|
13002
|
+
return { args: [], delegationClause: "", configPaths: [], warnings: [], liveDefs: [] };
|
|
13003
|
+
}
|
|
13004
|
+
if ("clientVersion" in options2 && !codexNativeAgentsSupported(options2.clientVersion ?? null)) {
|
|
13005
|
+
const seen2 = options2.clientVersion ?? "tak terdeteksi";
|
|
13006
|
+
return {
|
|
13007
|
+
args: [],
|
|
13008
|
+
delegationClause: "",
|
|
13009
|
+
configPaths: [],
|
|
13010
|
+
liveDefs: [],
|
|
13011
|
+
warnings: defs.map((def2) => ({
|
|
13012
|
+
agentName: def2.name,
|
|
13013
|
+
reason: `Codex ${seen2} tidak mendukung custom agent native; butuh >= ${CODEX_NATIVE_AGENTS_MIN_CLIENT}`
|
|
13014
|
+
}))
|
|
13015
|
+
};
|
|
13016
|
+
}
|
|
13017
|
+
const write2 = options2.writeFile ?? ((path, content) => writeFileSync2(path, content, { mode: 384 }));
|
|
13018
|
+
const chmod3 = options2.chmod ?? chmodSync2;
|
|
13019
|
+
const successful = [];
|
|
13020
|
+
const warnings = [];
|
|
13021
|
+
for (const [index, def2] of defs.entries()) {
|
|
13022
|
+
if (def2.workspacePolicy === "isolated-worktree") {
|
|
13023
|
+
warnings.push({
|
|
13024
|
+
agentName: def2.name,
|
|
13025
|
+
reason: "isolated-worktree belum tersedia untuk subagent Codex"
|
|
13026
|
+
});
|
|
13027
|
+
continue;
|
|
13028
|
+
}
|
|
13029
|
+
const path = join2(tempDir, `${String(index).padStart(2, "0")}-${safeFilename(def2.name)}.toml`);
|
|
13030
|
+
try {
|
|
13031
|
+
write2(path, renderCodexAgentToml(def2, defs, options2));
|
|
13032
|
+
chmod3(path, 384);
|
|
13033
|
+
successful.push({ def: def2, path });
|
|
13034
|
+
} catch (error) {
|
|
13035
|
+
warnings.push({
|
|
13036
|
+
agentName: def2.name,
|
|
13037
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
13038
|
+
});
|
|
13039
|
+
}
|
|
13040
|
+
}
|
|
13041
|
+
if (successful.length === 0) {
|
|
13042
|
+
return { args: [], delegationClause: "", configPaths: [], warnings, liveDefs: [] };
|
|
13043
|
+
}
|
|
13044
|
+
const args = [
|
|
13045
|
+
"-c",
|
|
13046
|
+
"agents.enabled=true",
|
|
13047
|
+
"-c",
|
|
13048
|
+
"agents.max_concurrent_threads_per_session=3"
|
|
13049
|
+
];
|
|
13050
|
+
for (const { def: def2, path } of successful) {
|
|
13051
|
+
const key = `agents.${tomlKey(def2.name)}`;
|
|
13052
|
+
args.push("-c", `${key}.description=${tomlString(def2.description)}`);
|
|
13053
|
+
args.push("-c", `${key}.config_file=${tomlString(path)}`);
|
|
13054
|
+
}
|
|
13055
|
+
const liveDefs = successful.map((entry) => entry.def);
|
|
13056
|
+
return {
|
|
13057
|
+
args,
|
|
13058
|
+
delegationClause: agentDelegationClause(liveDefs, "codex"),
|
|
13059
|
+
configPaths: successful.map((entry) => entry.path),
|
|
13060
|
+
warnings,
|
|
13061
|
+
liveDefs
|
|
13062
|
+
};
|
|
13063
|
+
}
|
|
13064
|
+
var CODEX_NATIVE_AGENTS_MIN_CLIENT, shellQuote2, tomlString, tomlKey, safeFilename;
|
|
13065
|
+
var init_codex_agent_config = __esm({
|
|
13066
|
+
"../runner/src/codex-agent-config.ts"() {
|
|
13067
|
+
"use strict";
|
|
13068
|
+
init_src();
|
|
13069
|
+
init_custom_agents();
|
|
13070
|
+
init_runtime_profile();
|
|
13071
|
+
CODEX_NATIVE_AGENTS_MIN_CLIENT = "0.151.0";
|
|
13072
|
+
shellQuote2 = (value) => `'${value.replaceAll("'", "'\\''")}'`;
|
|
13073
|
+
tomlString = (value) => JSON.stringify(value);
|
|
13074
|
+
tomlKey = (value) => JSON.stringify(value);
|
|
13075
|
+
safeFilename = (name2) => name2.replace(/[^a-z0-9-]/gi, "-");
|
|
13076
|
+
}
|
|
13077
|
+
});
|
|
13078
|
+
|
|
13079
|
+
// ../runner/src/custom-agent-eval.ts
|
|
13080
|
+
var init_custom_agent_eval = __esm({
|
|
13081
|
+
"../runner/src/custom-agent-eval.ts"() {
|
|
13082
|
+
"use strict";
|
|
13083
|
+
init_src();
|
|
13084
|
+
init_codex_agent_config();
|
|
13085
|
+
init_codex_settings();
|
|
13086
|
+
init_custom_agents();
|
|
13087
|
+
init_settings2();
|
|
12644
13088
|
}
|
|
12645
13089
|
});
|
|
12646
13090
|
|
|
12647
13091
|
// ../runner/src/paths.ts
|
|
12648
13092
|
import { homedir } from "node:os";
|
|
12649
|
-
import { dirname as dirname2, isAbsolute as isAbsolute2, join, resolve as resolve5 } from "node:path";
|
|
13093
|
+
import { dirname as dirname2, isAbsolute as isAbsolute2, join as join3, resolve as resolve5 } from "node:path";
|
|
12650
13094
|
function resolveHome(env = process.env, home3 = homedir()) {
|
|
12651
13095
|
const v = env.HANOMAN_HOME?.trim();
|
|
12652
|
-
return v ? v :
|
|
13096
|
+
return v ? v : join3(home3, ".hanoman");
|
|
12653
13097
|
}
|
|
12654
13098
|
function resolveDataDirs(env = process.env, home3 = homedir()) {
|
|
12655
13099
|
const root = resolveHome(env, home3);
|
|
12656
|
-
const dir2 = (key, name2) => env[key]?.trim() ||
|
|
13100
|
+
const dir2 = (key, name2) => env[key]?.trim() || join3(root, name2);
|
|
12657
13101
|
return {
|
|
12658
13102
|
home: root,
|
|
12659
13103
|
transcripts: dir2("HANOMAN_TRANSCRIPT_DIR", "transcripts"),
|
|
@@ -12675,7 +13119,7 @@ function resolveDbUrl(env, schemaDir2) {
|
|
|
12675
13119
|
return absoluteFileUrl(own, schemaDir2);
|
|
12676
13120
|
}
|
|
12677
13121
|
const raw = env.DATABASE_URL?.trim();
|
|
12678
|
-
if (!raw || !raw.startsWith("file:")) return `file:${
|
|
13122
|
+
if (!raw || !raw.startsWith("file:")) return `file:${join3(resolveHome(env), "hanoman.db")}`;
|
|
12679
13123
|
return absoluteFileUrl(raw, schemaDir2);
|
|
12680
13124
|
}
|
|
12681
13125
|
function absoluteFileUrl(raw, schemaDir2) {
|
|
@@ -12703,7 +13147,7 @@ var init_paths = __esm({
|
|
|
12703
13147
|
|
|
12704
13148
|
// ../runner/src/skills.ts
|
|
12705
13149
|
import { homedir as homedir2 } from "node:os";
|
|
12706
|
-
import { basename as basename2, dirname as dirname3, join as
|
|
13150
|
+
import { basename as basename2, dirname as dirname3, join as join4, resolve as resolve6 } from "node:path";
|
|
12707
13151
|
import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync2 } from "node:fs";
|
|
12708
13152
|
function agentSkillHome(agent, env = process.env, osHome = homedir2()) {
|
|
12709
13153
|
const own = (agent === "codex" ? env.HANOMAN_CODEX_HOME : env.HANOMAN_CLAUDE_HOME)?.trim();
|
|
@@ -12712,10 +13156,10 @@ function agentSkillHome(agent, env = process.env, osHome = homedir2()) {
|
|
|
12712
13156
|
const cx = env.CODEX_HOME?.trim();
|
|
12713
13157
|
if (cx) return cx;
|
|
12714
13158
|
}
|
|
12715
|
-
return
|
|
13159
|
+
return join4(osHome, agent === "codex" ? ".codex" : ".claude");
|
|
12716
13160
|
}
|
|
12717
13161
|
function agentsSkillHome(env = process.env, osHome = homedir2()) {
|
|
12718
|
-
return env.HANOMAN_AGENTS_HOME?.trim() ||
|
|
13162
|
+
return env.HANOMAN_AGENTS_HOME?.trim() || join4(osHome, ".agents");
|
|
12719
13163
|
}
|
|
12720
13164
|
function dirsIn(dir2) {
|
|
12721
13165
|
try {
|
|
@@ -12732,7 +13176,7 @@ function readJson(file) {
|
|
|
12732
13176
|
}
|
|
12733
13177
|
}
|
|
12734
13178
|
function isSkillDir(dir2) {
|
|
12735
|
-
return existsSync3(
|
|
13179
|
+
return existsSync3(join4(dir2, "SKILL.md"));
|
|
12736
13180
|
}
|
|
12737
13181
|
function asSkill(dir2, pkg, name2 = basename2(dir2)) {
|
|
12738
13182
|
return { id: pkg ? `${pkg}:${name2}` : name2, name: name2, pkg, dir: dir2 };
|
|
@@ -12740,7 +13184,7 @@ function asSkill(dir2, pkg, name2 = basename2(dir2)) {
|
|
|
12740
13184
|
function skillsUnder(dir2, pkg, depth = 2) {
|
|
12741
13185
|
const out4 = [];
|
|
12742
13186
|
for (const name2 of dirsIn(dir2)) {
|
|
12743
|
-
const sub =
|
|
13187
|
+
const sub = join4(dir2, name2);
|
|
12744
13188
|
if (isSkillDir(sub)) out4.push(asSkill(sub, pkg, name2));
|
|
12745
13189
|
else if (depth > 1) out4.push(...skillsUnder(sub, pkg, depth - 1));
|
|
12746
13190
|
}
|
|
@@ -12748,7 +13192,7 @@ function skillsUnder(dir2, pkg, depth = 2) {
|
|
|
12748
13192
|
}
|
|
12749
13193
|
function manifestSkills(installPath, pkg) {
|
|
12750
13194
|
for (const marker of [".claude-plugin", ".codex-plugin"]) {
|
|
12751
|
-
const j = readJson(
|
|
13195
|
+
const j = readJson(join4(installPath, marker, "plugin.json"));
|
|
12752
13196
|
const declared = j?.skills;
|
|
12753
13197
|
if (!Array.isArray(declared)) continue;
|
|
12754
13198
|
const out4 = [];
|
|
@@ -12764,11 +13208,11 @@ function manifestSkills(installPath, pkg) {
|
|
|
12764
13208
|
}
|
|
12765
13209
|
function pluginSkills(installPath, pkg) {
|
|
12766
13210
|
const declared = manifestSkills(installPath, pkg);
|
|
12767
|
-
return declared.length ? declared : skillsUnder(
|
|
13211
|
+
return declared.length ? declared : skillsUnder(join4(installPath, "skills"), pkg);
|
|
12768
13212
|
}
|
|
12769
13213
|
function lockPluginNames(agentsHome) {
|
|
12770
13214
|
const out4 = /* @__PURE__ */ new Map();
|
|
12771
|
-
const j = readJson(
|
|
13215
|
+
const j = readJson(join4(agentsHome, ".skill-lock.json"));
|
|
12772
13216
|
const skills = j?.skills;
|
|
12773
13217
|
if (!skills || typeof skills !== "object") return out4;
|
|
12774
13218
|
for (const [name2, entry] of Object.entries(skills)) {
|
|
@@ -12782,7 +13226,7 @@ function splitPluginKey(key) {
|
|
|
12782
13226
|
return at > 0 ? { pkg: key.slice(0, at), marketplace: key.slice(at + 1) } : { pkg: key, marketplace: "" };
|
|
12783
13227
|
}
|
|
12784
13228
|
function manifestRoots(home3) {
|
|
12785
|
-
const j = readJson(
|
|
13229
|
+
const j = readJson(join4(home3, "plugins", "installed_plugins.json"));
|
|
12786
13230
|
const plugins = j?.plugins;
|
|
12787
13231
|
if (!plugins || typeof plugins !== "object") return [];
|
|
12788
13232
|
const out4 = [];
|
|
@@ -12796,18 +13240,18 @@ function manifestRoots(home3) {
|
|
|
12796
13240
|
return out4;
|
|
12797
13241
|
}
|
|
12798
13242
|
function cacheRoots(home3) {
|
|
12799
|
-
const cache6 =
|
|
13243
|
+
const cache6 = join4(home3, "plugins", "cache");
|
|
12800
13244
|
const out4 = [];
|
|
12801
13245
|
for (const marketplace of dirsIn(cache6))
|
|
12802
|
-
for (const pkg of dirsIn(
|
|
12803
|
-
for (const version of dirsIn(
|
|
12804
|
-
out4.push({ pkg, marketplace, dir:
|
|
13246
|
+
for (const pkg of dirsIn(join4(cache6, marketplace)))
|
|
13247
|
+
for (const version of dirsIn(join4(cache6, marketplace, pkg)))
|
|
13248
|
+
out4.push({ pkg, marketplace, dir: join4(cache6, marketplace, pkg, version) });
|
|
12805
13249
|
return out4;
|
|
12806
13250
|
}
|
|
12807
13251
|
function disabledPlugins(agent, home3) {
|
|
12808
13252
|
const out4 = /* @__PURE__ */ new Set();
|
|
12809
13253
|
if (agent === "claude") {
|
|
12810
|
-
const en = readJson(
|
|
13254
|
+
const en = readJson(join4(home3, "settings.json"))?.enabledPlugins;
|
|
12811
13255
|
if (en && typeof en === "object") {
|
|
12812
13256
|
for (const [k, v] of Object.entries(en)) if (v === false) out4.add(k);
|
|
12813
13257
|
}
|
|
@@ -12815,7 +13259,7 @@ function disabledPlugins(agent, home3) {
|
|
|
12815
13259
|
}
|
|
12816
13260
|
let toml;
|
|
12817
13261
|
try {
|
|
12818
|
-
toml = readFileSync2(
|
|
13262
|
+
toml = readFileSync2(join4(home3, "config.toml"), "utf8");
|
|
12819
13263
|
} catch {
|
|
12820
13264
|
return out4;
|
|
12821
13265
|
}
|
|
@@ -12851,18 +13295,18 @@ function scanAgentSkills(agent, env = process.env, osHome = homedir2()) {
|
|
|
12851
13295
|
skills.push(s2);
|
|
12852
13296
|
}
|
|
12853
13297
|
};
|
|
12854
|
-
const userDir =
|
|
13298
|
+
const userDir = join4(home3, "skills");
|
|
12855
13299
|
add(userDir, skillsUnder(userDir, null));
|
|
12856
13300
|
for (const r of [...manifestRoots(home3), ...cacheRoots(home3)]) {
|
|
12857
13301
|
if (disabled.has(`${r.pkg}@${r.marketplace}`)) continue;
|
|
12858
13302
|
const found = pluginSkills(r.dir, r.pkg);
|
|
12859
13303
|
if (!found.length) continue;
|
|
12860
13304
|
packages.add(r.pkg);
|
|
12861
|
-
add(
|
|
13305
|
+
add(join4(r.dir, "skills"), found);
|
|
12862
13306
|
}
|
|
12863
13307
|
if (agent === "codex") {
|
|
12864
13308
|
const agentsHome = agentsSkillHome(env, osHome);
|
|
12865
|
-
const dir2 =
|
|
13309
|
+
const dir2 = join4(agentsHome, "skills");
|
|
12866
13310
|
const names = lockPluginNames(agentsHome);
|
|
12867
13311
|
const flat = skillsUnder(dir2, null);
|
|
12868
13312
|
const withPkg = flat.flatMap((s2) => {
|
|
@@ -12953,12 +13397,12 @@ var init_telegram_operator = __esm({
|
|
|
12953
13397
|
});
|
|
12954
13398
|
|
|
12955
13399
|
// ../runner/src/spawn-helper.ts
|
|
12956
|
-
import { readdirSync as readdirSync2, existsSync as existsSync4, statSync, chmodSync } from "node:fs";
|
|
12957
|
-
import { dirname as dirname4, join as
|
|
13400
|
+
import { readdirSync as readdirSync2, existsSync as existsSync4, statSync, chmodSync as chmodSync3 } from "node:fs";
|
|
13401
|
+
import { dirname as dirname4, join as join5 } from "node:path";
|
|
12958
13402
|
function spawnHelperPaths(ptyDir, listDir, exists) {
|
|
12959
|
-
const dirs = [
|
|
12960
|
-
for (const name2 of listDir(
|
|
12961
|
-
return dirs.map((d) =>
|
|
13403
|
+
const dirs = [join5(ptyDir, "build", "Release")];
|
|
13404
|
+
for (const name2 of listDir(join5(ptyDir, "prebuilds"))) dirs.push(join5(ptyDir, "prebuilds", name2));
|
|
13405
|
+
return dirs.map((d) => join5(d, "spawn-helper")).filter(exists);
|
|
12962
13406
|
}
|
|
12963
13407
|
function ensureSpawnHelpersExecutable(paths2, ops) {
|
|
12964
13408
|
const fixed = [];
|
|
@@ -12973,10 +13417,10 @@ function ensureSpawnHelpersExecutable(paths2, ops) {
|
|
|
12973
13417
|
}
|
|
12974
13418
|
return fixed;
|
|
12975
13419
|
}
|
|
12976
|
-
function repairSpawnHelper(
|
|
13420
|
+
function repairSpawnHelper(resolve22, notify) {
|
|
12977
13421
|
let ptyDir;
|
|
12978
13422
|
try {
|
|
12979
|
-
ptyDir = dirname4(
|
|
13423
|
+
ptyDir = dirname4(resolve22("node-pty/package.json"));
|
|
12980
13424
|
} catch {
|
|
12981
13425
|
return [];
|
|
12982
13426
|
}
|
|
@@ -12989,15 +13433,15 @@ function repairSpawnHelper(resolve21, notify) {
|
|
|
12989
13433
|
}, existsSync4);
|
|
12990
13434
|
const fixed = ensureSpawnHelpersExecutable(paths2, {
|
|
12991
13435
|
mode: (p3) => statSync(p3).mode,
|
|
12992
|
-
chmod: (p3, m) =>
|
|
13436
|
+
chmod: (p3, m) => chmodSync3(p3, m)
|
|
12993
13437
|
});
|
|
12994
13438
|
if (fixed.length) notify?.("hanoman \xB7 memperbaiki izin `spawn-helper` node-pty (sekali per instalasi)\n");
|
|
12995
13439
|
return fixed;
|
|
12996
13440
|
}
|
|
12997
|
-
function ensureSpawnHelperOnce(
|
|
13441
|
+
function ensureSpawnHelperOnce(resolve22, notify) {
|
|
12998
13442
|
if (repaired) return [];
|
|
12999
13443
|
repaired = true;
|
|
13000
|
-
return repairSpawnHelper(
|
|
13444
|
+
return repairSpawnHelper(resolve22, notify);
|
|
13001
13445
|
}
|
|
13002
13446
|
var repaired;
|
|
13003
13447
|
var init_spawn_helper = __esm({
|
|
@@ -13008,10 +13452,10 @@ var init_spawn_helper = __esm({
|
|
|
13008
13452
|
});
|
|
13009
13453
|
|
|
13010
13454
|
// ../runner/src/config-env.ts
|
|
13011
|
-
import { chmodSync as
|
|
13012
|
-
import { join as
|
|
13455
|
+
import { chmodSync as chmodSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
13456
|
+
import { join as join6 } from "node:path";
|
|
13013
13457
|
function configEnvPath(home3) {
|
|
13014
|
-
return
|
|
13458
|
+
return join6(home3, CONFIG_ENV_FILE);
|
|
13015
13459
|
}
|
|
13016
13460
|
function parseConfigEnv(text) {
|
|
13017
13461
|
const out4 = {};
|
|
@@ -13039,8 +13483,8 @@ function readConfigEnv(home3) {
|
|
|
13039
13483
|
function writeConfigEnv(home3, values) {
|
|
13040
13484
|
mkdirSync2(home3, { recursive: true, mode: 448 });
|
|
13041
13485
|
const path = configEnvPath(home3);
|
|
13042
|
-
|
|
13043
|
-
|
|
13486
|
+
writeFileSync3(path, formatConfigEnv(values), { mode: 384 });
|
|
13487
|
+
chmodSync4(path, 384);
|
|
13044
13488
|
}
|
|
13045
13489
|
var CONFIG_ENV_FILE;
|
|
13046
13490
|
var init_config_env = __esm({
|
|
@@ -13050,23 +13494,6 @@ var init_config_env = __esm({
|
|
|
13050
13494
|
}
|
|
13051
13495
|
});
|
|
13052
13496
|
|
|
13053
|
-
// ../runner/src/runtime-profile.ts
|
|
13054
|
-
function resolveHardening(env) {
|
|
13055
|
-
if (env.HANOMAN_HARDENING === "1") return true;
|
|
13056
|
-
return env.HANOMAN_SESSION_SANDBOX === "podman" || filled(env.HANOMAN_PUBLIC_ORIGINS) || filled(env.HANOMAN_TRUST_PROXY);
|
|
13057
|
-
}
|
|
13058
|
-
function resolveDeployment(env) {
|
|
13059
|
-
if (resolveHardening(env)) return "public";
|
|
13060
|
-
return env.HANOMAN_DEPLOYMENT === "public" ? "public" : "local";
|
|
13061
|
-
}
|
|
13062
|
-
var filled;
|
|
13063
|
-
var init_runtime_profile = __esm({
|
|
13064
|
-
"../runner/src/runtime-profile.ts"() {
|
|
13065
|
-
"use strict";
|
|
13066
|
-
filled = (v) => !!v && v.trim() !== "";
|
|
13067
|
-
}
|
|
13068
|
-
});
|
|
13069
|
-
|
|
13070
13497
|
// ../runner/src/sandbox-probe.ts
|
|
13071
13498
|
import { execFileSync } from "node:child_process";
|
|
13072
13499
|
import { accessSync, constants } from "node:fs";
|
|
@@ -13179,6 +13606,9 @@ var init_src2 = __esm({
|
|
|
13179
13606
|
init_codex_settings();
|
|
13180
13607
|
init_agent_cli();
|
|
13181
13608
|
init_custom_agents();
|
|
13609
|
+
init_agent_readonly();
|
|
13610
|
+
init_codex_agent_config();
|
|
13611
|
+
init_custom_agent_eval();
|
|
13182
13612
|
init_verify_scope();
|
|
13183
13613
|
init_code_style();
|
|
13184
13614
|
init_paths();
|
|
@@ -13465,9 +13895,9 @@ var init_session_id = __esm({
|
|
|
13465
13895
|
});
|
|
13466
13896
|
|
|
13467
13897
|
// src/services/secret-box.ts
|
|
13468
|
-
import { chmodSync as
|
|
13898
|
+
import { chmodSync as chmodSync5, existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
|
|
13469
13899
|
import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
|
|
13470
|
-
import { dirname as dirname6, join as
|
|
13900
|
+
import { dirname as dirname6, join as join7 } from "node:path";
|
|
13471
13901
|
function isEncrypted(value) {
|
|
13472
13902
|
return value.startsWith(ENC_PREFIX);
|
|
13473
13903
|
}
|
|
@@ -13499,7 +13929,7 @@ function fromEnv(raw) {
|
|
|
13499
13929
|
return null;
|
|
13500
13930
|
}
|
|
13501
13931
|
function secretKeyPath() {
|
|
13502
|
-
return
|
|
13932
|
+
return join7(resolveHome(), "secret.key");
|
|
13503
13933
|
}
|
|
13504
13934
|
function secretKey() {
|
|
13505
13935
|
if (cached) return cached;
|
|
@@ -13519,8 +13949,8 @@ function secretKey() {
|
|
|
13519
13949
|
}
|
|
13520
13950
|
const key = randomBytes(KEY_BYTES);
|
|
13521
13951
|
mkdirSync4(dirname6(path), { recursive: true });
|
|
13522
|
-
|
|
13523
|
-
|
|
13952
|
+
writeFileSync4(path, key.toString("base64url"), { mode: 384 });
|
|
13953
|
+
chmodSync5(path, 384);
|
|
13524
13954
|
cached = key;
|
|
13525
13955
|
return key;
|
|
13526
13956
|
}
|
|
@@ -13713,7 +14143,7 @@ import { request as httpRequest } from "node:http";
|
|
|
13713
14143
|
import { request as httpsRequest } from "node:https";
|
|
13714
14144
|
import { createGunzip } from "node:zlib";
|
|
13715
14145
|
async function pinnedRequest(input) {
|
|
13716
|
-
return new Promise((
|
|
14146
|
+
return new Promise((resolve22, reject2) => {
|
|
13717
14147
|
const transport = input.url.protocol === "https:" ? httpsRequest : httpRequest;
|
|
13718
14148
|
const req = transport({
|
|
13719
14149
|
protocol: input.url.protocol,
|
|
@@ -13735,7 +14165,7 @@ async function pinnedRequest(input) {
|
|
|
13735
14165
|
const capTerurai = input.maxDecodedBytes ?? input.maxResponseBytes;
|
|
13736
14166
|
const chunks = [];
|
|
13737
14167
|
let kabel = 0, terurai = 0;
|
|
13738
|
-
const selesai = () =>
|
|
14168
|
+
const selesai = () => resolve22({
|
|
13739
14169
|
status: response.statusCode ?? 0,
|
|
13740
14170
|
headers: response.headers,
|
|
13741
14171
|
body: Buffer.concat(chunks)
|
|
@@ -13801,7 +14231,7 @@ var init_safe_outbound_request = __esm({
|
|
|
13801
14231
|
// src/services/uploads.ts
|
|
13802
14232
|
import { randomUUID } from "node:crypto";
|
|
13803
14233
|
import { mkdir, writeFile, readFile as readFile2, unlink, rm } from "node:fs/promises";
|
|
13804
|
-
import { join as
|
|
14234
|
+
import { join as join8, resolve as resolve8 } from "node:path";
|
|
13805
14235
|
function extFor(mimeType) {
|
|
13806
14236
|
return EXT[mimeType] ?? ".bin";
|
|
13807
14237
|
}
|
|
@@ -13810,12 +14240,12 @@ function uploadDir() {
|
|
|
13810
14240
|
}
|
|
13811
14241
|
function sessionUploadDir(sessionId2) {
|
|
13812
14242
|
if (!SESSION_ID.test(sessionId2)) throw new Error(`sessionId tak sah: ${sessionId2}`);
|
|
13813
|
-
return
|
|
14243
|
+
return join8(uploadDir(), "terminal", sessionId2);
|
|
13814
14244
|
}
|
|
13815
14245
|
async function saveSessionUpload(sessionId2, buf, mimeType) {
|
|
13816
14246
|
const dir2 = sessionUploadDir(sessionId2);
|
|
13817
14247
|
await mkdir(dir2, { recursive: true, mode: 448 });
|
|
13818
|
-
const path =
|
|
14248
|
+
const path = join8(dir2, `${randomUUID()}${extFor(mimeType)}`);
|
|
13819
14249
|
await writeFile(path, buf, { mode: 384 });
|
|
13820
14250
|
return { path, size: buf.length };
|
|
13821
14251
|
}
|
|
@@ -13831,11 +14261,11 @@ async function dropSessionUploads(sessionId2) {
|
|
|
13831
14261
|
}
|
|
13832
14262
|
async function readUpload(storageKey) {
|
|
13833
14263
|
const safe = storageKey.replace(/[/\\]/g, "");
|
|
13834
|
-
return readFile2(
|
|
14264
|
+
return readFile2(join8(uploadDir(), safe));
|
|
13835
14265
|
}
|
|
13836
14266
|
async function readUploadOrFetch(storageKey) {
|
|
13837
14267
|
const safe = storageKey.replace(/[/\\]/g, "");
|
|
13838
|
-
const target2 =
|
|
14268
|
+
const target2 = join8(uploadDir(), safe);
|
|
13839
14269
|
try {
|
|
13840
14270
|
return await readFile2(target2);
|
|
13841
14271
|
} catch {
|
|
@@ -13862,7 +14292,7 @@ async function readUploadOrFetch(storageKey) {
|
|
|
13862
14292
|
}
|
|
13863
14293
|
async function deleteUpload(storageKey) {
|
|
13864
14294
|
const safe = storageKey.replace(/[/\\]/g, "");
|
|
13865
|
-
await unlink(
|
|
14295
|
+
await unlink(join8(uploadDir(), safe)).catch(() => {
|
|
13866
14296
|
});
|
|
13867
14297
|
}
|
|
13868
14298
|
var EXT, SESSION_ID;
|
|
@@ -14211,11 +14641,15 @@ function sandboxArgv(input) {
|
|
|
14211
14641
|
const mounts = ["--volume", `${input.worktree}:/workspace:${input.worktreeMode ?? "rw"}`];
|
|
14212
14642
|
if (input.phaseFile) mounts.push("--volume", `${input.phaseFile}:${input.phaseFile}:rw`);
|
|
14213
14643
|
if (input.promptFile) mounts.push("--volume", `${input.promptFile}:${input.promptFile}:ro`);
|
|
14644
|
+
if (input.agentConfigDir)
|
|
14645
|
+
mounts.push("--volume", `${input.agentConfigDir}:${input.agentConfigDir}:ro`);
|
|
14646
|
+
if (input.eventDir)
|
|
14647
|
+
mounts.push("--volume", `${input.eventDir}:${input.eventDir}:rw`);
|
|
14214
14648
|
if (input.attachmentsDir)
|
|
14215
14649
|
mounts.push("--volume", `${input.attachmentsDir}:${input.attachmentsDir}:ro`);
|
|
14216
14650
|
mounts.push("--volume", `${input.credentialDir}:/agent-home:ro`);
|
|
14217
14651
|
return [
|
|
14218
|
-
"podman",
|
|
14652
|
+
input.podmanBin ?? "podman",
|
|
14219
14653
|
"run",
|
|
14220
14654
|
"--rm",
|
|
14221
14655
|
"--read-only",
|
|
@@ -14243,6 +14677,7 @@ function sandboxArgv(input) {
|
|
|
14243
14677
|
"NO_PROXY=localhost,127.0.0.1,::1",
|
|
14244
14678
|
"--env",
|
|
14245
14679
|
"HOME=/agent-home",
|
|
14680
|
+
...input.eventDir ? ["--env", `HANOMAN_EVENT_DIR=${input.eventDir}`] : [],
|
|
14246
14681
|
...mounts,
|
|
14247
14682
|
input.image,
|
|
14248
14683
|
"/bin/sh",
|
|
@@ -14262,6 +14697,7 @@ function sandboxArgvFromEnv(input) {
|
|
|
14262
14697
|
...input,
|
|
14263
14698
|
credentialDir,
|
|
14264
14699
|
proxy,
|
|
14700
|
+
podmanBin: env.HANOMAN_PODMAN_BIN ?? "podman",
|
|
14265
14701
|
image: env.HANOMAN_SESSION_IMAGE ?? "hanoman-agent:latest",
|
|
14266
14702
|
network: env.HANOMAN_SESSION_NETWORK ?? "hanoman-egress"
|
|
14267
14703
|
});
|
|
@@ -14280,18 +14716,30 @@ var init_session_sandbox = __esm({
|
|
|
14280
14716
|
}
|
|
14281
14717
|
});
|
|
14282
14718
|
|
|
14719
|
+
// src/services/session-event-spool.ts
|
|
14720
|
+
import { tmpdir } from "node:os";
|
|
14721
|
+
import { join as join9 } from "node:path";
|
|
14722
|
+
var sessionEventSpoolRoot, sessionEventDir;
|
|
14723
|
+
var init_session_event_spool = __esm({
|
|
14724
|
+
"src/services/session-event-spool.ts"() {
|
|
14725
|
+
"use strict";
|
|
14726
|
+
sessionEventSpoolRoot = () => join9(tmpdir(), "hanoman-session-events");
|
|
14727
|
+
sessionEventDir = (sessionId2) => join9(sessionEventSpoolRoot(), sessionId2);
|
|
14728
|
+
}
|
|
14729
|
+
});
|
|
14730
|
+
|
|
14283
14731
|
// src/services/pty.ts
|
|
14284
14732
|
import { spawn } from "node-pty";
|
|
14285
14733
|
import { execFile, execFileSync as execFileSync2 } from "node:child_process";
|
|
14286
14734
|
import { createRequire } from "node:module";
|
|
14287
14735
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
14288
|
-
import { mkdirSync as mkdirSync5, readFileSync as readFileSync6, statSync as statSync2, writeFileSync as
|
|
14736
|
+
import { chmodSync as chmodSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync6, rmSync as rmSync2, statSync as statSync2, writeFileSync as writeFileSync5 } from "node:fs";
|
|
14289
14737
|
import { dirname as dirname7 } from "node:path";
|
|
14290
|
-
import { tmpdir } from "node:os";
|
|
14738
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
14291
14739
|
function noTtyPromptEnv() {
|
|
14292
14740
|
const path = askpassDenyPath();
|
|
14293
14741
|
mkdirSync5(dirname7(path), { recursive: true, mode: 448 });
|
|
14294
|
-
|
|
14742
|
+
writeFileSync5(path, ASKPASS_DENY, { mode: 448 });
|
|
14295
14743
|
return { SSH_ASKPASS: path, SSH_ASKPASS_REQUIRE: "force", GIT_TERMINAL_PROMPT: "0" };
|
|
14296
14744
|
}
|
|
14297
14745
|
function tmux(...args) {
|
|
@@ -14310,14 +14758,14 @@ function tmux(...args) {
|
|
|
14310
14758
|
}
|
|
14311
14759
|
}
|
|
14312
14760
|
function tmuxAsync(...args) {
|
|
14313
|
-
return new Promise((
|
|
14761
|
+
return new Promise((resolve22, reject2) => {
|
|
14314
14762
|
execFile(
|
|
14315
14763
|
"tmux",
|
|
14316
14764
|
["-L", socket(), "-f", "/dev/null", ...args],
|
|
14317
14765
|
{ encoding: "utf8" },
|
|
14318
14766
|
(e, stdout, stderr) => {
|
|
14319
14767
|
if (!e) {
|
|
14320
|
-
|
|
14768
|
+
resolve22(stdout);
|
|
14321
14769
|
return;
|
|
14322
14770
|
}
|
|
14323
14771
|
const err = e;
|
|
@@ -14368,7 +14816,8 @@ function parsePanes(out4) {
|
|
|
14368
14816
|
alternate,
|
|
14369
14817
|
activity,
|
|
14370
14818
|
eventHook,
|
|
14371
|
-
created
|
|
14819
|
+
created,
|
|
14820
|
+
agentRoster
|
|
14372
14821
|
] = line.split(" ");
|
|
14373
14822
|
if (!n2?.startsWith(PREFIX)) return [];
|
|
14374
14823
|
const exited = dead === "1";
|
|
@@ -14398,10 +14847,30 @@ function parsePanes(out4) {
|
|
|
14398
14847
|
// SPEC-919 · sesi yang lahir di tmux yang tak menjawab field ini → 0 (epoch).
|
|
14399
14848
|
startedAt: Number(created) || 0,
|
|
14400
14849
|
// SPEC-909 · ADR-0146 · sesi yang lahir sebelum pembaruan tak punya opsi ini → false.
|
|
14401
|
-
eventHook: eventHook === "1"
|
|
14850
|
+
eventHook: eventHook === "1",
|
|
14851
|
+
agentRoster: parseAgentRoster(agentRoster)
|
|
14402
14852
|
}];
|
|
14403
14853
|
});
|
|
14404
14854
|
}
|
|
14855
|
+
function parseAgentRoster(value) {
|
|
14856
|
+
try {
|
|
14857
|
+
const parsed = JSON.parse(value || "[]");
|
|
14858
|
+
if (!Array.isArray(parsed)) return [];
|
|
14859
|
+
return parsed.flatMap((entry) => {
|
|
14860
|
+
if (!entry || typeof entry !== "object") return [];
|
|
14861
|
+
const row = entry;
|
|
14862
|
+
if (typeof row.name !== "string") return [];
|
|
14863
|
+
return [{
|
|
14864
|
+
name: row.name,
|
|
14865
|
+
...typeof row.id === "string" ? { id: row.id } : {},
|
|
14866
|
+
...typeof row.model === "string" ? { model: row.model } : {},
|
|
14867
|
+
...typeof row.timeoutSeconds === "number" ? { timeoutSeconds: row.timeoutSeconds } : {}
|
|
14868
|
+
}];
|
|
14869
|
+
});
|
|
14870
|
+
} catch {
|
|
14871
|
+
return [];
|
|
14872
|
+
}
|
|
14873
|
+
}
|
|
14405
14874
|
function sessionEventEnv(sessionId2, env = process.env) {
|
|
14406
14875
|
const port2 = Number(env.PORT ?? 8787);
|
|
14407
14876
|
const host2 = controlHost(loadIngressPolicy(env));
|
|
@@ -14409,6 +14878,7 @@ function sessionEventEnv(sessionId2, env = process.env) {
|
|
|
14409
14878
|
HANOMAN_SESSION_ID: sessionId2,
|
|
14410
14879
|
HANOMAN_EVENT_URL: `http://127.0.0.1:${port2}/api/session-events`,
|
|
14411
14880
|
HANOMAN_EVENT_TOKEN: sessionEventToken(sessionId2),
|
|
14881
|
+
HANOMAN_EVENT_DIR: sessionEventDir(sessionId2),
|
|
14412
14882
|
...host2 ? { HANOMAN_EVENT_HOST: host2 } : {}
|
|
14413
14883
|
};
|
|
14414
14884
|
}
|
|
@@ -14418,6 +14888,24 @@ function registerSessionHooks(h) {
|
|
|
14418
14888
|
function registerCustomAgentSource(fn) {
|
|
14419
14889
|
customAgentSource = fn;
|
|
14420
14890
|
}
|
|
14891
|
+
function registerCodexNativeAgentSupport(fn) {
|
|
14892
|
+
codexNativeAgentSupport = fn;
|
|
14893
|
+
}
|
|
14894
|
+
function collectChangedFiles(cwd, baseSha, run4 = runGitDiff) {
|
|
14895
|
+
try {
|
|
14896
|
+
const files = /* @__PURE__ */ new Set();
|
|
14897
|
+
const add = (args) => {
|
|
14898
|
+
for (const path of run4(cwd, args).split("\0")) if (path) files.add(path);
|
|
14899
|
+
};
|
|
14900
|
+
if (baseSha) add(["diff", "--name-only", "-z", `${baseSha}...HEAD`]);
|
|
14901
|
+
add(["diff", "--name-only", "-z"]);
|
|
14902
|
+
add(["diff", "--cached", "--name-only", "-z"]);
|
|
14903
|
+
add(["ls-files", "--others", "--exclude-standard", "-z"]);
|
|
14904
|
+
return [...files].sort();
|
|
14905
|
+
} catch {
|
|
14906
|
+
return [];
|
|
14907
|
+
}
|
|
14908
|
+
}
|
|
14421
14909
|
function sessionKind(o, projectId, cwd) {
|
|
14422
14910
|
if (o.specId) return "spec";
|
|
14423
14911
|
if (o.flow === "reverse" || o.flow === "prd" || o.flow === "scaffold" || o.flow === "breakdown") return o.flow;
|
|
@@ -14440,15 +14928,62 @@ function createSession(projectId, cwd, opts = {}) {
|
|
|
14440
14928
|
const existing = getSession(id);
|
|
14441
14929
|
if (existing && !existing.exited) return existing;
|
|
14442
14930
|
if (existing) killSession(id);
|
|
14931
|
+
const eventDir = opts.command ? void 0 : sessionEventDir(id);
|
|
14932
|
+
if (eventDir) {
|
|
14933
|
+
mkdirSync5(eventDir, { recursive: true, mode: 448 });
|
|
14934
|
+
chmodSync6(eventDir, 448);
|
|
14935
|
+
}
|
|
14443
14936
|
const agentForDefs = opts.agent ?? "claude";
|
|
14444
|
-
const
|
|
14445
|
-
|
|
14937
|
+
const selectionContext = {
|
|
14938
|
+
projectId,
|
|
14939
|
+
runtime: agentForDefs,
|
|
14940
|
+
flow: opts.flow,
|
|
14941
|
+
cwd,
|
|
14942
|
+
baseSha: opts.env?.HANOMAN_BASE_SHA,
|
|
14943
|
+
prompt: opts.prompt,
|
|
14944
|
+
changedFiles: opts.command ? [] : collectChangedFiles(cwd, opts.env?.HANOMAN_BASE_SHA)
|
|
14945
|
+
};
|
|
14946
|
+
const customDefs = opts.command ? [] : customAgentsFor(selectionContext);
|
|
14947
|
+
let rosterBlock = "";
|
|
14948
|
+
let codexAgentArgs = [];
|
|
14949
|
+
let agentsFile;
|
|
14950
|
+
let agentConfigDir;
|
|
14951
|
+
let liveAgentDefs = [];
|
|
14952
|
+
if (customDefs.length > 0) {
|
|
14953
|
+
const tempDir = agentTempDir(id);
|
|
14954
|
+
agentConfigDir = tempDir;
|
|
14955
|
+
mkdirSync5(tempDir, { recursive: true, mode: 448 });
|
|
14956
|
+
const readOnlyHook = customDefs.some((def2) => def2.workspacePolicy === "read-only") ? writeReadOnlyHook(tempDir) : void 0;
|
|
14957
|
+
if (agentForDefs === "claude") {
|
|
14958
|
+
const json = renderAgentsJson(customDefs, { readOnlyHookCommand: readOnlyHook?.command });
|
|
14959
|
+
if (json) {
|
|
14960
|
+
agentsFile = agentsFilePath(id);
|
|
14961
|
+
writeFileSync5(agentsFile, json, { mode: 384 });
|
|
14962
|
+
rosterBlock = agentDelegationClause(customDefs, "claude");
|
|
14963
|
+
liveAgentDefs = customDefs;
|
|
14964
|
+
}
|
|
14965
|
+
} else {
|
|
14966
|
+
const materialized = materializeCodexAgents(customDefs, tempDir, {
|
|
14967
|
+
readOnlyHookCommand: readOnlyHook?.command,
|
|
14968
|
+
clientVersion: codexNativeAgentSupport().version
|
|
14969
|
+
});
|
|
14970
|
+
codexAgentArgs = materialized.args;
|
|
14971
|
+
rosterBlock = materialized.delegationClause;
|
|
14972
|
+
liveAgentDefs = materialized.liveDefs;
|
|
14973
|
+
for (const warning of materialized.warnings) {
|
|
14974
|
+
process.stderr.write(
|
|
14975
|
+
`hanoman: custom agent ${warning.agentName} tidak dimaterialisasi: ${warning.reason}
|
|
14976
|
+
`
|
|
14977
|
+
);
|
|
14978
|
+
}
|
|
14979
|
+
}
|
|
14980
|
+
}
|
|
14446
14981
|
let promptArg = "";
|
|
14447
14982
|
let promptFile;
|
|
14448
14983
|
if (!opts.command && opts.prompt) {
|
|
14449
14984
|
promptFile = promptFilePath(id);
|
|
14450
14985
|
mkdirSync5(dirname7(promptFile), { recursive: true, mode: 448 });
|
|
14451
|
-
|
|
14986
|
+
writeFileSync5(promptFile, opts.prompt + rosterBlock, { mode: 384 });
|
|
14452
14987
|
promptArg = `"$(cat ${sq(promptFile)})"`;
|
|
14453
14988
|
}
|
|
14454
14989
|
const agent = opts.agent ?? "claude";
|
|
@@ -14460,7 +14995,7 @@ function createSession(projectId, cwd, opts = {}) {
|
|
|
14460
14995
|
if (agent === "codex" && opts.goal && opts.flow && opts.specId) {
|
|
14461
14996
|
goalGate = goalGatePath(id);
|
|
14462
14997
|
mkdirSync5(dirname7(goalGate), { recursive: true, mode: 448 });
|
|
14463
|
-
|
|
14998
|
+
writeFileSync5(goalGate, codexGoalScript({
|
|
14464
14999
|
flow: opts.flow,
|
|
14465
15000
|
specId: opts.specId,
|
|
14466
15001
|
condition: opts.goal,
|
|
@@ -14470,15 +15005,6 @@ function createSession(projectId, cwd, opts = {}) {
|
|
|
14470
15005
|
}), { mode: 448 });
|
|
14471
15006
|
}
|
|
14472
15007
|
const effort = agent === "codex" && opts.model && opts.effort ? coerceCodexEffort(opts.model, opts.effort) : opts.effort;
|
|
14473
|
-
let agentsFile;
|
|
14474
|
-
if (agent === "claude") {
|
|
14475
|
-
const json = renderAgentsJson(customDefs);
|
|
14476
|
-
if (json) {
|
|
14477
|
-
agentsFile = agentsFilePath(id);
|
|
14478
|
-
mkdirSync5(dirname7(agentsFile), { recursive: true, mode: 448 });
|
|
14479
|
-
writeFileSync3(agentsFile, json, { mode: 384 });
|
|
14480
|
-
}
|
|
14481
|
-
}
|
|
14482
15008
|
const flags = agentFlags({
|
|
14483
15009
|
agent,
|
|
14484
15010
|
model: opts.model,
|
|
@@ -14491,7 +15017,8 @@ function createSession(projectId, cwd, opts = {}) {
|
|
|
14491
15017
|
eventHook: true
|
|
14492
15018
|
}).map(sq).join(" ");
|
|
14493
15019
|
const agentsArg = agentsFile ? `--agents "$(cat ${sq(agentsFile)})"` : "";
|
|
14494
|
-
|
|
15020
|
+
const nativeAgentArgs = agent === "codex" ? codexAgentArgs.map(sq).join(" ") : "";
|
|
15021
|
+
argv = [sq(agentBin(agent)), promptArg, flags, agentsArg, nativeAgentArgs].filter(Boolean).join(" ");
|
|
14495
15022
|
}
|
|
14496
15023
|
const envPairs = [];
|
|
14497
15024
|
if (!opts.command && agent === "claude") {
|
|
@@ -14510,16 +15037,16 @@ function createSession(projectId, cwd, opts = {}) {
|
|
|
14510
15037
|
if (opts.attachmentsDir) envPairs.push(`HANOMAN_ATTACHMENTS_DIR=${sq(opts.attachmentsDir)}`);
|
|
14511
15038
|
for (const [k, v] of Object.entries(opts.env ?? {})) envPairs.push(`${k}=${sq(v)}`);
|
|
14512
15039
|
let cmd = envPairs.length ? `${envPairs.join(" ")} ${argv}` : argv;
|
|
14513
|
-
let sandboxed = false;
|
|
14514
15040
|
if (!opts.command) {
|
|
14515
15041
|
const wrapped = sandboxCommand({
|
|
14516
15042
|
command: cmd,
|
|
14517
15043
|
worktree: cwd,
|
|
14518
15044
|
phaseFile: opts.phaseFile,
|
|
14519
15045
|
promptFile,
|
|
15046
|
+
agentConfigDir,
|
|
15047
|
+
eventDir,
|
|
14520
15048
|
attachmentsDir: opts.attachmentsDir
|
|
14521
15049
|
});
|
|
14522
|
-
sandboxed = wrapped !== cmd;
|
|
14523
15050
|
cmd = wrapped;
|
|
14524
15051
|
}
|
|
14525
15052
|
if (opts.decisionFile) mkdirSync5(dirname7(opts.decisionFile), { recursive: true });
|
|
@@ -14594,9 +15121,18 @@ function createSession(projectId, cwd, opts = {}) {
|
|
|
14594
15121
|
if (opts.flow) tmux("set-option", "-t", name(id), "@hanoman_flow", opts.flow);
|
|
14595
15122
|
if (opts.branch) tmux("set-option", "-t", name(id), "@hanoman_branch", opts.branch);
|
|
14596
15123
|
tmux("set-option", "-t", name(id), "@hanoman_agent", agent);
|
|
15124
|
+
if (liveAgentDefs.length > 0) {
|
|
15125
|
+
const roster = liveAgentDefs.map((def2) => ({
|
|
15126
|
+
...def2.id ? { id: def2.id } : {},
|
|
15127
|
+
name: def2.name,
|
|
15128
|
+
...def2.model ? { model: def2.model } : {},
|
|
15129
|
+
...def2.timeoutSeconds ? { timeoutSeconds: def2.timeoutSeconds } : {}
|
|
15130
|
+
}));
|
|
15131
|
+
tmux("set-option", "-t", name(id), "@hanoman_agent_roster", JSON.stringify(roster));
|
|
15132
|
+
}
|
|
14597
15133
|
if (opts.phaseFile) tmux("set-option", "-t", name(id), "@hanoman_phase_file", opts.phaseFile);
|
|
14598
15134
|
if (opts.decisionFile) tmux("set-option", "-t", name(id), "@hanoman_decision_file", opts.decisionFile);
|
|
14599
|
-
if (!opts.command
|
|
15135
|
+
if (!opts.command) tmux("set-option", "-t", name(id), "@hanoman_event_hook", "1");
|
|
14600
15136
|
if (opts.goal && !opts.command) void armGoalInTui(id, opts.goal, { agent }).catch(() => {
|
|
14601
15137
|
});
|
|
14602
15138
|
emitBirth({
|
|
@@ -14787,10 +15323,10 @@ function startPoll() {
|
|
|
14787
15323
|
poll = setInterval(() => {
|
|
14788
15324
|
if (polling) return;
|
|
14789
15325
|
polling = true;
|
|
14790
|
-
const
|
|
15326
|
+
const snapshot3 = [...attached.entries()];
|
|
14791
15327
|
listPanesAsync().then((panes) => {
|
|
14792
15328
|
const live = new Map(panes.map((p3) => [p3.id, p3]));
|
|
14793
|
-
for (const [id, a] of
|
|
15329
|
+
for (const [id, a] of snapshot3) {
|
|
14794
15330
|
if (attached.get(id) !== a) continue;
|
|
14795
15331
|
const p3 = live.get(id);
|
|
14796
15332
|
if (!p3) end(id, 0);
|
|
@@ -14853,6 +15389,14 @@ function killSession(id) {
|
|
|
14853
15389
|
const transcript = captureTranscript(id);
|
|
14854
15390
|
drop(id);
|
|
14855
15391
|
tmux("kill-session", "-t", name(id));
|
|
15392
|
+
try {
|
|
15393
|
+
rmSync2(agentTempDir(id), { recursive: true, force: true });
|
|
15394
|
+
} catch {
|
|
15395
|
+
}
|
|
15396
|
+
try {
|
|
15397
|
+
rmSync2(sessionEventDir(id), { recursive: true, force: true });
|
|
15398
|
+
} catch {
|
|
15399
|
+
}
|
|
14856
15400
|
emitDeath({ sessionId: id, exitCode: p3.exited ? p3.code : null, transcript });
|
|
14857
15401
|
void dropSessionUploads(id).catch(() => {
|
|
14858
15402
|
});
|
|
@@ -14878,7 +15422,7 @@ function spawnPty(...args) {
|
|
|
14878
15422
|
);
|
|
14879
15423
|
}
|
|
14880
15424
|
}
|
|
14881
|
-
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, agentsFilePath, askpassDenyPath, ASKPASS_DENY, NO_SERVER, TmuxError, sq, idFor, FMT, toSessionInfo, listSessions, listSessionsAsync, liveDecisions, getSession, getSessionAsync, hooks, emitBirth, emitDeath, customAgentSource, customAgentsFor, 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;
|
|
15425
|
+
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;
|
|
14882
15426
|
var init_pty = __esm({
|
|
14883
15427
|
"src/services/pty.ts"() {
|
|
14884
15428
|
"use strict";
|
|
@@ -14892,6 +15436,8 @@ var init_pty = __esm({
|
|
|
14892
15436
|
init_ingress_policy();
|
|
14893
15437
|
init_session_event_token();
|
|
14894
15438
|
init_session_sandbox();
|
|
15439
|
+
init_session_event_spool();
|
|
15440
|
+
init_session_event_spool();
|
|
14895
15441
|
socket = () => effectiveStr("HANOMAN_TMUX_SOCKET") ?? "hanoman";
|
|
14896
15442
|
PREFIX = "hanoman-";
|
|
14897
15443
|
MAX_SCROLLBACK = 256 * 1024;
|
|
@@ -14906,7 +15452,7 @@ var init_pty = __esm({
|
|
|
14906
15452
|
};
|
|
14907
15453
|
clearMarker = (f) => {
|
|
14908
15454
|
try {
|
|
14909
|
-
|
|
15455
|
+
writeFileSync5(f, "");
|
|
14910
15456
|
} catch {
|
|
14911
15457
|
}
|
|
14912
15458
|
};
|
|
@@ -14934,11 +15480,12 @@ var init_pty = __esm({
|
|
|
14934
15480
|
rootBypassEnv = (uid = process.getuid?.()) => uid === 0 ? { IS_SANDBOX: "1" } : {};
|
|
14935
15481
|
frame = (f) => JSON.stringify(f);
|
|
14936
15482
|
name = (id) => PREFIX + id;
|
|
14937
|
-
promptFilePath = (id) => `${
|
|
14938
|
-
goalGatePath = (id) => `${
|
|
14939
|
-
goalStatePath = (id) => `${
|
|
14940
|
-
|
|
14941
|
-
|
|
15483
|
+
promptFilePath = (id) => `${tmpdir2()}/hanoman-prompts/${id}`;
|
|
15484
|
+
goalGatePath = (id) => `${tmpdir2()}/hanoman-goal-gates/${id}.sh`;
|
|
15485
|
+
goalStatePath = (id) => `${tmpdir2()}/hanoman-goal-gates/${id}.count`;
|
|
15486
|
+
agentTempDir = (id) => `${tmpdir2()}/hanoman-agents/${id}`;
|
|
15487
|
+
agentsFilePath = (id) => `${agentTempDir(id)}/claude.json`;
|
|
15488
|
+
askpassDenyPath = () => `${tmpdir2()}/hanoman-askpass/deny.sh`;
|
|
14942
15489
|
ASKPASS_DENY = `#!/bin/sh
|
|
14943
15490
|
echo "hanoman: tak ada manusia di pane ini \u2014 permintaan ketikan ditolak: $1" >&2
|
|
14944
15491
|
echo "hanoman: buka kuncinya di luar sesi (mis. ssh-add ~/.ssh/id_rsa), lalu ulangi." >&2
|
|
@@ -14970,7 +15517,8 @@ exit 1
|
|
|
14970
15517
|
// `pty-parse.test.ts` mengunci panjang FMT terhadap destructuring `parsePanes`.
|
|
14971
15518
|
"#{window_activity}",
|
|
14972
15519
|
"#{@hanoman_event_hook}",
|
|
14973
|
-
"#{session_created}"
|
|
15520
|
+
"#{session_created}",
|
|
15521
|
+
"#{@hanoman_agent_roster}"
|
|
14974
15522
|
].join(" ");
|
|
14975
15523
|
toSessionInfo = ({
|
|
14976
15524
|
id,
|
|
@@ -15028,13 +15576,18 @@ exit 1
|
|
|
15028
15576
|
}
|
|
15029
15577
|
};
|
|
15030
15578
|
customAgentSource = () => [];
|
|
15031
|
-
|
|
15579
|
+
codexNativeAgentSupport = () => ({ version: "0.151.0", ok: true });
|
|
15580
|
+
customAgentsFor = (context) => {
|
|
15032
15581
|
try {
|
|
15033
|
-
return customAgentSource(
|
|
15582
|
+
return customAgentSource(context);
|
|
15034
15583
|
} catch {
|
|
15035
15584
|
return [];
|
|
15036
15585
|
}
|
|
15037
15586
|
};
|
|
15587
|
+
runGitDiff = (cwd, args) => execFileSync2("git", ["-C", cwd, ...args], {
|
|
15588
|
+
encoding: "utf8",
|
|
15589
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
15590
|
+
});
|
|
15038
15591
|
sleep = (ms) => new Promise((r) => {
|
|
15039
15592
|
setTimeout(r, ms);
|
|
15040
15593
|
});
|
|
@@ -15260,6 +15813,11 @@ function validateSyncData(entity, data, options2 = {}) {
|
|
|
15260
15813
|
if (!Number.isSafeInteger(value)) throw new Error(`sync tipe invalid: ${entity}.${field}`);
|
|
15261
15814
|
continue;
|
|
15262
15815
|
}
|
|
15816
|
+
if (NULLABLE_NUMBER_FIELDS.has(key)) {
|
|
15817
|
+
if (value !== null && !Number.isSafeInteger(value))
|
|
15818
|
+
throw new Error(`sync tipe invalid: ${entity}.${field}`);
|
|
15819
|
+
continue;
|
|
15820
|
+
}
|
|
15263
15821
|
if (BOOLEAN_FIELDS.has(key)) {
|
|
15264
15822
|
if (typeof value !== "boolean") throw new Error(`sync tipe invalid: ${entity}.${field}`);
|
|
15265
15823
|
continue;
|
|
@@ -15527,7 +16085,7 @@ async function upsertLocal(entity, id, version, data) {
|
|
|
15527
16085
|
function setAcceptedHook(hook) {
|
|
15528
16086
|
onAccepted = hook;
|
|
15529
16087
|
}
|
|
15530
|
-
var SYNCED, DELEGATE, FIELDS, DATE_FIELDS, PARENTS, __FIELDS, __DATE_FIELDS, NUMBER_FIELDS, FLOAT_FIELDS, BOOLEAN_FIELDS, JSON_FIELDS, __JSON_FIELDS, PULL_MAX_BYTES, BOOTSTRAP_ORDER, onAccepted, __FIELDS_FOR_TEST;
|
|
16088
|
+
var SYNCED, DELEGATE, FIELDS, DATE_FIELDS, PARENTS, __FIELDS, __DATE_FIELDS, NUMBER_FIELDS, NULLABLE_NUMBER_FIELDS, FLOAT_FIELDS, BOOLEAN_FIELDS, JSON_FIELDS, __JSON_FIELDS, PULL_MAX_BYTES, BOOTSTRAP_ORDER, onAccepted, __FIELDS_FOR_TEST;
|
|
15531
16089
|
var init_sync = __esm({
|
|
15532
16090
|
"src/services/sync.ts"() {
|
|
15533
16091
|
"use strict";
|
|
@@ -15584,7 +16142,24 @@ var init_sync = __esm({
|
|
|
15584
16142
|
// SPEC-484 · ADR-0101 · `runtime` ikut: ia menentukan sesi mesin mana yang memakai persona ini,
|
|
15585
16143
|
// dan kolom yang terlewat di sini mendarat sebagai default palsu (= "warisi") di setiap mesin
|
|
15586
16144
|
// lain tanpa satu pun error.
|
|
15587
|
-
customAgent: [
|
|
16145
|
+
customAgent: [
|
|
16146
|
+
"projectId",
|
|
16147
|
+
"name",
|
|
16148
|
+
"description",
|
|
16149
|
+
"instructions",
|
|
16150
|
+
"tools",
|
|
16151
|
+
"model",
|
|
16152
|
+
"mentions",
|
|
16153
|
+
"runtime",
|
|
16154
|
+
"activation",
|
|
16155
|
+
"effort",
|
|
16156
|
+
"workspacePolicy",
|
|
16157
|
+
"maxTurns",
|
|
16158
|
+
"timeoutSeconds",
|
|
16159
|
+
"enabled",
|
|
16160
|
+
"createdAt",
|
|
16161
|
+
"updatedAt"
|
|
16162
|
+
],
|
|
15588
16163
|
// SPEC-471 · ADR-0095 · SELURUH kolom bermakna ikut. `status`/`specId` termasuk: keputusan
|
|
15589
16164
|
// triase adalah bagian keadaan yang harus dilihat sama oleh semua mesin — tanpa itu satu
|
|
15590
16165
|
// mesin bisa menerima ulang issue yang di mesin lain sudah jadi backlog.
|
|
@@ -15663,6 +16238,7 @@ var init_sync = __esm({
|
|
|
15663
16238
|
"ticketAttachment:size",
|
|
15664
16239
|
"githubIssue:number"
|
|
15665
16240
|
]);
|
|
16241
|
+
NULLABLE_NUMBER_FIELDS = /* @__PURE__ */ new Set(["customAgent:maxTurns", "customAgent:timeoutSeconds"]);
|
|
15666
16242
|
FLOAT_FIELDS = /* @__PURE__ */ new Set(["task:order"]);
|
|
15667
16243
|
BOOLEAN_FIELDS = /* @__PURE__ */ new Set(["vps:hardened", "customAgent:enabled", "member:active"]);
|
|
15668
16244
|
JSON_FIELDS = /* @__PURE__ */ new Set([
|
|
@@ -15697,6 +16273,33 @@ var init_sync = __esm({
|
|
|
15697
16273
|
}
|
|
15698
16274
|
});
|
|
15699
16275
|
|
|
16276
|
+
// src/services/sync-notify.ts
|
|
16277
|
+
async function notifySynced(entity, id) {
|
|
16278
|
+
try {
|
|
16279
|
+
if (!isEntity(entity)) return;
|
|
16280
|
+
await consumeTombstoneOnRecreate(entity, id);
|
|
16281
|
+
if (effectiveStr("SYNC_SERVER_URL")) await enqueueOutbox(entity, id);
|
|
16282
|
+
else await publishLocal(entity, id);
|
|
16283
|
+
} catch {
|
|
16284
|
+
}
|
|
16285
|
+
}
|
|
16286
|
+
async function notifyDeleted(entity, id) {
|
|
16287
|
+
try {
|
|
16288
|
+
if (!isEntity(entity)) return;
|
|
16289
|
+
if (effectiveStr("SYNC_SERVER_URL")) await enqueueOutbox(entity, id);
|
|
16290
|
+
else await publishDelete(entity, id);
|
|
16291
|
+
} catch {
|
|
16292
|
+
}
|
|
16293
|
+
}
|
|
16294
|
+
var init_sync_notify = __esm({
|
|
16295
|
+
"src/services/sync-notify.ts"() {
|
|
16296
|
+
"use strict";
|
|
16297
|
+
init_config2();
|
|
16298
|
+
init_outbox();
|
|
16299
|
+
init_sync();
|
|
16300
|
+
}
|
|
16301
|
+
});
|
|
16302
|
+
|
|
15700
16303
|
// src/services/settings.ts
|
|
15701
16304
|
async function getSetting() {
|
|
15702
16305
|
const raw = (await prisma.setting.findUnique({ where: { id: 1 } }))?.data;
|
|
@@ -15777,8 +16380,10 @@ var init_settings3 = __esm({
|
|
|
15777
16380
|
// SPEC-518 · agen pembuat changelog (opt-in, mati)
|
|
15778
16381
|
portalChat: PORTAL_CHAT_DEFAULTS,
|
|
15779
16382
|
// SPEC-854 · ADR-0130 · chat portal klien (opt-in, mati)
|
|
15780
|
-
builtinAgents: {}
|
|
16383
|
+
builtinAgents: {},
|
|
15781
16384
|
// SPEC-881 · ADR-0136 · sidik jari seed (lokal, tak disync)
|
|
16385
|
+
builtinAgentPolicies: {}
|
|
16386
|
+
// SPEC-950 · marker safety policy sekali-jalan (lokal)
|
|
15782
16387
|
};
|
|
15783
16388
|
RETIRED_MODELS = { "claude-opus-4-8": "claude-opus-5" };
|
|
15784
16389
|
}
|
|
@@ -16184,12 +16789,12 @@ var require_common = __commonJS({
|
|
|
16184
16789
|
createDebug.skips = [];
|
|
16185
16790
|
createDebug.formatters = {};
|
|
16186
16791
|
function selectColor(namespace) {
|
|
16187
|
-
let
|
|
16792
|
+
let hash3 = 0;
|
|
16188
16793
|
for (let i = 0; i < namespace.length; i++) {
|
|
16189
|
-
|
|
16190
|
-
|
|
16794
|
+
hash3 = (hash3 << 5) - hash3 + namespace.charCodeAt(i);
|
|
16795
|
+
hash3 |= 0;
|
|
16191
16796
|
}
|
|
16192
|
-
return createDebug.colors[Math.abs(
|
|
16797
|
+
return createDebug.colors[Math.abs(hash3) % createDebug.colors.length];
|
|
16193
16798
|
}
|
|
16194
16799
|
createDebug.selectColor = selectColor;
|
|
16195
16800
|
function createDebug(namespace) {
|
|
@@ -17335,7 +17940,7 @@ var init_gateway = __esm({
|
|
|
17335
17940
|
const message = sanitizeTelegramOutput(error.message, this.deps.exactSecrets).slice(0, 500);
|
|
17336
17941
|
updateTelegramRuntimeStatus({ running: false, readiness: "error", lastError: message });
|
|
17337
17942
|
if (error instanceof TelegramApiError && error.code === 409) return;
|
|
17338
|
-
await new Promise((
|
|
17943
|
+
await new Promise((resolve22) => setTimeout(resolve22, 1e3));
|
|
17339
17944
|
}
|
|
17340
17945
|
}
|
|
17341
17946
|
}
|
|
@@ -17498,9 +18103,9 @@ ${input.text}`;
|
|
|
17498
18103
|
}
|
|
17499
18104
|
const engine = await this.deps.defaults();
|
|
17500
18105
|
await this.deps.store.setChatEngine(input.chatId, engine);
|
|
17501
|
-
const
|
|
17502
|
-
const projectId = `telegram:${
|
|
17503
|
-
const cwd = `${this.deps.home.replace(/\/$/, "")}/telegram/${
|
|
18106
|
+
const hash3 = chatHash(input.chatId);
|
|
18107
|
+
const projectId = `telegram:${hash3}`;
|
|
18108
|
+
const cwd = `${this.deps.home.replace(/\/$/, "")}/telegram/${hash3}`;
|
|
17504
18109
|
this.deps.ensureDir(cwd);
|
|
17505
18110
|
if (engine.agent === "codex") this.deps.ensureCodexTrust(cwd);
|
|
17506
18111
|
const personality = await this.deps.personality(context.personalityAgentId, context.activeProjectId);
|
|
@@ -18049,6 +18654,57 @@ var init_bootstrap = __esm({
|
|
|
18049
18654
|
}
|
|
18050
18655
|
});
|
|
18051
18656
|
|
|
18657
|
+
// src/services/codex-version.ts
|
|
18658
|
+
import { execFile as execFile14 } from "node:child_process";
|
|
18659
|
+
import { promisify as promisify13 } from "node:util";
|
|
18660
|
+
async function probeCodexVersion(env = process.env, execute = runVersion) {
|
|
18661
|
+
const probe = codexNativeVersionProbe(env, codexBin2());
|
|
18662
|
+
try {
|
|
18663
|
+
return parseCodexVersion((await execute(probe.bin, probe.args)).stdout);
|
|
18664
|
+
} catch {
|
|
18665
|
+
return null;
|
|
18666
|
+
}
|
|
18667
|
+
}
|
|
18668
|
+
function parseCodexVersion(out4) {
|
|
18669
|
+
const m = /(\d+)\.(\d+)\.(\d+)/.exec(out4);
|
|
18670
|
+
return m ? `${m[1]}.${m[2]}.${m[3]}` : null;
|
|
18671
|
+
}
|
|
18672
|
+
async function getCodexVersion(now = Date.now()) {
|
|
18673
|
+
if (cache3 && now - cache3.at < TTL_MS4) return cache3.version;
|
|
18674
|
+
const version = await probeCodexVersion();
|
|
18675
|
+
cache3 = { at: now, version };
|
|
18676
|
+
return version;
|
|
18677
|
+
}
|
|
18678
|
+
async function codexVersionInfo() {
|
|
18679
|
+
const version = await getCodexVersion();
|
|
18680
|
+
return {
|
|
18681
|
+
version,
|
|
18682
|
+
minRequired: CODEX_MIN_CLIENT,
|
|
18683
|
+
ok: version === null || cmpVersion(version, CODEX_MIN_CLIENT) >= 0
|
|
18684
|
+
};
|
|
18685
|
+
}
|
|
18686
|
+
function _resetCodexVersionCache() {
|
|
18687
|
+
cache3 = null;
|
|
18688
|
+
}
|
|
18689
|
+
var run3, CODEX_MIN_CLIENT, TTL_MS4, cache3, codexBin2, runVersion;
|
|
18690
|
+
var init_codex_version = __esm({
|
|
18691
|
+
"src/services/codex-version.ts"() {
|
|
18692
|
+
"use strict";
|
|
18693
|
+
init_src();
|
|
18694
|
+
init_src2();
|
|
18695
|
+
init_config2();
|
|
18696
|
+
run3 = promisify13(execFile14);
|
|
18697
|
+
CODEX_MIN_CLIENT = "0.144.0";
|
|
18698
|
+
TTL_MS4 = 5 * 6e4;
|
|
18699
|
+
cache3 = null;
|
|
18700
|
+
codexBin2 = () => effectiveStr("HANOMAN_CODEX_BIN") ?? "codex";
|
|
18701
|
+
runVersion = async (bin, args) => {
|
|
18702
|
+
const { stdout } = await run3(bin, args, { timeout: 1e4 });
|
|
18703
|
+
return { stdout };
|
|
18704
|
+
};
|
|
18705
|
+
}
|
|
18706
|
+
});
|
|
18707
|
+
|
|
18052
18708
|
// src/services/presence/snapshot.ts
|
|
18053
18709
|
function paneToPresence(p3, phase) {
|
|
18054
18710
|
return {
|
|
@@ -20448,7 +21104,7 @@ var require_websocket = __commonJS({
|
|
|
20448
21104
|
var http = __require("http");
|
|
20449
21105
|
var net = __require("net");
|
|
20450
21106
|
var tls = __require("tls");
|
|
20451
|
-
var { randomBytes: randomBytes11, createHash:
|
|
21107
|
+
var { randomBytes: randomBytes11, createHash: createHash12 } = __require("crypto");
|
|
20452
21108
|
var { Duplex, Readable } = __require("stream");
|
|
20453
21109
|
var { URL: URL2 } = __require("url");
|
|
20454
21110
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -21116,8 +21772,8 @@ var require_websocket = __commonJS({
|
|
|
21116
21772
|
abortHandshake(websocket2, socket2, "Invalid Upgrade header");
|
|
21117
21773
|
return;
|
|
21118
21774
|
}
|
|
21119
|
-
const
|
|
21120
|
-
if (res.headers["sec-websocket-accept"] !==
|
|
21775
|
+
const digest2 = createHash12("sha1").update(key + GUID).digest("base64");
|
|
21776
|
+
if (res.headers["sec-websocket-accept"] !== digest2) {
|
|
21121
21777
|
abortHandshake(websocket2, socket2, "Invalid Sec-WebSocket-Accept header");
|
|
21122
21778
|
return;
|
|
21123
21779
|
}
|
|
@@ -21397,7 +22053,7 @@ var require_stream = __commonJS({
|
|
|
21397
22053
|
};
|
|
21398
22054
|
duplex._final = function(callback) {
|
|
21399
22055
|
if (ws2.readyState === ws2.CONNECTING) {
|
|
21400
|
-
ws2.once("open", function
|
|
22056
|
+
ws2.once("open", function open5() {
|
|
21401
22057
|
duplex._final(callback);
|
|
21402
22058
|
});
|
|
21403
22059
|
return;
|
|
@@ -21418,7 +22074,7 @@ var require_stream = __commonJS({
|
|
|
21418
22074
|
};
|
|
21419
22075
|
duplex._write = function(chunk, encoding, callback) {
|
|
21420
22076
|
if (ws2.readyState === ws2.CONNECTING) {
|
|
21421
|
-
ws2.once("open", function
|
|
22077
|
+
ws2.once("open", function open5() {
|
|
21422
22078
|
duplex._write(chunk, encoding, callback);
|
|
21423
22079
|
});
|
|
21424
22080
|
return;
|
|
@@ -21485,7 +22141,7 @@ var require_websocket_server = __commonJS({
|
|
|
21485
22141
|
var EventEmitter = __require("events");
|
|
21486
22142
|
var http = __require("http");
|
|
21487
22143
|
var { Duplex } = __require("stream");
|
|
21488
|
-
var { createHash:
|
|
22144
|
+
var { createHash: createHash12 } = __require("crypto");
|
|
21489
22145
|
var extension2 = require_extension();
|
|
21490
22146
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
21491
22147
|
var subprotocol2 = require_subprotocol();
|
|
@@ -21792,12 +22448,12 @@ var require_websocket_server = __commonJS({
|
|
|
21792
22448
|
);
|
|
21793
22449
|
}
|
|
21794
22450
|
if (this._state > RUNNING) return abortHandshake(socket2, 503);
|
|
21795
|
-
const
|
|
22451
|
+
const digest2 = createHash12("sha1").update(key + GUID).digest("base64");
|
|
21796
22452
|
const headers = [
|
|
21797
22453
|
"HTTP/1.1 101 Switching Protocols",
|
|
21798
22454
|
"Upgrade: websocket",
|
|
21799
22455
|
"Connection: Upgrade",
|
|
21800
|
-
`Sec-WebSocket-Accept: ${
|
|
22456
|
+
`Sec-WebSocket-Accept: ${digest2}`
|
|
21801
22457
|
];
|
|
21802
22458
|
const ws2 = new this.options.WebSocket(null, void 0, this.options);
|
|
21803
22459
|
if (protocols.size) {
|
|
@@ -22343,6 +22999,382 @@ var init_sync_client = __esm({
|
|
|
22343
22999
|
}
|
|
22344
23000
|
});
|
|
22345
23001
|
|
|
23002
|
+
// src/services/agent-tool-catalog.ts
|
|
23003
|
+
import { readFileSync as readFileSync17 } from "node:fs";
|
|
23004
|
+
import { homedir as homedir10 } from "node:os";
|
|
23005
|
+
import { join as join29 } from "node:path";
|
|
23006
|
+
function mcpServerNames(repoDir) {
|
|
23007
|
+
const names = [];
|
|
23008
|
+
const claudeJson = readJson2(join29(home(), ".claude.json"));
|
|
23009
|
+
names.push(...serversOf(claudeJson));
|
|
23010
|
+
if (repoDir) {
|
|
23011
|
+
const projects = claudeJson?.projects;
|
|
23012
|
+
if (projects && typeof projects === "object") names.push(...serversOf(projects[repoDir]));
|
|
23013
|
+
names.push(...serversOf(readJson2(join29(repoDir, ".mcp.json"))));
|
|
23014
|
+
}
|
|
23015
|
+
names.push(...codexServers());
|
|
23016
|
+
return [...new Set(names.filter(Boolean))].sort((a, b) => a.localeCompare(b));
|
|
23017
|
+
}
|
|
23018
|
+
function agentToolCatalog(repoDir) {
|
|
23019
|
+
return [ALL_TOOLS_ENTRY, ...BUILTIN_AGENT_TOOLS, ...mcpServerNames(repoDir).map(mcpToolEntry)];
|
|
23020
|
+
}
|
|
23021
|
+
var home, readJson2, serversOf, codexServers, agentToolIds;
|
|
23022
|
+
var init_agent_tool_catalog = __esm({
|
|
23023
|
+
"src/services/agent-tool-catalog.ts"() {
|
|
23024
|
+
"use strict";
|
|
23025
|
+
init_src();
|
|
23026
|
+
home = () => process.env.HOME || homedir10();
|
|
23027
|
+
readJson2 = (path) => {
|
|
23028
|
+
try {
|
|
23029
|
+
return JSON.parse(readFileSync17(path, "utf8"));
|
|
23030
|
+
} catch {
|
|
23031
|
+
return null;
|
|
23032
|
+
}
|
|
23033
|
+
};
|
|
23034
|
+
serversOf = (node) => {
|
|
23035
|
+
const ms = node?.mcpServers;
|
|
23036
|
+
if (!ms || typeof ms !== "object" || Array.isArray(ms)) return [];
|
|
23037
|
+
return Object.keys(ms);
|
|
23038
|
+
};
|
|
23039
|
+
codexServers = () => {
|
|
23040
|
+
let text;
|
|
23041
|
+
try {
|
|
23042
|
+
text = readFileSync17(join29(home(), ".codex", "config.toml"), "utf8");
|
|
23043
|
+
} catch {
|
|
23044
|
+
return [];
|
|
23045
|
+
}
|
|
23046
|
+
const out4 = [];
|
|
23047
|
+
for (const m of text.matchAll(/^\s*\[mcp_servers\.(?:"([^"]+)"|([A-Za-z0-9_-]+))(?:\.[^\]]*)?\]/gm)) {
|
|
23048
|
+
const name2 = m[1] ?? m[2];
|
|
23049
|
+
if (name2) out4.push(name2);
|
|
23050
|
+
}
|
|
23051
|
+
return out4;
|
|
23052
|
+
};
|
|
23053
|
+
agentToolIds = (repoDir) => agentToolCatalog(repoDir).map((t) => t.id);
|
|
23054
|
+
}
|
|
23055
|
+
});
|
|
23056
|
+
|
|
23057
|
+
// src/services/builtin-agents.ts
|
|
23058
|
+
import { createHash as createHash9 } from "node:crypto";
|
|
23059
|
+
async function seedBuiltinAgents() {
|
|
23060
|
+
try {
|
|
23061
|
+
const setting = await getSetting();
|
|
23062
|
+
const stamps = { ...setting.builtinAgents };
|
|
23063
|
+
const policies = { ...setting.builtinAgentPolicies };
|
|
23064
|
+
let changed = false;
|
|
23065
|
+
for (const a of BUILTIN_AGENTS) {
|
|
23066
|
+
const id = customAgentId(null, a.name);
|
|
23067
|
+
const fp = builtinFingerprint(a);
|
|
23068
|
+
const row = await prisma.customAgent.findUnique({ where: { id } });
|
|
23069
|
+
const qaPolicyPending = a.name === "qa-verifier" && policies[a.name] !== QA_SAFETY_POLICY;
|
|
23070
|
+
if (!row) {
|
|
23071
|
+
if (await findTombstone("customAgent", id)) {
|
|
23072
|
+
if (qaPolicyPending) {
|
|
23073
|
+
policies[a.name] = QA_SAFETY_POLICY;
|
|
23074
|
+
changed = true;
|
|
23075
|
+
}
|
|
23076
|
+
continue;
|
|
23077
|
+
}
|
|
23078
|
+
await prisma.customAgent.create({ data: {
|
|
23079
|
+
id,
|
|
23080
|
+
projectId: null,
|
|
23081
|
+
name: a.name,
|
|
23082
|
+
description: a.description,
|
|
23083
|
+
instructions: a.instructions,
|
|
23084
|
+
tools: [...a.tools],
|
|
23085
|
+
model: null,
|
|
23086
|
+
mentions: [],
|
|
23087
|
+
runtime: null,
|
|
23088
|
+
activation: a.activation,
|
|
23089
|
+
effort: a.effort,
|
|
23090
|
+
workspacePolicy: a.workspacePolicy,
|
|
23091
|
+
maxTurns: a.maxTurns,
|
|
23092
|
+
timeoutSeconds: a.timeoutSeconds,
|
|
23093
|
+
enabled: a.enabledByDefault
|
|
23094
|
+
} });
|
|
23095
|
+
await notifySynced("customAgent", id);
|
|
23096
|
+
stamps[a.name] = fp;
|
|
23097
|
+
changed = true;
|
|
23098
|
+
if (qaPolicyPending) policies[a.name] = QA_SAFETY_POLICY;
|
|
23099
|
+
continue;
|
|
23100
|
+
}
|
|
23101
|
+
const stamped = stamps[a.name];
|
|
23102
|
+
const unedited = Boolean(stamped) && (stamped === rowFingerprint(row) || stamped === legacyRowFingerprint(row));
|
|
23103
|
+
const data = {};
|
|
23104
|
+
if (unedited && stamped !== fp) {
|
|
23105
|
+
Object.assign(data, {
|
|
23106
|
+
description: a.description,
|
|
23107
|
+
instructions: a.instructions,
|
|
23108
|
+
tools: [...a.tools],
|
|
23109
|
+
activation: a.activation,
|
|
23110
|
+
effort: a.effort,
|
|
23111
|
+
workspacePolicy: a.workspacePolicy,
|
|
23112
|
+
maxTurns: a.maxTurns,
|
|
23113
|
+
timeoutSeconds: a.timeoutSeconds
|
|
23114
|
+
});
|
|
23115
|
+
stamps[a.name] = fp;
|
|
23116
|
+
changed = true;
|
|
23117
|
+
}
|
|
23118
|
+
if (qaPolicyPending && unedited) data.enabled = false;
|
|
23119
|
+
if (Object.keys(data).length > 0) {
|
|
23120
|
+
await prisma.customAgent.update({ where: { id }, data });
|
|
23121
|
+
await notifySynced("customAgent", id);
|
|
23122
|
+
}
|
|
23123
|
+
if (qaPolicyPending) {
|
|
23124
|
+
policies[a.name] = QA_SAFETY_POLICY;
|
|
23125
|
+
changed = true;
|
|
23126
|
+
}
|
|
23127
|
+
}
|
|
23128
|
+
if (changed) {
|
|
23129
|
+
const data = { ...setting, builtinAgents: stamps, builtinAgentPolicies: policies };
|
|
23130
|
+
await prisma.setting.upsert({
|
|
23131
|
+
where: { id: 1 },
|
|
23132
|
+
update: { data },
|
|
23133
|
+
create: { id: 1, data }
|
|
23134
|
+
});
|
|
23135
|
+
}
|
|
23136
|
+
} catch {
|
|
23137
|
+
}
|
|
23138
|
+
}
|
|
23139
|
+
var digest, legacyFingerprint, fingerprint, builtinFingerprint, rowFingerprint, legacyRowFingerprint, QA_SAFETY_POLICY;
|
|
23140
|
+
var init_builtin_agents2 = __esm({
|
|
23141
|
+
"src/services/builtin-agents.ts"() {
|
|
23142
|
+
"use strict";
|
|
23143
|
+
init_src();
|
|
23144
|
+
init_db();
|
|
23145
|
+
init_settings3();
|
|
23146
|
+
init_tombstone();
|
|
23147
|
+
init_sync_notify();
|
|
23148
|
+
digest = (parts) => createHash9("sha256").update(parts.join(" ")).digest("hex").slice(0, 16);
|
|
23149
|
+
legacyFingerprint = (name2, description, instructions, tools) => digest([name2, description, instructions, [...tools].join(",")]);
|
|
23150
|
+
fingerprint = (a) => digest([
|
|
23151
|
+
a.name,
|
|
23152
|
+
a.description,
|
|
23153
|
+
a.instructions,
|
|
23154
|
+
(toolsOf(a.tools) ?? []).join(","),
|
|
23155
|
+
activationOf(a.activation),
|
|
23156
|
+
effortOf(a.effort) ?? "",
|
|
23157
|
+
workspacePolicyOf(a.workspacePolicy),
|
|
23158
|
+
String(maxTurnsOf(a.maxTurns) ?? ""),
|
|
23159
|
+
String(timeoutSecondsOf(a.timeoutSeconds) ?? "")
|
|
23160
|
+
]);
|
|
23161
|
+
builtinFingerprint = (a) => fingerprint(a);
|
|
23162
|
+
rowFingerprint = (r) => fingerprint(r);
|
|
23163
|
+
legacyRowFingerprint = (r) => legacyFingerprint(r.name, r.description, r.instructions, toolsOf(r.tools) ?? []);
|
|
23164
|
+
QA_SAFETY_POLICY = "disable-unedited-v1";
|
|
23165
|
+
}
|
|
23166
|
+
});
|
|
23167
|
+
|
|
23168
|
+
// src/services/custom-agents.ts
|
|
23169
|
+
var custom_agents_exports = {};
|
|
23170
|
+
__export(custom_agents_exports, {
|
|
23171
|
+
agentDefsFor: () => agentDefsFor,
|
|
23172
|
+
collectChangedFiles: () => collectChangedFiles,
|
|
23173
|
+
currentCustomAgentRuntimeSupport: () => currentCustomAgentRuntimeSupport,
|
|
23174
|
+
installCustomAgents: () => installCustomAgents,
|
|
23175
|
+
loadCustomAgents: () => loadCustomAgents,
|
|
23176
|
+
refreshCustomAgentRuntimeSupport: () => refreshCustomAgentRuntimeSupport,
|
|
23177
|
+
selectAgentRows: () => selectAgentRows,
|
|
23178
|
+
toDef: () => toDef,
|
|
23179
|
+
unknownMentions: () => unknownMentions,
|
|
23180
|
+
validateGraph: () => validateGraph
|
|
23181
|
+
});
|
|
23182
|
+
function smartBuiltinSelected(row, context) {
|
|
23183
|
+
switch (row.name) {
|
|
23184
|
+
case "scout":
|
|
23185
|
+
return hasPhase(context, "Plan") || hasPhase(context, "Execute") || hasPhase(context, "Audit") || context.changedFiles.length === 0;
|
|
23186
|
+
case "blast-radius":
|
|
23187
|
+
return hasPhase(context, "Execute") || hasPhase(context, "Audit") || context.changedFiles.length > 0;
|
|
23188
|
+
case "security-reviewer":
|
|
23189
|
+
return (hasPhase(context, "Execute") || hasPhase(context, "Audit")) && touchesExternalInput(context);
|
|
23190
|
+
case "spec-auditor":
|
|
23191
|
+
return hasPhase(context, "Plan") || hasPhase(context, "Execute");
|
|
23192
|
+
case "dep-auditor":
|
|
23193
|
+
return touchesDependency(context.changedFiles);
|
|
23194
|
+
case "root-causer":
|
|
23195
|
+
return hasPhase(context, "Audit");
|
|
23196
|
+
case "qa-verifier":
|
|
23197
|
+
return context.runtime === "claude" && hasPhase(context, "Execute") && workspacePolicyOf(row.workspacePolicy) === "isolated-worktree" && touchesExecutableWork(context.changedFiles);
|
|
23198
|
+
case "edge-case-hunter":
|
|
23199
|
+
return context.runtime === "claude" && hasPhase(context, "Execute") && workspacePolicyOf(row.workspacePolicy) === "isolated-worktree";
|
|
23200
|
+
default:
|
|
23201
|
+
return true;
|
|
23202
|
+
}
|
|
23203
|
+
}
|
|
23204
|
+
function selectAgentRows(rows, context) {
|
|
23205
|
+
return rows.filter((row) => {
|
|
23206
|
+
if (!row.enabled) return false;
|
|
23207
|
+
const runtime = runtimeOf(row.runtime);
|
|
23208
|
+
if (runtime !== null && runtime !== context.runtime) return false;
|
|
23209
|
+
if (workspacePolicyOf(row.workspacePolicy) === "isolated-worktree" && context.runtime !== "claude") return false;
|
|
23210
|
+
if (activationOf(row.activation) === "always") return true;
|
|
23211
|
+
const builtin = row.projectId === null && BUILTIN_AGENT_NAMES.includes(row.name);
|
|
23212
|
+
return builtin ? smartBuiltinSelected(row, context) : true;
|
|
23213
|
+
});
|
|
23214
|
+
}
|
|
23215
|
+
function currentCustomAgentRuntimeSupport() {
|
|
23216
|
+
return { ...codexNativeSupport };
|
|
23217
|
+
}
|
|
23218
|
+
function toDef(r) {
|
|
23219
|
+
return {
|
|
23220
|
+
id: r.id,
|
|
23221
|
+
name: r.name,
|
|
23222
|
+
description: r.description,
|
|
23223
|
+
instructions: r.instructions,
|
|
23224
|
+
tools: toolsOf(r.tools),
|
|
23225
|
+
model: r.model,
|
|
23226
|
+
mentions: mentionsOf(r.mentions),
|
|
23227
|
+
activation: activationOf(r.activation),
|
|
23228
|
+
effort: effortOf(r.effort),
|
|
23229
|
+
workspacePolicy: workspacePolicyOf(r.workspacePolicy),
|
|
23230
|
+
maxTurns: maxTurnsOf(r.maxTurns),
|
|
23231
|
+
timeoutSeconds: timeoutSecondsOf(r.timeoutSeconds)
|
|
23232
|
+
};
|
|
23233
|
+
}
|
|
23234
|
+
function recommendedModel(row, runtime) {
|
|
23235
|
+
if (row.model) return row.model;
|
|
23236
|
+
if (row.projectId !== null) return null;
|
|
23237
|
+
const builtin = BUILTIN_AGENTS.find((agent) => agent.name === row.name);
|
|
23238
|
+
if (!builtin) return null;
|
|
23239
|
+
const model = builtin.models[runtime];
|
|
23240
|
+
if (runtime === "claude" && (model === "haiku" || model === "sonnet")) return model;
|
|
23241
|
+
return modelsForRuntime(runtime).some((entry) => entry.id === model) ? model : null;
|
|
23242
|
+
}
|
|
23243
|
+
function toRuntimeDef(row, runtime) {
|
|
23244
|
+
return { ...toDef(row), model: recommendedModel(row, runtime) };
|
|
23245
|
+
}
|
|
23246
|
+
async function loadCustomAgents() {
|
|
23247
|
+
try {
|
|
23248
|
+
cache4 = await prisma.customAgent.findMany();
|
|
23249
|
+
const projects = await prisma.project.findMany({ select: { id: true, repoDir: true } });
|
|
23250
|
+
const bindings = await prisma.localBinding.findMany({ select: { projectId: true, repoDir: true } });
|
|
23251
|
+
const next = /* @__PURE__ */ new Map();
|
|
23252
|
+
for (const p3 of projects) next.set(p3.id, p3.repoDir ?? null);
|
|
23253
|
+
for (const b of bindings) next.set(b.projectId, b.repoDir ?? null);
|
|
23254
|
+
repoDirCache = next;
|
|
23255
|
+
} catch {
|
|
23256
|
+
cache4 = [];
|
|
23257
|
+
repoDirCache = /* @__PURE__ */ new Map();
|
|
23258
|
+
}
|
|
23259
|
+
}
|
|
23260
|
+
function agentDefsFor(contextOrProjectId, legacyAgent) {
|
|
23261
|
+
const legacy = typeof contextOrProjectId === "string";
|
|
23262
|
+
const context = legacy ? {
|
|
23263
|
+
projectId: contextOrProjectId,
|
|
23264
|
+
runtime: legacyAgent ?? "claude",
|
|
23265
|
+
cwd: "",
|
|
23266
|
+
changedFiles: []
|
|
23267
|
+
} : contextOrProjectId;
|
|
23268
|
+
const { projectId } = context;
|
|
23269
|
+
const globals = cache4.filter((r) => r.projectId === null).map(asCustomAgent);
|
|
23270
|
+
const project = cache4.filter((r) => r.projectId === projectId).map(asCustomAgent);
|
|
23271
|
+
const effectiveIds = new Set(effectiveAgents(globals, project).map((agent) => agent.id));
|
|
23272
|
+
const effectiveRows = cache4.filter((row) => effectiveIds.has(row.id));
|
|
23273
|
+
const eff = legacy ? effectiveRows.filter((row) => row.enabled && (runtimeOf(row.runtime) === null || runtimeOf(row.runtime) === context.runtime) && !(workspacePolicyOf(row.workspacePolicy) === "isolated-worktree" && context.runtime !== "claude")) : selectAgentRows(effectiveRows, context);
|
|
23274
|
+
const needsCatalog = eff.some((row) => (toolsOf(row.tools) ?? []).includes(ALL_TOOLS));
|
|
23275
|
+
const catalogIds = needsCatalog ? agentToolIds(repoDirCache.get(projectId) ?? null) : [];
|
|
23276
|
+
return eff.map((row) => {
|
|
23277
|
+
const a = asCustomAgent(row);
|
|
23278
|
+
return {
|
|
23279
|
+
...toRuntimeDef(row, context.runtime),
|
|
23280
|
+
// Ekspansi terjadi DI SINI, sebelum `resolveTools` di runner: meneruskan `"*"` apa adanya
|
|
23281
|
+
// membuat claude membuangnya senyap (agen tanpa alat), sementara menerjemahkannya jadi `null`
|
|
23282
|
+
// membuat agen mewarisi SELURUH tool termasuk `Task` — lapis 2 anti-loop lenyap tanpa jejak.
|
|
23283
|
+
tools: expandTools(a.tools, catalogIds),
|
|
23284
|
+
mentions: a.mentions ?? []
|
|
23285
|
+
};
|
|
23286
|
+
});
|
|
23287
|
+
}
|
|
23288
|
+
function validateGraph(rows) {
|
|
23289
|
+
const projectScopes = [...new Set(rows.map((r) => r.projectId).filter((p3) => p3 !== null))];
|
|
23290
|
+
const globals = rows.filter((r) => r.projectId === null).map(asCustomAgent);
|
|
23291
|
+
for (const scope of [null, ...projectScopes]) {
|
|
23292
|
+
const project = scope === null ? [] : rows.filter((r) => r.projectId === scope).map(asCustomAgent);
|
|
23293
|
+
const nodes = effectiveAgents(globals, project).map((a) => ({ name: a.name, mentions: a.mentions ?? [] }));
|
|
23294
|
+
const cycle = detectCycle(nodes);
|
|
23295
|
+
if (cycle) return { scope: scope ?? GLOBAL_SCOPE, cycle };
|
|
23296
|
+
}
|
|
23297
|
+
return null;
|
|
23298
|
+
}
|
|
23299
|
+
function unknownMentions(row, all) {
|
|
23300
|
+
const visible = new Set(
|
|
23301
|
+
all.filter((r) => r.projectId === null || row.projectId !== null && r.projectId === row.projectId).map((r) => r.name)
|
|
23302
|
+
);
|
|
23303
|
+
return mentionsOf(row.mentions).filter((m) => !visible.has(m));
|
|
23304
|
+
}
|
|
23305
|
+
async function refreshCustomAgentRuntimeSupport(probe = defaultCodexSupportProbe) {
|
|
23306
|
+
const refresh = codexSupportRefreshTail.then(async () => {
|
|
23307
|
+
const version = await probe();
|
|
23308
|
+
codexNativeSupport = { version, ok: codexNativeAgentsSupported(version) };
|
|
23309
|
+
});
|
|
23310
|
+
codexSupportRefreshTail = refresh.catch(() => {
|
|
23311
|
+
});
|
|
23312
|
+
return refresh;
|
|
23313
|
+
}
|
|
23314
|
+
async function installCustomAgents() {
|
|
23315
|
+
await seedBuiltinAgents();
|
|
23316
|
+
await loadCustomAgents();
|
|
23317
|
+
await refreshCustomAgentRuntimeSupport();
|
|
23318
|
+
registerCodexNativeAgentSupport(() => codexNativeSupport);
|
|
23319
|
+
registerCustomAgentSource((context) => agentDefsFor(context));
|
|
23320
|
+
if (!codexSupportRefreshTimer) {
|
|
23321
|
+
codexSupportRefreshTimer = setInterval(() => {
|
|
23322
|
+
void refreshCustomAgentRuntimeSupport().catch((error) => console.error("custom agent: probe Codex gagal:", error));
|
|
23323
|
+
}, 5 * 6e4);
|
|
23324
|
+
codexSupportRefreshTimer.unref();
|
|
23325
|
+
}
|
|
23326
|
+
}
|
|
23327
|
+
var phasesOf, hasPhase, touchesDependency, touchesExternalInput, touchesExecutableWork, cache4, codexNativeSupport, codexSupportRefreshTimer, codexSupportRefreshTail, repoDirCache, asCustomAgent, defaultCodexSupportProbe;
|
|
23328
|
+
var init_custom_agents2 = __esm({
|
|
23329
|
+
"src/services/custom-agents.ts"() {
|
|
23330
|
+
"use strict";
|
|
23331
|
+
init_db();
|
|
23332
|
+
init_src();
|
|
23333
|
+
init_src2();
|
|
23334
|
+
init_pty();
|
|
23335
|
+
init_agent_tool_catalog();
|
|
23336
|
+
init_builtin_agents2();
|
|
23337
|
+
init_codex_version();
|
|
23338
|
+
phasesOf = (flow) => flow ? PIPELINES[flow] : [];
|
|
23339
|
+
hasPhase = (context, name2) => phasesOf(context.flow).includes(name2);
|
|
23340
|
+
touchesDependency = (files) => files.some((path) => /(^|\/)(?:package\.json|pnpm-lock\.yaml|package-lock\.json|yarn\.lock|bun\.lockb?|Cargo\.(?:toml|lock)|go\.(?:mod|sum)|requirements[^/]*\.txt|pyproject\.toml)$/i.test(path));
|
|
23341
|
+
touchesExternalInput = (context) => {
|
|
23342
|
+
const surface = [context.prompt ?? "", ...context.changedFiles].join("\n");
|
|
23343
|
+
return /(?:^|[\W_/.-])(route|routes|handler|auth|oauth|api|cli|config|filesystem|upload|webhook|input)(?:$|[\W_/.-])/i.test(surface);
|
|
23344
|
+
};
|
|
23345
|
+
touchesExecutableWork = (files) => files.some((path) => !/(^|\/)(?:docs?|internal\/docs)\//i.test(path) && /(?:\.(?:[cm]?[jt]sx?|py|go|rs|java|rb|php)|(?:^|\/)test(?:s)?\/)/i.test(path));
|
|
23346
|
+
cache4 = [];
|
|
23347
|
+
codexNativeSupport = { version: null, ok: false };
|
|
23348
|
+
codexSupportRefreshTimer = null;
|
|
23349
|
+
codexSupportRefreshTail = Promise.resolve();
|
|
23350
|
+
repoDirCache = /* @__PURE__ */ new Map();
|
|
23351
|
+
asCustomAgent = (r) => ({
|
|
23352
|
+
id: r.id,
|
|
23353
|
+
projectId: r.projectId,
|
|
23354
|
+
name: r.name,
|
|
23355
|
+
description: r.description,
|
|
23356
|
+
instructions: r.instructions,
|
|
23357
|
+
tools: toolsOf(r.tools),
|
|
23358
|
+
model: r.model,
|
|
23359
|
+
mentions: mentionsOf(r.mentions),
|
|
23360
|
+
runtime: runtimeOf(r.runtime),
|
|
23361
|
+
activation: activationOf(r.activation),
|
|
23362
|
+
effort: effortOf(r.effort),
|
|
23363
|
+
workspacePolicy: workspacePolicyOf(r.workspacePolicy),
|
|
23364
|
+
maxTurns: maxTurnsOf(r.maxTurns),
|
|
23365
|
+
timeoutSeconds: timeoutSecondsOf(r.timeoutSeconds),
|
|
23366
|
+
enabled: r.enabled,
|
|
23367
|
+
createdAt: "",
|
|
23368
|
+
updatedAt: ""
|
|
23369
|
+
// tak dipakai lapis ini
|
|
23370
|
+
});
|
|
23371
|
+
defaultCodexSupportProbe = async () => {
|
|
23372
|
+
_resetCodexVersionCache();
|
|
23373
|
+
return getCodexVersion();
|
|
23374
|
+
};
|
|
23375
|
+
}
|
|
23376
|
+
});
|
|
23377
|
+
|
|
22346
23378
|
// src/services/config-apply.ts
|
|
22347
23379
|
var config_apply_exports = {};
|
|
22348
23380
|
__export(config_apply_exports, {
|
|
@@ -22370,6 +23402,10 @@ async function applyConfigSideEffect(key) {
|
|
|
22370
23402
|
return;
|
|
22371
23403
|
}
|
|
22372
23404
|
if (configEntry(key)?.inheritEnv) mirrorInheritEnv(key);
|
|
23405
|
+
if (key === "HANOMAN_CODEX_BIN") {
|
|
23406
|
+
const { refreshCustomAgentRuntimeSupport: refreshCustomAgentRuntimeSupport2 } = await Promise.resolve().then(() => (init_custom_agents2(), custom_agents_exports));
|
|
23407
|
+
await refreshCustomAgentRuntimeSupport2();
|
|
23408
|
+
}
|
|
22373
23409
|
}
|
|
22374
23410
|
async function rotateSyncOrigin(input) {
|
|
22375
23411
|
const url2 = new URL(input);
|
|
@@ -24854,14 +25890,14 @@ var require_stream_consumer = __commonJS({
|
|
|
24854
25890
|
"../node_modules/.pnpm/@fastify+multipart@10.1.0/node_modules/@fastify/multipart/lib/stream-consumer.js"(exports, module) {
|
|
24855
25891
|
"use strict";
|
|
24856
25892
|
module.exports = function streamToNull(stream) {
|
|
24857
|
-
return new Promise((
|
|
25893
|
+
return new Promise((resolve22, reject2) => {
|
|
24858
25894
|
stream.on("data", () => {
|
|
24859
25895
|
});
|
|
24860
25896
|
stream.on("close", () => {
|
|
24861
|
-
|
|
25897
|
+
resolve22();
|
|
24862
25898
|
});
|
|
24863
25899
|
stream.on("end", () => {
|
|
24864
|
-
|
|
25900
|
+
resolve22();
|
|
24865
25901
|
});
|
|
24866
25902
|
stream.on("error", (error) => {
|
|
24867
25903
|
reject2(error);
|
|
@@ -25310,10 +26346,10 @@ var require_multipart2 = __commonJS({
|
|
|
25310
26346
|
}
|
|
25311
26347
|
};
|
|
25312
26348
|
const parts = () => {
|
|
25313
|
-
return new Promise((
|
|
26349
|
+
return new Promise((resolve22, reject2) => {
|
|
25314
26350
|
handle((val) => {
|
|
25315
26351
|
if (val instanceof Error) return reject2(val);
|
|
25316
|
-
|
|
26352
|
+
resolve22(val);
|
|
25317
26353
|
});
|
|
25318
26354
|
});
|
|
25319
26355
|
};
|
|
@@ -25494,7 +26530,7 @@ var require_multipart2 = __commonJS({
|
|
|
25494
26530
|
parts = this.parts(options3);
|
|
25495
26531
|
}
|
|
25496
26532
|
this.savedRequestFiles = [];
|
|
25497
|
-
const
|
|
26533
|
+
const tmpdir7 = options3?.tmpdir || os.tmpdir();
|
|
25498
26534
|
this.tmpUploads = [];
|
|
25499
26535
|
let i = 0;
|
|
25500
26536
|
for await (const part of parts) {
|
|
@@ -25502,7 +26538,7 @@ var require_multipart2 = __commonJS({
|
|
|
25502
26538
|
if (!part.file) {
|
|
25503
26539
|
continue;
|
|
25504
26540
|
}
|
|
25505
|
-
const filepath = path.join(
|
|
26541
|
+
const filepath = path.join(tmpdir7, generateId() + path.extname(part.filename || "file" + i++));
|
|
25506
26542
|
const target2 = createWriteStream2(filepath);
|
|
25507
26543
|
try {
|
|
25508
26544
|
this.tmpUploads.push(filepath);
|
|
@@ -25679,15 +26715,15 @@ init_stage_machine();
|
|
|
25679
26715
|
init_src();
|
|
25680
26716
|
import { execFile as execFile2 } from "node:child_process";
|
|
25681
26717
|
import { promisify } from "node:util";
|
|
25682
|
-
import { existsSync as existsSync6, rmSync as
|
|
26718
|
+
import { existsSync as existsSync6, rmSync as rmSync3 } from "node:fs";
|
|
25683
26719
|
import { resolve as resolve10 } from "node:path";
|
|
25684
26720
|
|
|
25685
26721
|
// src/services/safe-repo-path.ts
|
|
25686
26722
|
import { constants as constants2 } from "node:fs";
|
|
25687
26723
|
import { lstat, mkdir as mkdir2, open as open2, realpath, rename, unlink as unlink2 } from "node:fs/promises";
|
|
25688
|
-
import { isAbsolute as isAbsolute4, join as
|
|
26724
|
+
import { isAbsolute as isAbsolute4, join as join10, relative, resolve as resolve9, sep as sep2 } from "node:path";
|
|
25689
26725
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
25690
|
-
import { closeSync, fstatSync, lstatSync, mkdirSync as mkdirSync6, openSync, readFileSync as readFileSync7, realpathSync as realpathSync2, renameSync as renameSync2, unlinkSync, writeFileSync as
|
|
26726
|
+
import { closeSync, fstatSync, lstatSync, mkdirSync as mkdirSync6, openSync, readFileSync as readFileSync7, realpathSync as realpathSync2, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync6 } from "node:fs";
|
|
25691
26727
|
var PathContainmentError = class extends Error {
|
|
25692
26728
|
code = "PATH_CONTAINMENT";
|
|
25693
26729
|
};
|
|
@@ -25715,7 +26751,7 @@ async function ensureRepoParents(root, rel) {
|
|
|
25715
26751
|
const parts = components(rel).slice(0, -1);
|
|
25716
26752
|
let current = base2;
|
|
25717
26753
|
for (const part of parts) {
|
|
25718
|
-
current =
|
|
26754
|
+
current = join10(current, part);
|
|
25719
26755
|
try {
|
|
25720
26756
|
await mkdir2(current, { mode: 448 });
|
|
25721
26757
|
} catch (error) {
|
|
@@ -25732,7 +26768,7 @@ function ensureRepoParentsSync(root, rel) {
|
|
|
25732
26768
|
const parts = components(rel).slice(0, -1);
|
|
25733
26769
|
let current = base2;
|
|
25734
26770
|
for (const part of parts) {
|
|
25735
|
-
current =
|
|
26771
|
+
current = join10(current, part);
|
|
25736
26772
|
try {
|
|
25737
26773
|
mkdirSync6(current, { mode: 448 });
|
|
25738
26774
|
} catch (error) {
|
|
@@ -25748,7 +26784,7 @@ async function resolveRepoEntry(root, rel, opts = {}) {
|
|
|
25748
26784
|
const parts = components(rel);
|
|
25749
26785
|
let current = base2;
|
|
25750
26786
|
for (let i = 0; i < parts.length; i++) {
|
|
25751
|
-
current =
|
|
26787
|
+
current = join10(current, parts[i]);
|
|
25752
26788
|
const stat4 = await lstat(current).catch((error) => {
|
|
25753
26789
|
if (error.code === "ENOENT" && opts.allowMissingTail) return null;
|
|
25754
26790
|
if (error.code === "ENOENT" && opts.allowMissingFinal && i === parts.length - 1) return null;
|
|
@@ -25769,7 +26805,7 @@ function assertSafeRepoPathSync(root, rel, allowMissingFinal = false, allowMissi
|
|
|
25769
26805
|
const parts = components(rel);
|
|
25770
26806
|
let current = base2;
|
|
25771
26807
|
for (let i = 0; i < parts.length; i++) {
|
|
25772
|
-
current =
|
|
26808
|
+
current = join10(current, parts[i]);
|
|
25773
26809
|
try {
|
|
25774
26810
|
const stat4 = lstatSync(current);
|
|
25775
26811
|
if (stat4.isSymbolicLink()) denied("symlink");
|
|
@@ -25799,10 +26835,10 @@ function writeRepoFileAtomicSync(root, rel, data) {
|
|
|
25799
26835
|
const parent = resolve9(path, "..");
|
|
25800
26836
|
const parentRel = relative(realpathSync2(root), parent);
|
|
25801
26837
|
if (parentRel) assertSafeRepoPathSync(root, parentRel);
|
|
25802
|
-
const temp =
|
|
26838
|
+
const temp = join10(parent, `.hanoman-${randomUUID3()}.tmp`);
|
|
25803
26839
|
const fd = openSync(temp, constants2.O_WRONLY | constants2.O_CREAT | constants2.O_EXCL | (constants2.O_NOFOLLOW ?? 0), 384);
|
|
25804
26840
|
try {
|
|
25805
|
-
|
|
26841
|
+
writeFileSync6(fd, data);
|
|
25806
26842
|
} finally {
|
|
25807
26843
|
closeSync(fd);
|
|
25808
26844
|
}
|
|
@@ -25846,7 +26882,7 @@ async function writeRepoFileAtomic(root, rel, data) {
|
|
|
25846
26882
|
await mkdir2(entry.parent, { recursive: false, mode: 448 }).catch((error) => {
|
|
25847
26883
|
if (error.code !== "EEXIST") throw error;
|
|
25848
26884
|
});
|
|
25849
|
-
const temp =
|
|
26885
|
+
const temp = join10(entry.parent, `.hanoman-${randomUUID3()}.tmp`);
|
|
25850
26886
|
const handle = await open2(temp, constants2.O_WRONLY | constants2.O_CREAT | constants2.O_EXCL | (constants2.O_NOFOLLOW ?? 0), 384);
|
|
25851
26887
|
try {
|
|
25852
26888
|
await handle.writeFile(data);
|
|
@@ -25945,7 +26981,7 @@ function writeDocFile(repoDir, rel, content) {
|
|
|
25945
26981
|
function deleteDocFile(repoDir, rel) {
|
|
25946
26982
|
const abs = docAbsPath(repoDir, rel);
|
|
25947
26983
|
if (!existsSync6(abs)) return false;
|
|
25948
|
-
|
|
26984
|
+
rmSync3(abs);
|
|
25949
26985
|
return true;
|
|
25950
26986
|
}
|
|
25951
26987
|
|
|
@@ -26003,9 +27039,9 @@ async function toProjectView(p3, sessions, devices2) {
|
|
|
26003
27039
|
const specs = await prisma.spec.findMany({ where: { projectId: p3.id } });
|
|
26004
27040
|
const { coverage } = await scanRepoDocs(await resolveRepoDir(p3.id));
|
|
26005
27041
|
const binding = await getBinding(p3.id);
|
|
26006
|
-
const
|
|
27042
|
+
const open5 = specs.filter((s2) => s2.stage !== "done");
|
|
26007
27043
|
const { session, commit } = sessionOf(p3.id, sessions);
|
|
26008
|
-
const topStage =
|
|
27044
|
+
const topStage = open5.length ? open5.map((s2) => s2.stage).sort((a, b) => STAGES.indexOf(b) - STAGES.indexOf(a))[0] : "spec";
|
|
26009
27045
|
return {
|
|
26010
27046
|
id: p3.id,
|
|
26011
27047
|
name: p3.name,
|
|
@@ -26018,7 +27054,7 @@ async function toProjectView(p3, sessions, devices2) {
|
|
|
26018
27054
|
docStatus: docStatusFor(coverage),
|
|
26019
27055
|
coverage,
|
|
26020
27056
|
createdAt: p3.createdAt.toISOString(),
|
|
26021
|
-
backlog:
|
|
27057
|
+
backlog: open5.length,
|
|
26022
27058
|
topStage,
|
|
26023
27059
|
session,
|
|
26024
27060
|
activity: session.status === "running" ? `running \xB7 ${session.flow ?? "sesi"}` : "idle",
|
|
@@ -26037,31 +27073,13 @@ async function toProjectView(p3, sessions, devices2) {
|
|
|
26037
27073
|
};
|
|
26038
27074
|
}
|
|
26039
27075
|
|
|
26040
|
-
// src/
|
|
26041
|
-
|
|
26042
|
-
init_outbox();
|
|
26043
|
-
init_sync();
|
|
26044
|
-
async function notifySynced(entity, id) {
|
|
26045
|
-
try {
|
|
26046
|
-
if (!isEntity(entity)) return;
|
|
26047
|
-
await consumeTombstoneOnRecreate(entity, id);
|
|
26048
|
-
if (effectiveStr("SYNC_SERVER_URL")) await enqueueOutbox(entity, id);
|
|
26049
|
-
else await publishLocal(entity, id);
|
|
26050
|
-
} catch {
|
|
26051
|
-
}
|
|
26052
|
-
}
|
|
26053
|
-
async function notifyDeleted(entity, id) {
|
|
26054
|
-
try {
|
|
26055
|
-
if (!isEntity(entity)) return;
|
|
26056
|
-
if (effectiveStr("SYNC_SERVER_URL")) await enqueueOutbox(entity, id);
|
|
26057
|
-
else await publishDelete(entity, id);
|
|
26058
|
-
} catch {
|
|
26059
|
-
}
|
|
26060
|
-
}
|
|
27076
|
+
// src/routes/projects.ts
|
|
27077
|
+
init_sync_notify();
|
|
26061
27078
|
|
|
26062
27079
|
// src/services/sync-delete.ts
|
|
26063
27080
|
init_sync();
|
|
26064
27081
|
init_tombstone();
|
|
27082
|
+
init_sync_notify();
|
|
26065
27083
|
init_outbox();
|
|
26066
27084
|
async function deleteSynced(entity, id, deviceId) {
|
|
26067
27085
|
const snap = await snapshot(entity, id);
|
|
@@ -26305,8 +27323,8 @@ import { existsSync as existsSync9 } from "node:fs";
|
|
|
26305
27323
|
// src/services/integrate.ts
|
|
26306
27324
|
import { execFile as execFile4 } from "node:child_process";
|
|
26307
27325
|
import { promisify as promisify3 } from "node:util";
|
|
26308
|
-
import { rmSync as
|
|
26309
|
-
import { join as
|
|
27326
|
+
import { rmSync as rmSync4 } from "node:fs";
|
|
27327
|
+
import { join as join11 } from "node:path";
|
|
26310
27328
|
var sanitize = (id) => id.toLowerCase().replace(/[^a-z0-9_-]/g, "_");
|
|
26311
27329
|
var sourceBranch = (specId) => `hanoman/${sanitize(specId)}`;
|
|
26312
27330
|
var exec3 = promisify3(execFile4);
|
|
@@ -26342,7 +27360,7 @@ async function resolveTarget(repoDir, target2) {
|
|
|
26342
27360
|
async function reclaim(repoDir, wt) {
|
|
26343
27361
|
await sh(repoDir, ["worktree", "remove", "--force", wt]);
|
|
26344
27362
|
await sh(repoDir, ["worktree", "prune"]);
|
|
26345
|
-
|
|
27363
|
+
rmSync4(wt, { recursive: true, force: true });
|
|
26346
27364
|
}
|
|
26347
27365
|
async function integrateBranch(repoDir, src, op, target2) {
|
|
26348
27366
|
const source = await resolveSource(repoDir, src.branch);
|
|
@@ -26350,7 +27368,7 @@ async function integrateBranch(repoDir, src, op, target2) {
|
|
|
26350
27368
|
const tgt = await resolveTarget(repoDir, target2);
|
|
26351
27369
|
if (!tgt) return { status: "error", code: 400, error: `target "${target2}" tidak dikenal` };
|
|
26352
27370
|
await sh(repoDir, ["fetch", "origin"]);
|
|
26353
|
-
const wt =
|
|
27371
|
+
const wt = join11(repoDir, ".worktrees", `merge-${sanitize(src.mergeId)}`);
|
|
26354
27372
|
await reclaim(repoDir, wt);
|
|
26355
27373
|
const baseRef = op === "merge" ? tgt.ref : source;
|
|
26356
27374
|
const baseSha = await out(repoDir, ["rev-parse", "--verify", "--end-of-options", `${baseRef}^{commit}`]);
|
|
@@ -26411,7 +27429,7 @@ async function mergeIntoCurrent(repoDir, source, opts = {}) {
|
|
|
26411
27429
|
const src = await resolveGraphSource(repoDir, source);
|
|
26412
27430
|
if (!src) return { status: "error", code: 400, error: `source "${source}" tak dikenal` };
|
|
26413
27431
|
await sh(repoDir, ["fetch", "origin"]);
|
|
26414
|
-
const wt =
|
|
27432
|
+
const wt = join11(repoDir, ".worktrees", `merge-${sanitize(current)}`);
|
|
26415
27433
|
await reclaim(repoDir, wt);
|
|
26416
27434
|
const baseSha = await out(repoDir, ["rev-parse", "--verify", "--end-of-options", `refs/heads/${current}^{commit}`]);
|
|
26417
27435
|
if (!await ok(repoDir, ["worktree", "add", "--detach", "-q", wt, baseSha]))
|
|
@@ -26449,7 +27467,7 @@ async function replayCurrent(repoDir, source, cmd) {
|
|
|
26449
27467
|
const current = await out(repoDir, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
26450
27468
|
if (!current || current === "HEAD")
|
|
26451
27469
|
return { status: "error", code: 409, error: "HEAD detached \u2014 checkout sebuah branch dulu" };
|
|
26452
|
-
const wt =
|
|
27470
|
+
const wt = join11(repoDir, ".worktrees", `merge-${sanitize(current)}`);
|
|
26453
27471
|
await reclaim(repoDir, wt);
|
|
26454
27472
|
const baseSha = await out(repoDir, ["rev-parse", "--verify", "--end-of-options", `refs/heads/${current}^{commit}`]);
|
|
26455
27473
|
if (!await ok(repoDir, ["worktree", "add", "--detach", "-q", wt, baseSha]))
|
|
@@ -26497,16 +27515,16 @@ init_db();
|
|
|
26497
27515
|
import { execFile as execFile5 } from "node:child_process";
|
|
26498
27516
|
import { promisify as promisify4 } from "node:util";
|
|
26499
27517
|
import { mkdtemp, copyFile, rm as rm2 } from "node:fs/promises";
|
|
26500
|
-
import { tmpdir as
|
|
26501
|
-
import { join as
|
|
27518
|
+
import { tmpdir as tmpdir3 } from "node:os";
|
|
27519
|
+
import { join as join12, resolve as resolve11 } from "node:path";
|
|
26502
27520
|
var exec4 = promisify4(execFile5);
|
|
26503
27521
|
var GIT3 = { maxBuffer: 1 << 24 };
|
|
26504
27522
|
var MAX = 256 * 1024;
|
|
26505
|
-
var worktreeDir = (repoDir, specId) =>
|
|
27523
|
+
var worktreeDir = (repoDir, specId) => join12(repoDir, ".worktrees", specId.toLowerCase().replace(/[^a-z0-9_-]/g, "_"));
|
|
26506
27524
|
async function withTempIndex(wt, fn) {
|
|
26507
27525
|
const idx = (await exec4("git", ["rev-parse", "--git-path", "index"], { cwd: wt, ...GIT3 })).stdout.trim();
|
|
26508
|
-
const dir2 = await mkdtemp(
|
|
26509
|
-
const tmp =
|
|
27526
|
+
const dir2 = await mkdtemp(join12(tmpdir3(), "hanoman-idx-"));
|
|
27527
|
+
const tmp = join12(dir2, "index");
|
|
26510
27528
|
await copyFile(resolve11(wt, idx), tmp);
|
|
26511
27529
|
const env = { ...process.env, GIT_INDEX_FILE: tmp };
|
|
26512
27530
|
try {
|
|
@@ -26705,7 +27723,7 @@ function appendSourceHistory(current, entry) {
|
|
|
26705
27723
|
// src/services/spec-reset.ts
|
|
26706
27724
|
import { execFile as execFile8 } from "node:child_process";
|
|
26707
27725
|
import { existsSync as existsSync8 } from "node:fs";
|
|
26708
|
-
import { join as
|
|
27726
|
+
import { join as join14 } from "node:path";
|
|
26709
27727
|
import { promisify as promisify7 } from "node:util";
|
|
26710
27728
|
|
|
26711
27729
|
// src/services/stage-artifacts.ts
|
|
@@ -26754,7 +27772,7 @@ init_db();
|
|
|
26754
27772
|
import { execFile as execFile6 } from "node:child_process";
|
|
26755
27773
|
import { readdir, rm as rm3 } from "node:fs/promises";
|
|
26756
27774
|
import { promisify as promisify5 } from "node:util";
|
|
26757
|
-
import { join as
|
|
27775
|
+
import { join as join13, resolve as resolve12 } from "node:path";
|
|
26758
27776
|
init_notifications2();
|
|
26759
27777
|
var TICK_MS = 6e4;
|
|
26760
27778
|
var trashDirOf = (repoDir) => resolve12(repoDir, ".worktrees", ".trash");
|
|
@@ -26814,7 +27832,7 @@ async function sweepRepo(repoDir, projectId, deps = prodReaperDeps) {
|
|
|
26814
27832
|
}
|
|
26815
27833
|
let removed = 0;
|
|
26816
27834
|
for (const entry of entries3) {
|
|
26817
|
-
const path =
|
|
27835
|
+
const path = join13(dir2, entry);
|
|
26818
27836
|
const known = pending.get(path);
|
|
26819
27837
|
const row = known ?? { path, repoDir, projectId, entry, sessionId: sessionIdOf(entry), since: Date.now() };
|
|
26820
27838
|
pending.set(path, row);
|
|
@@ -27370,7 +28388,7 @@ async function planSpecReset(spec) {
|
|
|
27370
28388
|
const repoDir = await resolveRepoDir(spec.projectId);
|
|
27371
28389
|
if (!repoDir) return EMPTY;
|
|
27372
28390
|
const sid = sessionIdForSpec(spec.id);
|
|
27373
|
-
const wt =
|
|
28391
|
+
const wt = join14(repoDir, ".worktrees", sid);
|
|
27374
28392
|
const branch = `hanoman/${sid}`;
|
|
27375
28393
|
const hasBranch = await shaResolvable(repoDir, `refs/heads/${branch}`);
|
|
27376
28394
|
return {
|
|
@@ -27401,6 +28419,7 @@ async function applySpecReset(spec, plan) {
|
|
|
27401
28419
|
|
|
27402
28420
|
// src/routes/specs.ts
|
|
27403
28421
|
init_notifications2();
|
|
28422
|
+
init_sync_notify();
|
|
27404
28423
|
|
|
27405
28424
|
// src/services/spec-complete.ts
|
|
27406
28425
|
init_db();
|
|
@@ -27408,6 +28427,7 @@ init_notifications2();
|
|
|
27408
28427
|
|
|
27409
28428
|
// src/services/session-result.ts
|
|
27410
28429
|
init_db();
|
|
28430
|
+
init_sync_notify();
|
|
27411
28431
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
27412
28432
|
var WHITELIST = [
|
|
27413
28433
|
"projectId",
|
|
@@ -27433,6 +28453,7 @@ async function recordSessionResult(input) {
|
|
|
27433
28453
|
}
|
|
27434
28454
|
|
|
27435
28455
|
// src/services/spec-complete.ts
|
|
28456
|
+
init_sync_notify();
|
|
27436
28457
|
async function completeSpecManually(spec, input) {
|
|
27437
28458
|
const at = input.at ?? /* @__PURE__ */ new Date();
|
|
27438
28459
|
const manualDone = {
|
|
@@ -27468,7 +28489,7 @@ import { extname } from "node:path";
|
|
|
27468
28489
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
27469
28490
|
import { spawn as spawn2 } from "node:child_process";
|
|
27470
28491
|
import { mkdir as mkdir3, rename as rename2, unlink as unlink3, writeFile as writeFile2 } from "node:fs/promises";
|
|
27471
|
-
import { basename as basename3, isAbsolute as isAbsolute5, join as
|
|
28492
|
+
import { basename as basename3, isAbsolute as isAbsolute5, join as join15 } from "node:path";
|
|
27472
28493
|
|
|
27473
28494
|
// ../node_modules/.pnpm/strtok3@10.3.5/node_modules/strtok3/lib/stream/Errors.js
|
|
27474
28495
|
var defaultMessages = "End-Of-Stream";
|
|
@@ -29581,7 +30602,7 @@ function readByobReaderWithSignal(reader, buffer, signal) {
|
|
|
29581
30602
|
return reader.read(buffer);
|
|
29582
30603
|
}
|
|
29583
30604
|
signal.throwIfAborted();
|
|
29584
|
-
return new Promise((
|
|
30605
|
+
return new Promise((resolve22, reject2) => {
|
|
29585
30606
|
const cleanup = () => {
|
|
29586
30607
|
signal.removeEventListener("abort", onAbort);
|
|
29587
30608
|
};
|
|
@@ -29601,7 +30622,7 @@ function readByobReaderWithSignal(reader, buffer, signal) {
|
|
|
29601
30622
|
try {
|
|
29602
30623
|
const result = await reader.read(buffer);
|
|
29603
30624
|
cleanup();
|
|
29604
|
-
|
|
30625
|
+
resolve22(result);
|
|
29605
30626
|
} catch (error) {
|
|
29606
30627
|
cleanup();
|
|
29607
30628
|
reject2(error);
|
|
@@ -31360,11 +32381,11 @@ var UploadError = class extends Error {
|
|
|
31360
32381
|
}
|
|
31361
32382
|
};
|
|
31362
32383
|
function timeout(promise, ms, code) {
|
|
31363
|
-
return new Promise((
|
|
32384
|
+
return new Promise((resolve22, reject2) => {
|
|
31364
32385
|
const timer9 = setTimeout(() => reject2(new UploadError(code, "upload operation timed out")), ms);
|
|
31365
32386
|
promise.then((value) => {
|
|
31366
32387
|
clearTimeout(timer9);
|
|
31367
|
-
|
|
32388
|
+
resolve22(value);
|
|
31368
32389
|
}, (error) => {
|
|
31369
32390
|
clearTimeout(timer9);
|
|
31370
32391
|
reject2(error);
|
|
@@ -31387,7 +32408,7 @@ function scannerFromEnv(path) {
|
|
|
31387
32408
|
return Promise.resolve();
|
|
31388
32409
|
}
|
|
31389
32410
|
if (!isAbsolute5(command)) return Promise.reject(new UploadError("UPLOAD_SCAN", "scanner path must be absolute"));
|
|
31390
|
-
return new Promise((
|
|
32411
|
+
return new Promise((resolve22, reject2) => {
|
|
31391
32412
|
const child = spawn2(command, [path], { shell: false, stdio: "ignore" });
|
|
31392
32413
|
const timer9 = setTimeout(() => {
|
|
31393
32414
|
child.kill("SIGKILL");
|
|
@@ -31399,27 +32420,27 @@ function scannerFromEnv(path) {
|
|
|
31399
32420
|
});
|
|
31400
32421
|
child.once("exit", (code) => {
|
|
31401
32422
|
clearTimeout(timer9);
|
|
31402
|
-
if (code === 0)
|
|
32423
|
+
if (code === 0) resolve22();
|
|
31403
32424
|
else reject2(new UploadError("UPLOAD_SCAN", `scanner exit ${code}`));
|
|
31404
32425
|
});
|
|
31405
32426
|
});
|
|
31406
32427
|
}
|
|
31407
|
-
function
|
|
32428
|
+
function safeFilename2(input, extension2) {
|
|
31408
32429
|
const stem = basename3(input).replace(/\.[^.]*$/, "").replace(/[^a-zA-Z0-9._ -]/g, "_").slice(0, 180).trim();
|
|
31409
32430
|
return `${stem || "upload"}${extension2}`;
|
|
31410
32431
|
}
|
|
31411
32432
|
async function commitToStorage(buffer, extension2, deps, beforePromote) {
|
|
31412
32433
|
const storageDir = deps.storageDir ?? uploadDir();
|
|
31413
|
-
const quarantineDir =
|
|
32434
|
+
const quarantineDir = join15(storageDir, ".quarantine");
|
|
31414
32435
|
await mkdir3(quarantineDir, { recursive: true, mode: 448 });
|
|
31415
32436
|
await mkdir3(storageDir, { recursive: true, mode: 448 });
|
|
31416
|
-
const quarantine =
|
|
32437
|
+
const quarantine = join15(quarantineDir, `${randomUUID5()}.upload`);
|
|
31417
32438
|
const storageKey = `${randomUUID5()}${extension2}`;
|
|
31418
32439
|
await writeFile2(quarantine, buffer, { mode: 384, flag: "wx" });
|
|
31419
32440
|
try {
|
|
31420
32441
|
await timeout((deps.scanner ?? scannerFromEnv)(quarantine), UPLOAD_LIMITS.scanMs, "UPLOAD_SCAN");
|
|
31421
32442
|
await beforePromote?.();
|
|
31422
|
-
await rename2(quarantine,
|
|
32443
|
+
await rename2(quarantine, join15(storageDir, storageKey));
|
|
31423
32444
|
} catch (error) {
|
|
31424
32445
|
await unlink3(quarantine).catch(() => {
|
|
31425
32446
|
});
|
|
@@ -31466,7 +32487,7 @@ async function processUpload(input, deps = {}) {
|
|
|
31466
32487
|
});
|
|
31467
32488
|
return {
|
|
31468
32489
|
storageKey,
|
|
31469
|
-
filename:
|
|
32490
|
+
filename: safeFilename2(input.clientName, type.extension),
|
|
31470
32491
|
mimeType,
|
|
31471
32492
|
extension: type.extension,
|
|
31472
32493
|
size: normalized.byteLength,
|
|
@@ -31506,7 +32527,7 @@ async function processDocumentUpload(input, deps = {}) {
|
|
|
31506
32527
|
const storageKey = await commitToStorage(input.buffer, extension2, deps);
|
|
31507
32528
|
return {
|
|
31508
32529
|
storageKey,
|
|
31509
|
-
filename:
|
|
32530
|
+
filename: safeFilename2(input.clientName, extension2),
|
|
31510
32531
|
mimeType: input.clientMime,
|
|
31511
32532
|
extension: extension2,
|
|
31512
32533
|
size: input.buffer.byteLength
|
|
@@ -31620,10 +32641,10 @@ async function dropSpecAttachments(specId) {
|
|
|
31620
32641
|
// src/services/spec-attachment-dir.ts
|
|
31621
32642
|
init_db();
|
|
31622
32643
|
import { mkdir as mkdir4, readdir as readdir3, readFile as readFile3, rm as rm4, writeFile as writeFile3 } from "node:fs/promises";
|
|
31623
|
-
import { join as
|
|
32644
|
+
import { join as join16 } from "node:path";
|
|
31624
32645
|
init_session_id();
|
|
31625
32646
|
init_uploads();
|
|
31626
|
-
var specAttachmentsDir = (repoDir, sessionId2) =>
|
|
32647
|
+
var specAttachmentsDir = (repoDir, sessionId2) => join16(repoDir, ".worktrees", ".attachments", sessionId2);
|
|
31627
32648
|
var INDEX = "INDEX.md";
|
|
31628
32649
|
var humanSize = (n2) => n2 >= 1024 * 1024 ? `${(n2 / 1024 / 1024).toFixed(1)} MB` : n2 >= 1024 ? `${Math.round(n2 / 1024)} KB` : `${n2} B`;
|
|
31629
32650
|
function uniqueName(taken, filename) {
|
|
@@ -31673,19 +32694,19 @@ async function syncSpecAttachmentsDir(specId, projectId) {
|
|
|
31673
32694
|
for (const a of rows) {
|
|
31674
32695
|
let bytes;
|
|
31675
32696
|
try {
|
|
31676
|
-
bytes = await readFile3(
|
|
32697
|
+
bytes = await readFile3(join16(uploadDir(), a.storageKey));
|
|
31677
32698
|
} catch {
|
|
31678
32699
|
continue;
|
|
31679
32700
|
}
|
|
31680
32701
|
const filename = uniqueName(taken, a.filename);
|
|
31681
|
-
const path =
|
|
32702
|
+
const path = join16(dir2, filename);
|
|
31682
32703
|
await writeFile3(path, bytes, { mode: 384 });
|
|
31683
32704
|
items.push({ filename, mimeType: a.mimeType, size: a.size, path });
|
|
31684
32705
|
}
|
|
31685
|
-
await writeFile3(
|
|
32706
|
+
await writeFile3(join16(dir2, INDEX), renderIndex(specId, items), { mode: 384 });
|
|
31686
32707
|
const keep = /* @__PURE__ */ new Set([INDEX, ...items.map((a) => a.filename)]);
|
|
31687
32708
|
for (const name2 of await readdir3(dir2)) {
|
|
31688
|
-
if (!keep.has(name2)) await rm4(
|
|
32709
|
+
if (!keep.has(name2)) await rm4(join16(dir2, name2), { recursive: true, force: true }).catch(() => {
|
|
31689
32710
|
});
|
|
31690
32711
|
}
|
|
31691
32712
|
return items;
|
|
@@ -34242,7 +35263,7 @@ function renderDocPdf(text, name2, meta) {
|
|
|
34242
35263
|
});
|
|
34243
35264
|
const chunks = [];
|
|
34244
35265
|
doc.on("data", (c) => chunks.push(c));
|
|
34245
|
-
const done = new Promise((
|
|
35266
|
+
const done = new Promise((resolve22) => doc.on("end", () => resolve22(Buffer.concat(chunks))));
|
|
34246
35267
|
doc.font("Helvetica-Bold").fontSize(8).fillColor(BRASS).text(toWinAnsi(meta.eyebrow.toUpperCase()), { characterSpacing: 0.8 });
|
|
34247
35268
|
doc.moveDown(0.25);
|
|
34248
35269
|
doc.font("Helvetica-Bold").fontSize(17).fillColor(STRONG).text(toWinAnsi(name2.split("/").pop() ?? name2));
|
|
@@ -34317,6 +35338,7 @@ init_pty();
|
|
|
34317
35338
|
init_session_phases();
|
|
34318
35339
|
init_stage_machine();
|
|
34319
35340
|
init_notifications2();
|
|
35341
|
+
init_sync_notify();
|
|
34320
35342
|
|
|
34321
35343
|
// src/services/spec-head.ts
|
|
34322
35344
|
init_db();
|
|
@@ -34337,11 +35359,12 @@ async function recordHeadSha(specId, worktree, read2 = readHead) {
|
|
|
34337
35359
|
}
|
|
34338
35360
|
|
|
34339
35361
|
// src/services/live-specs.ts
|
|
35362
|
+
var specNum = (id) => Number.parseInt(id.match(/\d+/)?.[0] ?? "0", 10);
|
|
34340
35363
|
async function liveSpecs(filter = {}) {
|
|
34341
|
-
const specs = await prisma.spec.findMany({
|
|
35364
|
+
const specs = (await prisma.spec.findMany({
|
|
34342
35365
|
where: { projectId: filter.project, source: filter.source },
|
|
34343
35366
|
orderBy: { id: "desc" }
|
|
34344
|
-
});
|
|
35367
|
+
})).sort((a, b) => specNum(b.id) - specNum(a.id));
|
|
34345
35368
|
const live = sessionPhasesBySpec();
|
|
34346
35369
|
if (live.size === 0) return decorateBlocked(specs);
|
|
34347
35370
|
const advanced = [];
|
|
@@ -35226,7 +36249,7 @@ async function deleteBranches(repoDir, names, opts) {
|
|
|
35226
36249
|
import { execFile as execFile11 } from "node:child_process";
|
|
35227
36250
|
import { realpathSync as realpathSync4 } from "node:fs";
|
|
35228
36251
|
import { stat } from "node:fs/promises";
|
|
35229
|
-
import { basename as basename4, join as
|
|
36252
|
+
import { basename as basename4, join as join17, resolve as resolve15, sep as sep4 } from "node:path";
|
|
35230
36253
|
import { promisify as promisify10 } from "node:util";
|
|
35231
36254
|
var exec10 = promisify10(execFile11);
|
|
35232
36255
|
var GIT7 = { timeout: 6e4, maxBuffer: 1 << 24, encoding: "utf8" };
|
|
@@ -35271,7 +36294,7 @@ var real = (p3) => {
|
|
|
35271
36294
|
};
|
|
35272
36295
|
async function bornAt(path) {
|
|
35273
36296
|
try {
|
|
35274
|
-
const st = await stat(
|
|
36297
|
+
const st = await stat(join17(path, ".git"));
|
|
35275
36298
|
const ms = st.birthtimeMs > 0 ? st.birthtimeMs : st.mtimeMs;
|
|
35276
36299
|
return new Date(ms).toISOString();
|
|
35277
36300
|
} catch {
|
|
@@ -35436,7 +36459,7 @@ init_session_id();
|
|
|
35436
36459
|
// src/services/repo-fs.ts
|
|
35437
36460
|
import { lstat as lstat2, rename as rename3, rm as rm5 } from "node:fs/promises";
|
|
35438
36461
|
import { createWriteStream } from "node:fs";
|
|
35439
|
-
import { join as
|
|
36462
|
+
import { join as join18 } from "node:path";
|
|
35440
36463
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
35441
36464
|
import { pipeline } from "node:stream/promises";
|
|
35442
36465
|
var EntryExistsError = class extends Error {
|
|
@@ -35497,7 +36520,7 @@ async function saveUpload(repoDir, rel, source, opts = {}) {
|
|
|
35497
36520
|
if (current && !opts.overwrite) return { status: "exists" };
|
|
35498
36521
|
if (current && !current.isFile())
|
|
35499
36522
|
throw new PathContainmentError("repository path ditolak: target bukan file regular");
|
|
35500
|
-
const temp =
|
|
36523
|
+
const temp = join18(entry.parent, `.hanoman-${randomUUID6()}.tmp`);
|
|
35501
36524
|
try {
|
|
35502
36525
|
await pipeline(source, createWriteStream(temp, { flags: "wx", mode: 384 }));
|
|
35503
36526
|
if (opts.isTruncated?.()) {
|
|
@@ -35515,13 +36538,13 @@ async function saveUpload(repoDir, rel, source, opts = {}) {
|
|
|
35515
36538
|
// src/routes/ide.ts
|
|
35516
36539
|
var activeSessions = (id) => listSessions().filter((s2) => s2.projectId === id && !s2.exited).length;
|
|
35517
36540
|
async function lockInputs(id) {
|
|
35518
|
-
const
|
|
36541
|
+
const open5 = await prisma.spec.findMany({
|
|
35519
36542
|
where: { projectId: id, stage: { not: "done" } },
|
|
35520
36543
|
select: { id: true }
|
|
35521
36544
|
});
|
|
35522
36545
|
const sessions = listSessions().filter((s2) => s2.projectId === id && !s2.exited).map((s2) => s2.branch || (s2.specId ? `hanoman/${s2.id}` : "")).filter(Boolean);
|
|
35523
36546
|
return {
|
|
35524
|
-
openSpecBranches: new Set(
|
|
36547
|
+
openSpecBranches: new Set(open5.map((s2) => sourceBranch(s2.id))),
|
|
35525
36548
|
sessionBranches: new Set(sessions)
|
|
35526
36549
|
};
|
|
35527
36550
|
}
|
|
@@ -36013,14 +37036,14 @@ async function finishGraphOp(reply, id, repoDir, r, verb) {
|
|
|
36013
37036
|
// src/routes/fs.ts
|
|
36014
37037
|
import { readdir as readdir4 } from "node:fs/promises";
|
|
36015
37038
|
import { homedir as homedir4 } from "node:os";
|
|
36016
|
-
import { resolve as resolve17, dirname as dirname9, join as
|
|
37039
|
+
import { resolve as resolve17, dirname as dirname9, join as join19 } from "node:path";
|
|
36017
37040
|
async function fs_default(app2) {
|
|
36018
37041
|
app2.get("/fs/browse", async (req, reply) => {
|
|
36019
37042
|
const q = req.query.path;
|
|
36020
37043
|
const dir2 = q && q.trim() ? resolve17(q.trim()) : homedir4();
|
|
36021
37044
|
try {
|
|
36022
37045
|
const ents = await readdir4(dir2, { withFileTypes: true });
|
|
36023
|
-
const entries3 = ents.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => ({ name: e.name, path:
|
|
37046
|
+
const entries3 = ents.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => ({ name: e.name, path: join19(dir2, e.name) })).sort((a, b) => a.name.localeCompare(b.name));
|
|
36024
37047
|
const parent = dirname9(dir2);
|
|
36025
37048
|
return { path: dir2, parent: parent === dir2 ? null : parent, entries: entries3 };
|
|
36026
37049
|
} catch {
|
|
@@ -36502,14 +37525,14 @@ function installCommand(m, agent, shell = shellBin()) {
|
|
|
36502
37525
|
}
|
|
36503
37526
|
|
|
36504
37527
|
// src/services/terminal-diag.ts
|
|
36505
|
-
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync9, statSync as statSync3, rmSync as
|
|
36506
|
-
import { join as
|
|
37528
|
+
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync9, statSync as statSync3, rmSync as rmSync5 } from "node:fs";
|
|
37529
|
+
import { join as join20 } from "node:path";
|
|
36507
37530
|
var DIAG_MAX_BYTES = 2 * 1024 * 1024;
|
|
36508
37531
|
var KINDS = /* @__PURE__ */ new Set(["key", "comp", "data", "ack", "pred"]);
|
|
36509
37532
|
var ID = /^[A-Za-z0-9_-]{1,64}$/;
|
|
36510
37533
|
function diagFile(home3, sessionId2) {
|
|
36511
37534
|
if (!ID.test(sessionId2)) throw new Error(`id sesi tak sah untuk diag: ${sessionId2}`);
|
|
36512
|
-
return
|
|
37535
|
+
return join20(home3, "diag", `${sessionId2}.jsonl`);
|
|
36513
37536
|
}
|
|
36514
37537
|
function usable(ev) {
|
|
36515
37538
|
if (!ev || typeof ev !== "object") return false;
|
|
@@ -36520,9 +37543,9 @@ function appendDiag(home3, sessionId2, events) {
|
|
|
36520
37543
|
const file = diagFile(home3, sessionId2);
|
|
36521
37544
|
const rows = events.filter(usable);
|
|
36522
37545
|
if (!rows.length) return;
|
|
36523
|
-
mkdirSync9(
|
|
37546
|
+
mkdirSync9(join20(home3, "diag"), { recursive: true });
|
|
36524
37547
|
try {
|
|
36525
|
-
if (statSync3(file).size > DIAG_MAX_BYTES)
|
|
37548
|
+
if (statSync3(file).size > DIAG_MAX_BYTES) rmSync5(file);
|
|
36526
37549
|
} catch {
|
|
36527
37550
|
}
|
|
36528
37551
|
appendFileSync2(file, rows.map((r) => JSON.stringify(r)).join("\n") + "\n");
|
|
@@ -37077,9 +38100,9 @@ init_pty();
|
|
|
37077
38100
|
init_session_sandbox();
|
|
37078
38101
|
import { execFile as execFile13 } from "node:child_process";
|
|
37079
38102
|
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
37080
|
-
import { mkdirSync as mkdirSync10, rmSync as
|
|
37081
|
-
import { tmpdir as
|
|
37082
|
-
import { join as
|
|
38103
|
+
import { mkdirSync as mkdirSync10, rmSync as rmSync6, writeFileSync as writeFileSync7 } from "node:fs";
|
|
38104
|
+
import { tmpdir as tmpdir4 } from "node:os";
|
|
38105
|
+
import { join as join21 } from "node:path";
|
|
37083
38106
|
var binFor = (agent) => agent === "codex" ? effectiveStr("HANOMAN_CODEX_BIN") ?? "codex" : effectiveStr("HANOMAN_CLAUDE_BIN") ?? "claude";
|
|
37084
38107
|
function leadArgv(o) {
|
|
37085
38108
|
if (o.agent === "codex") {
|
|
@@ -37100,22 +38123,22 @@ function leadArgv(o) {
|
|
|
37100
38123
|
];
|
|
37101
38124
|
}
|
|
37102
38125
|
var leadEnv = (agent, base2 = process.env, uid = process.getuid?.()) => agent === "claude" ? { ...rootBypassEnv(uid), ...base2 } : { ...base2 };
|
|
37103
|
-
var
|
|
38126
|
+
var shellQuote3 = (value) => `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
37104
38127
|
function leadProcess(prompt, o, env = process.env) {
|
|
37105
38128
|
const file = binFor(o.agent);
|
|
37106
38129
|
const directArgs = leadArgv({ agent: o.agent, model: o.model, effort: o.effort, prompt });
|
|
37107
38130
|
const mode = env.HANOMAN_SESSION_SANDBOX ?? (resolveHardening(env) ? "required" : "off");
|
|
37108
38131
|
if (mode === "off") return { file, args: directArgs, cwd: o.cwd, cleanup: () => {
|
|
37109
38132
|
} };
|
|
37110
|
-
const promptDir =
|
|
38133
|
+
const promptDir = join21(tmpdir4(), "hanoman-prompts");
|
|
37111
38134
|
mkdirSync10(promptDir, { recursive: true, mode: 448 });
|
|
37112
|
-
const promptFile =
|
|
37113
|
-
|
|
37114
|
-
const workspace = o.cwd ??
|
|
38135
|
+
const promptFile = join21(promptDir, `oneshot-${randomUUID7()}`);
|
|
38136
|
+
writeFileSync7(promptFile, prompt, { flag: "wx", mode: 384 });
|
|
38137
|
+
const workspace = o.cwd ?? join21(tmpdir4(), `hanoman-oneshot-${randomUUID7()}`);
|
|
37115
38138
|
if (!o.cwd) mkdirSync10(workspace, { recursive: false, mode: 448 });
|
|
37116
38139
|
try {
|
|
37117
38140
|
const argsWithoutPrompt = directArgs.slice(0, -1);
|
|
37118
|
-
const command = [file, ...argsWithoutPrompt].map(
|
|
38141
|
+
const command = [file, ...argsWithoutPrompt].map(shellQuote3).join(" ") + ` "$(cat ${shellQuote3(promptFile)})"`;
|
|
37119
38142
|
const sandbox = sandboxArgvFromEnv({
|
|
37120
38143
|
command,
|
|
37121
38144
|
worktree: workspace,
|
|
@@ -37129,13 +38152,13 @@ function leadProcess(prompt, o, env = process.env) {
|
|
|
37129
38152
|
args: sandbox.slice(1),
|
|
37130
38153
|
promptFile,
|
|
37131
38154
|
cleanup: () => {
|
|
37132
|
-
|
|
37133
|
-
if (!o.cwd)
|
|
38155
|
+
rmSync6(promptFile, { force: true });
|
|
38156
|
+
if (!o.cwd) rmSync6(workspace, { recursive: true, force: true });
|
|
37134
38157
|
}
|
|
37135
38158
|
};
|
|
37136
38159
|
} catch (error) {
|
|
37137
|
-
|
|
37138
|
-
if (!o.cwd)
|
|
38160
|
+
rmSync6(promptFile, { force: true });
|
|
38161
|
+
if (!o.cwd) rmSync6(workspace, { recursive: true, force: true });
|
|
37139
38162
|
throw error;
|
|
37140
38163
|
}
|
|
37141
38164
|
}
|
|
@@ -37153,7 +38176,7 @@ function leadFailureReason(agent, timeoutMs, err, stdout, stderr) {
|
|
|
37153
38176
|
}
|
|
37154
38177
|
function think(prompt, o) {
|
|
37155
38178
|
const process2 = leadProcess(prompt, o);
|
|
37156
|
-
return new Promise((
|
|
38179
|
+
return new Promise((resolve22, reject2) => {
|
|
37157
38180
|
const child = execFile13(process2.file, process2.args, {
|
|
37158
38181
|
cwd: process2.cwd,
|
|
37159
38182
|
timeout: o.timeoutMs,
|
|
@@ -37167,7 +38190,7 @@ function think(prompt, o) {
|
|
|
37167
38190
|
reject2(new Error(leadFailureReason(o.agent, o.timeoutMs, err, stdout, stderr)));
|
|
37168
38191
|
return;
|
|
37169
38192
|
}
|
|
37170
|
-
|
|
38193
|
+
resolve22(stdout);
|
|
37171
38194
|
});
|
|
37172
38195
|
child.stdin?.end();
|
|
37173
38196
|
});
|
|
@@ -37204,9 +38227,9 @@ function acquire(cap, waitMs) {
|
|
|
37204
38227
|
return Promise.resolve();
|
|
37205
38228
|
}
|
|
37206
38229
|
const startedAt = Date.now();
|
|
37207
|
-
return new Promise((
|
|
38230
|
+
return new Promise((resolve22, reject2) => {
|
|
37208
38231
|
const w = {
|
|
37209
|
-
grant:
|
|
38232
|
+
grant: resolve22,
|
|
37210
38233
|
deny: reject2,
|
|
37211
38234
|
timer: setTimeout(() => {
|
|
37212
38235
|
const i = queue.indexOf(w);
|
|
@@ -38602,9 +39625,9 @@ import { readFileSync as readFileSync10 } from "node:fs";
|
|
|
38602
39625
|
// src/services/vps-ssh.ts
|
|
38603
39626
|
init_config2();
|
|
38604
39627
|
import { spawn as spawn4 } from "node:child_process";
|
|
38605
|
-
import { mkdtempSync, rmSync as
|
|
38606
|
-
import { tmpdir as
|
|
38607
|
-
import { dirname as dirname10, join as
|
|
39628
|
+
import { mkdtempSync, rmSync as rmSync7, writeFileSync as writeFileSync8 } from "node:fs";
|
|
39629
|
+
import { tmpdir as tmpdir5 } from "node:os";
|
|
39630
|
+
import { dirname as dirname10, join as join22 } from "node:path";
|
|
38608
39631
|
var sshBin = () => effectiveStr("HANOMAN_SSH_BIN") ?? "ssh";
|
|
38609
39632
|
function consoleArgv(t) {
|
|
38610
39633
|
return [
|
|
@@ -38619,9 +39642,9 @@ function consoleArgv(t) {
|
|
|
38619
39642
|
];
|
|
38620
39643
|
}
|
|
38621
39644
|
function askpassScript() {
|
|
38622
|
-
const dir2 = mkdtempSync(
|
|
38623
|
-
const path =
|
|
38624
|
-
|
|
39645
|
+
const dir2 = mkdtempSync(join22(tmpdir5(), "hanoman-askpass-"));
|
|
39646
|
+
const path = join22(dir2, "askpass.sh");
|
|
39647
|
+
writeFileSync8(path, `#!/bin/sh
|
|
38625
39648
|
printf '%s' "$HANOMAN_SSH_PASSWORD"
|
|
38626
39649
|
`, { mode: 448 });
|
|
38627
39650
|
return path;
|
|
@@ -38654,14 +39677,14 @@ function sshExec(t, remoteCmd, opts = {}) {
|
|
|
38654
39677
|
SSH_ASKPASS_REQUIRE: "force",
|
|
38655
39678
|
HANOMAN_SSH_PASSWORD: opts.password
|
|
38656
39679
|
} : process.env;
|
|
38657
|
-
return new Promise((
|
|
39680
|
+
return new Promise((resolve22) => {
|
|
38658
39681
|
const p3 = spawn4(sshBin(), args, { stdio: ["pipe", "pipe", "pipe"], env });
|
|
38659
39682
|
let out4 = "";
|
|
38660
39683
|
const timer9 = setTimeout(() => p3.kill("SIGKILL"), opts.timeoutMs ?? 6e4);
|
|
38661
39684
|
const done = (r) => {
|
|
38662
39685
|
clearTimeout(timer9);
|
|
38663
|
-
if (askpass)
|
|
38664
|
-
|
|
39686
|
+
if (askpass) rmSync7(dirname10(askpass), { recursive: true, force: true });
|
|
39687
|
+
resolve22(r);
|
|
38665
39688
|
};
|
|
38666
39689
|
p3.stdout.on("data", (d) => {
|
|
38667
39690
|
out4 += d;
|
|
@@ -38681,16 +39704,16 @@ function sshExec(t, remoteCmd, opts = {}) {
|
|
|
38681
39704
|
// src/services/vps-audit.ts
|
|
38682
39705
|
init_db();
|
|
38683
39706
|
import { existsSync as existsSync14, readFileSync as readFileSync9 } from "node:fs";
|
|
38684
|
-
import { join as
|
|
39707
|
+
import { join as join24 } from "node:path";
|
|
38685
39708
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
38686
39709
|
|
|
38687
39710
|
// src/runner/deps.ts
|
|
38688
39711
|
import { existsSync as existsSync13 } from "node:fs";
|
|
38689
|
-
import { dirname as dirname11, join as
|
|
39712
|
+
import { dirname as dirname11, join as join23 } from "node:path";
|
|
38690
39713
|
function repoRootFrom(startDir) {
|
|
38691
39714
|
let dir2 = startDir;
|
|
38692
39715
|
for (let i = 0; i < 8; i++) {
|
|
38693
|
-
if (existsSync13(
|
|
39716
|
+
if (existsSync13(join23(dir2, "pnpm-workspace.yaml"))) return dir2;
|
|
38694
39717
|
const parent = dirname11(dir2);
|
|
38695
39718
|
if (parent === dir2) break;
|
|
38696
39719
|
dir2 = parent;
|
|
@@ -38699,6 +39722,9 @@ function repoRootFrom(startDir) {
|
|
|
38699
39722
|
}
|
|
38700
39723
|
var repoRoot = (startDir = process.cwd()) => repoRootFrom(startDir);
|
|
38701
39724
|
|
|
39725
|
+
// src/services/vps-audit.ts
|
|
39726
|
+
init_sync_notify();
|
|
39727
|
+
|
|
38702
39728
|
// src/vps/scoring.ts
|
|
38703
39729
|
var pct = (fulfilled, applicable) => applicable === 0 ? 100 : Math.round(fulfilled / applicable * 100);
|
|
38704
39730
|
function scoreCompliance(probeStatus, states, items = CATALOG) {
|
|
@@ -38800,7 +39826,7 @@ var packagedScript = (f) => fileURLToPath3(new URL(`../scripts/vps/${f}`, import
|
|
|
38800
39826
|
var moduleDir = () => fileURLToPath3(new URL(".", import.meta.url));
|
|
38801
39827
|
var scriptPath = (f) => {
|
|
38802
39828
|
const packed = packagedScript(f);
|
|
38803
|
-
return existsSync14(packed) ? packed :
|
|
39829
|
+
return existsSync14(packed) ? packed : join24(repoRoot(moduleDir()), "server", "scripts", "vps", f);
|
|
38804
39830
|
};
|
|
38805
39831
|
async function itemStatesOf(vpsId) {
|
|
38806
39832
|
const rows = await prisma.vpsItemState.findMany({ where: { vpsId } });
|
|
@@ -39008,27 +40034,27 @@ async function buildChecklist(vpsId) {
|
|
|
39008
40034
|
init_src2();
|
|
39009
40035
|
init_config2();
|
|
39010
40036
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
39011
|
-
import { chmodSync as
|
|
40037
|
+
import { chmodSync as chmodSync7, copyFileSync, existsSync as existsSync15, mkdirSync as mkdirSync11, readFileSync as readFileSync12, unlinkSync as unlinkSync2 } from "node:fs";
|
|
39012
40038
|
import { homedir as homedir5 } from "node:os";
|
|
39013
|
-
import { join as
|
|
40039
|
+
import { join as join25 } from "node:path";
|
|
39014
40040
|
var keyDir = () => effectiveStr("HANOMAN_SSH_KEY_DIR") ?? resolveDataDirs().sshKeys;
|
|
39015
40041
|
var KEY_FILES = ["id_ed25519", "id_ed25519.pub"];
|
|
39016
40042
|
function adoptLegacyKey(dir2) {
|
|
39017
40043
|
if (effectiveStr("HANOMAN_SSH_KEY_DIR")) return;
|
|
39018
|
-
const legacy =
|
|
40044
|
+
const legacy = join25(homedir5(), ".hanoman");
|
|
39019
40045
|
if (legacy === dir2) return;
|
|
39020
|
-
if (!KEY_FILES.every((f) => existsSync15(
|
|
40046
|
+
if (!KEY_FILES.every((f) => existsSync15(join25(legacy, f)))) return;
|
|
39021
40047
|
mkdirSync11(dir2, { recursive: true, mode: 448 });
|
|
39022
40048
|
for (const f of KEY_FILES) {
|
|
39023
|
-
copyFileSync(
|
|
39024
|
-
|
|
40049
|
+
copyFileSync(join25(legacy, f), join25(dir2, f));
|
|
40050
|
+
chmodSync7(join25(dir2, f), 384);
|
|
39025
40051
|
}
|
|
39026
|
-
for (const f of KEY_FILES) unlinkSync2(
|
|
40052
|
+
for (const f of KEY_FILES) unlinkSync2(join25(legacy, f));
|
|
39027
40053
|
console.log(`vps: key SSH dipindah dari ${legacy} ke ${dir2} (SPEC-846 \u2014 satu batas backup)`);
|
|
39028
40054
|
}
|
|
39029
40055
|
function ensureHanomanKey() {
|
|
39030
40056
|
const dir2 = keyDir();
|
|
39031
|
-
const privPath =
|
|
40057
|
+
const privPath = join25(dir2, KEY_FILES[0]);
|
|
39032
40058
|
const pubPath = `${privPath}.pub`;
|
|
39033
40059
|
if (!existsSync15(privPath)) adoptLegacyKey(dir2);
|
|
39034
40060
|
if (!existsSync15(privPath)) {
|
|
@@ -39057,6 +40083,7 @@ async function bootstrapKey(t, password) {
|
|
|
39057
40083
|
// src/routes/vps.ts
|
|
39058
40084
|
init_pty();
|
|
39059
40085
|
init_settings3();
|
|
40086
|
+
init_sync_notify();
|
|
39060
40087
|
function keyMissing(v) {
|
|
39061
40088
|
return !!v.keyPath && !existsSync16(v.keyPath);
|
|
39062
40089
|
}
|
|
@@ -39340,7 +40367,7 @@ init_config2();
|
|
|
39340
40367
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
39341
40368
|
import { readFileSync as readFileSync14 } from "node:fs";
|
|
39342
40369
|
import { homedir as homedir7 } from "node:os";
|
|
39343
|
-
import { join as
|
|
40370
|
+
import { join as join26 } from "node:path";
|
|
39344
40371
|
var USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
|
|
39345
40372
|
var TTL_MS2 = 3e4;
|
|
39346
40373
|
var lastOk = null;
|
|
@@ -39353,7 +40380,7 @@ var LABELS = {
|
|
|
39353
40380
|
var humanize = (k) => k.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
39354
40381
|
var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
39355
40382
|
function credsFile() {
|
|
39356
|
-
return
|
|
40383
|
+
return join26(effectiveStr("CLAUDE_CONFIG_DIR") ?? join26(homedir7(), ".claude"), ".credentials.json");
|
|
39357
40384
|
}
|
|
39358
40385
|
function readAccessToken() {
|
|
39359
40386
|
if (process.platform === "darwin" && !effectiveStr("CLAUDE_CONFIG_DIR")) {
|
|
@@ -39427,14 +40454,14 @@ async function getLimits() {
|
|
|
39427
40454
|
// src/services/codex-limits.ts
|
|
39428
40455
|
import { open as open3, readdir as readdir5, stat as stat2 } from "node:fs/promises";
|
|
39429
40456
|
import { homedir as homedir8 } from "node:os";
|
|
39430
|
-
import { join as
|
|
40457
|
+
import { join as join27 } from "node:path";
|
|
39431
40458
|
var TTL_MS3 = 3e4;
|
|
39432
40459
|
var STALE_AFTER_MS = 12 * 36e5;
|
|
39433
40460
|
var TAIL_BYTES = 512 * 1024;
|
|
39434
40461
|
var MAX_FILES = 8;
|
|
39435
40462
|
var cache2 = null;
|
|
39436
40463
|
var freshUntil2 = 0;
|
|
39437
|
-
var codexSessionsDir = () =>
|
|
40464
|
+
var codexSessionsDir = () => join27(process.env.CODEX_HOME ?? join27(homedir8(), ".codex"), "sessions");
|
|
39438
40465
|
var UNAVAILABLE = { status: "unavailable", windows: [], fetchedAt: null, plan: null };
|
|
39439
40466
|
function windowLabel(minutes) {
|
|
39440
40467
|
if (minutes === 300) return "Sesi 5 jam";
|
|
@@ -39467,7 +40494,7 @@ async function recentRollouts(dir2) {
|
|
|
39467
40494
|
return [];
|
|
39468
40495
|
}
|
|
39469
40496
|
const stamped = await Promise.all(entries3.map(async (rel) => {
|
|
39470
|
-
const full =
|
|
40497
|
+
const full = join27(dir2, rel);
|
|
39471
40498
|
try {
|
|
39472
40499
|
return { full, mtime: (await stat2(full)).mtimeMs };
|
|
39473
40500
|
} catch {
|
|
@@ -39544,41 +40571,8 @@ async function limits(app2) {
|
|
|
39544
40571
|
app2.get("/limits/codex", async () => getCodexLimits());
|
|
39545
40572
|
}
|
|
39546
40573
|
|
|
39547
|
-
// src/services/codex-version.ts
|
|
39548
|
-
init_src();
|
|
39549
|
-
init_config2();
|
|
39550
|
-
import { execFile as execFile14 } from "node:child_process";
|
|
39551
|
-
import { promisify as promisify13 } from "node:util";
|
|
39552
|
-
var run3 = promisify13(execFile14);
|
|
39553
|
-
var CODEX_MIN_CLIENT = "0.144.0";
|
|
39554
|
-
var TTL_MS4 = 5 * 6e4;
|
|
39555
|
-
var cache3 = null;
|
|
39556
|
-
var codexBin2 = () => effectiveStr("HANOMAN_CODEX_BIN") ?? "codex";
|
|
39557
|
-
function parseCodexVersion(out4) {
|
|
39558
|
-
const m = /(\d+)\.(\d+)\.(\d+)/.exec(out4);
|
|
39559
|
-
return m ? `${m[1]}.${m[2]}.${m[3]}` : null;
|
|
39560
|
-
}
|
|
39561
|
-
async function getCodexVersion(now = Date.now()) {
|
|
39562
|
-
if (cache3 && now - cache3.at < TTL_MS4) return cache3.version;
|
|
39563
|
-
let version = null;
|
|
39564
|
-
try {
|
|
39565
|
-
const { stdout } = await run3(codexBin2(), ["--version"], { timeout: 5e3 });
|
|
39566
|
-
version = parseCodexVersion(stdout);
|
|
39567
|
-
} catch {
|
|
39568
|
-
}
|
|
39569
|
-
cache3 = { at: now, version };
|
|
39570
|
-
return version;
|
|
39571
|
-
}
|
|
39572
|
-
async function codexVersionInfo() {
|
|
39573
|
-
const version = await getCodexVersion();
|
|
39574
|
-
return {
|
|
39575
|
-
version,
|
|
39576
|
-
minRequired: CODEX_MIN_CLIENT,
|
|
39577
|
-
ok: version === null || cmpVersion(version, CODEX_MIN_CLIENT) >= 0
|
|
39578
|
-
};
|
|
39579
|
-
}
|
|
39580
|
-
|
|
39581
40574
|
// src/routes/codex.ts
|
|
40575
|
+
init_codex_version();
|
|
39582
40576
|
async function codex(app2) {
|
|
39583
40577
|
app2.get("/codex/version", async () => codexVersionInfo());
|
|
39584
40578
|
}
|
|
@@ -41179,7 +42173,7 @@ init_config2();
|
|
|
41179
42173
|
init_src2();
|
|
41180
42174
|
import { randomUUID as randomUUID8 } from "node:crypto";
|
|
41181
42175
|
import { mkdir as mkdir5, writeFile as writeFile4, readFile as readFile4, unlink as unlink4, readdir as readdir6, stat as stat3 } from "node:fs/promises";
|
|
41182
|
-
import { join as
|
|
42176
|
+
import { join as join28, resolve as resolve19, basename as basename6 } from "node:path";
|
|
41183
42177
|
var MAX_TRANSCRIPT_BYTES = 1024 * 1024;
|
|
41184
42178
|
function transcriptDir() {
|
|
41185
42179
|
return resolve19(effectiveStr("HANOMAN_TRANSCRIPT_DIR")?.trim() || resolveDataDirs().transcripts);
|
|
@@ -41200,13 +42194,13 @@ async function saveTranscript(text) {
|
|
|
41200
42194
|
const dir2 = transcriptDir();
|
|
41201
42195
|
await mkdir5(dir2, { recursive: true, mode: 448 });
|
|
41202
42196
|
const key = `${randomUUID8()}.log`;
|
|
41203
|
-
await writeFile4(
|
|
42197
|
+
await writeFile4(join28(dir2, key), body, { encoding: "utf8", mode: 384 });
|
|
41204
42198
|
return { key, bytes: Buffer.byteLength(body, "utf8"), truncated };
|
|
41205
42199
|
}
|
|
41206
42200
|
async function readTranscript(key) {
|
|
41207
42201
|
if (!key) return null;
|
|
41208
42202
|
try {
|
|
41209
|
-
return await readFile4(
|
|
42203
|
+
return await readFile4(join28(transcriptDir(), basename6(key)), "utf8");
|
|
41210
42204
|
} catch {
|
|
41211
42205
|
return null;
|
|
41212
42206
|
}
|
|
@@ -41214,7 +42208,7 @@ async function readTranscript(key) {
|
|
|
41214
42208
|
async function deleteTranscript(key) {
|
|
41215
42209
|
if (!key) return;
|
|
41216
42210
|
try {
|
|
41217
|
-
await unlink4(
|
|
42211
|
+
await unlink4(join28(transcriptDir(), basename6(key)));
|
|
41218
42212
|
} catch (e) {
|
|
41219
42213
|
if (e.code !== "ENOENT") throw e;
|
|
41220
42214
|
}
|
|
@@ -41232,7 +42226,7 @@ async function listTranscripts() {
|
|
|
41232
42226
|
for (const name2 of names) {
|
|
41233
42227
|
if (!name2.endsWith(".log")) continue;
|
|
41234
42228
|
try {
|
|
41235
|
-
rows.push({ key: name2, mtimeMs: (await stat3(
|
|
42229
|
+
rows.push({ key: name2, mtimeMs: (await stat3(join28(dir2, name2))).mtimeMs });
|
|
41236
42230
|
} catch {
|
|
41237
42231
|
}
|
|
41238
42232
|
}
|
|
@@ -41286,11 +42280,11 @@ async function beginSession(b) {
|
|
|
41286
42280
|
});
|
|
41287
42281
|
}
|
|
41288
42282
|
async function finishSession(d) {
|
|
41289
|
-
const
|
|
42283
|
+
const open5 = await prisma.sessionHistory.findFirst({
|
|
41290
42284
|
where: { sessionId: d.sessionId, endedAt: null },
|
|
41291
42285
|
orderBy: { startedAt: "desc" }
|
|
41292
42286
|
});
|
|
41293
|
-
if (!
|
|
42287
|
+
if (!open5) return;
|
|
41294
42288
|
let t = { key: "", bytes: 0 };
|
|
41295
42289
|
if (d.transcript) {
|
|
41296
42290
|
try {
|
|
@@ -41300,7 +42294,7 @@ async function finishSession(d) {
|
|
|
41300
42294
|
}
|
|
41301
42295
|
}
|
|
41302
42296
|
await prisma.sessionHistory.update({
|
|
41303
|
-
where: { id:
|
|
42297
|
+
where: { id: open5.id },
|
|
41304
42298
|
data: {
|
|
41305
42299
|
endedAt: /* @__PURE__ */ new Date(),
|
|
41306
42300
|
endedReason: CLOSED,
|
|
@@ -41415,14 +42409,14 @@ async function reconcileTranscripts(opts = {}) {
|
|
|
41415
42409
|
return report;
|
|
41416
42410
|
}
|
|
41417
42411
|
async function reconcileHistory(liveSessionIds) {
|
|
41418
|
-
const
|
|
42412
|
+
const open5 = await prisma.sessionHistory.findMany({
|
|
41419
42413
|
where: { endedAt: null },
|
|
41420
42414
|
select: { id: true, sessionId: true, updatedAt: true }
|
|
41421
42415
|
});
|
|
41422
42416
|
const live = new Set(liveSessionIds);
|
|
41423
42417
|
const at = /* @__PURE__ */ new Date();
|
|
41424
42418
|
let closed = 0;
|
|
41425
|
-
for (const r of
|
|
42419
|
+
for (const r of open5) {
|
|
41426
42420
|
if (live.has(r.sessionId)) continue;
|
|
41427
42421
|
await prisma.sessionHistory.update({
|
|
41428
42422
|
where: { id: r.id },
|
|
@@ -41473,7 +42467,285 @@ async function session_history_default(app2) {
|
|
|
41473
42467
|
init_src();
|
|
41474
42468
|
init_session_event_token();
|
|
41475
42469
|
init_pty();
|
|
42470
|
+
|
|
42471
|
+
// src/services/agent-invocations.ts
|
|
42472
|
+
init_src();
|
|
42473
|
+
init_db();
|
|
42474
|
+
import { createHash as createHash8, randomUUID as randomUUID10 } from "node:crypto";
|
|
42475
|
+
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
42476
|
+
import { homedir as homedir9 } from "node:os";
|
|
42477
|
+
import { readFileSync as readFileSync16, realpathSync as realpathSync5, statSync as statSync4 } from "node:fs";
|
|
42478
|
+
import { isAbsolute as isAbsolute7, relative as relative3, resolve as resolve20 } from "node:path";
|
|
42479
|
+
var MAX_EXCERPT_BYTES = 4096;
|
|
42480
|
+
var MAX_TRANSCRIPT_BYTES2 = 10 * 1024 * 1024;
|
|
42481
|
+
var ANSI2 = /[\u001b\u009b](?:\][^\u0007]*(?:\u0007|\u001b\\)|\[[0-?]*[ -/]*[@-~]|[@-_])/g;
|
|
42482
|
+
var snapshotHashes = /* @__PURE__ */ new Map();
|
|
42483
|
+
var keyOf = (x) => `${x.sessionId}\0${x.runtimeInvocationId}`;
|
|
42484
|
+
var hash2 = (value) => createHash8("sha256").update(value).digest("hex");
|
|
42485
|
+
var defaultGitStatus = (cwd) => {
|
|
42486
|
+
try {
|
|
42487
|
+
return execFileSync5("git", ["-C", cwd, "status", "--porcelain=v1", "-z"], {
|
|
42488
|
+
encoding: "utf8",
|
|
42489
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
42490
|
+
});
|
|
42491
|
+
} catch {
|
|
42492
|
+
return null;
|
|
42493
|
+
}
|
|
42494
|
+
};
|
|
42495
|
+
function snapshot2(cwd, run4 = defaultGitStatus) {
|
|
42496
|
+
try {
|
|
42497
|
+
const value = run4(cwd);
|
|
42498
|
+
return value === null ? null : hash2(value);
|
|
42499
|
+
} catch {
|
|
42500
|
+
return null;
|
|
42501
|
+
}
|
|
42502
|
+
}
|
|
42503
|
+
var stripAnsi = (value) => value.replace(ANSI2, "");
|
|
42504
|
+
var utf8Prefix = (value, maxBytes) => {
|
|
42505
|
+
let out4 = "", bytes = 0;
|
|
42506
|
+
for (const char of value) {
|
|
42507
|
+
const next = Buffer.byteLength(char, "utf8");
|
|
42508
|
+
if (bytes + next > maxBytes) break;
|
|
42509
|
+
out4 += char;
|
|
42510
|
+
bytes += next;
|
|
42511
|
+
}
|
|
42512
|
+
return out4;
|
|
42513
|
+
};
|
|
42514
|
+
var transcriptRoots = () => [
|
|
42515
|
+
resolve20(process.env.CLAUDE_CONFIG_DIR ?? `${homedir9()}/.claude`),
|
|
42516
|
+
resolve20(process.env.CODEX_HOME ?? `${homedir9()}/.codex`)
|
|
42517
|
+
];
|
|
42518
|
+
var inside = (path, root) => {
|
|
42519
|
+
const rel = relative3(root, path);
|
|
42520
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute7(rel);
|
|
42521
|
+
};
|
|
42522
|
+
var EMPTY_USAGE = { inputTokens: null, outputTokens: null, cachedTokens: null };
|
|
42523
|
+
var nonnegativeInt = (value) => Number.isSafeInteger(value) && Number(value) >= 0 ? Number(value) : null;
|
|
42524
|
+
function usageFromText(text) {
|
|
42525
|
+
const found = [];
|
|
42526
|
+
const inspect = (value) => {
|
|
42527
|
+
if (!value || typeof value !== "object") return;
|
|
42528
|
+
const record2 = value;
|
|
42529
|
+
const usage2 = record2.usage;
|
|
42530
|
+
if (usage2 && typeof usage2 === "object") {
|
|
42531
|
+
const u = usage2;
|
|
42532
|
+
found.push([
|
|
42533
|
+
nonnegativeInt(u.input_tokens ?? u.inputTokens) ?? -1,
|
|
42534
|
+
nonnegativeInt(u.output_tokens ?? u.outputTokens) ?? -1,
|
|
42535
|
+
nonnegativeInt(u.cached_tokens ?? u.cachedTokens ?? u.cache_read_input_tokens ?? u.cacheReadInputTokens) ?? -1
|
|
42536
|
+
]);
|
|
42537
|
+
}
|
|
42538
|
+
};
|
|
42539
|
+
for (const line of text.split("\n")) {
|
|
42540
|
+
if (!line.trim()) continue;
|
|
42541
|
+
try {
|
|
42542
|
+
inspect(JSON.parse(line));
|
|
42543
|
+
} catch {
|
|
42544
|
+
}
|
|
42545
|
+
}
|
|
42546
|
+
if (found.length === 0) return EMPTY_USAGE;
|
|
42547
|
+
const max = (index) => {
|
|
42548
|
+
const values = found.map((entry) => entry[index]).filter((n2) => n2 >= 0);
|
|
42549
|
+
return values.length ? Math.max(...values) : null;
|
|
42550
|
+
};
|
|
42551
|
+
return { inputTokens: max(0), outputTokens: max(1), cachedTokens: max(2) };
|
|
42552
|
+
}
|
|
42553
|
+
function transcriptUsage(path, roots = transcriptRoots()) {
|
|
42554
|
+
if (!path) return EMPTY_USAGE;
|
|
42555
|
+
try {
|
|
42556
|
+
const real2 = realpathSync5(path);
|
|
42557
|
+
const safeRoots = roots.map((root) => realpathSync5(root));
|
|
42558
|
+
if (!safeRoots.some((root) => inside(real2, root))) return EMPTY_USAGE;
|
|
42559
|
+
const info = statSync4(real2);
|
|
42560
|
+
if (!info.isFile() || info.size > MAX_TRANSCRIPT_BYTES2) return EMPTY_USAGE;
|
|
42561
|
+
return usageFromText(readFileSync16(real2, "utf8"));
|
|
42562
|
+
} catch {
|
|
42563
|
+
return EMPTY_USAGE;
|
|
42564
|
+
}
|
|
42565
|
+
}
|
|
42566
|
+
async function startAgentInvocation(input, io = {}) {
|
|
42567
|
+
const startedAt = input.startedAt ?? /* @__PURE__ */ new Date();
|
|
42568
|
+
const existing = await prisma.agentInvocation.findUnique({
|
|
42569
|
+
where: { sessionId_runtimeInvocationId: {
|
|
42570
|
+
sessionId: input.sessionId,
|
|
42571
|
+
runtimeInvocationId: input.runtimeInvocationId
|
|
42572
|
+
} }
|
|
42573
|
+
});
|
|
42574
|
+
const row = await prisma.agentInvocation.upsert({
|
|
42575
|
+
where: { sessionId_runtimeInvocationId: {
|
|
42576
|
+
sessionId: input.sessionId,
|
|
42577
|
+
runtimeInvocationId: input.runtimeInvocationId
|
|
42578
|
+
} },
|
|
42579
|
+
update: {},
|
|
42580
|
+
create: {
|
|
42581
|
+
id: randomUUID10(),
|
|
42582
|
+
sessionId: input.sessionId,
|
|
42583
|
+
projectId: input.projectId,
|
|
42584
|
+
specId: input.specId ?? null,
|
|
42585
|
+
runtime: input.runtime,
|
|
42586
|
+
runtimeInvocationId: input.runtimeInvocationId,
|
|
42587
|
+
customAgentId: input.customAgentId ?? null,
|
|
42588
|
+
agentName: input.agentName,
|
|
42589
|
+
model: input.model ?? null,
|
|
42590
|
+
status: "running",
|
|
42591
|
+
startedAt
|
|
42592
|
+
}
|
|
42593
|
+
});
|
|
42594
|
+
const before2 = snapshot2(input.cwd, io.gitStatus);
|
|
42595
|
+
if (before2 !== null) snapshotHashes.set(keyOf(input), before2);
|
|
42596
|
+
return { row, duplicate: existing !== null };
|
|
42597
|
+
}
|
|
42598
|
+
async function stopAgentInvocation(input, io = {}) {
|
|
42599
|
+
const unique = { sessionId_runtimeInvocationId: {
|
|
42600
|
+
sessionId: input.sessionId,
|
|
42601
|
+
runtimeInvocationId: input.runtimeInvocationId
|
|
42602
|
+
} };
|
|
42603
|
+
const existing = await prisma.agentInvocation.findUnique({ where: unique });
|
|
42604
|
+
if (existing?.endedAt) return { row: existing, duplicate: true };
|
|
42605
|
+
const endedAt = input.endedAt ?? /* @__PURE__ */ new Date();
|
|
42606
|
+
const startedAt = existing?.startedAt ?? endedAt;
|
|
42607
|
+
const cleanResult = input.result === void 0 ? null : stripAnsi(input.result);
|
|
42608
|
+
const usage2 = transcriptUsage(input.transcriptPath, io.transcriptRoots);
|
|
42609
|
+
const before2 = snapshotHashes.get(keyOf(input));
|
|
42610
|
+
const after = snapshot2(input.cwd, io.gitStatus);
|
|
42611
|
+
snapshotHashes.delete(keyOf(input));
|
|
42612
|
+
const evidence = {
|
|
42613
|
+
// Stop tanpa start lazim setelah restart server di tengah invocation. Waktu start dan status
|
|
42614
|
+
// runtime sudah hilang; simpan baris sintetis yang dapat diaudit tanpa mengarang durasi 0 ms.
|
|
42615
|
+
status: existing ? input.status ?? "completed" : "completed",
|
|
42616
|
+
endedAt,
|
|
42617
|
+
durationMs: existing ? Math.max(0, endedAt.getTime() - startedAt.getTime()) : null,
|
|
42618
|
+
...usage2,
|
|
42619
|
+
resultExcerpt: cleanResult === null ? null : utf8Prefix(cleanResult, MAX_EXCERPT_BYTES),
|
|
42620
|
+
resultHash: cleanResult === null ? null : hash2(cleanResult),
|
|
42621
|
+
workspaceChanged: before2 !== void 0 && after !== null && before2 !== after
|
|
42622
|
+
};
|
|
42623
|
+
if (existing) {
|
|
42624
|
+
const row2 = await prisma.agentInvocation.update({ where: { id: existing.id }, data: evidence });
|
|
42625
|
+
return { row: row2, duplicate: false };
|
|
42626
|
+
}
|
|
42627
|
+
const row = await prisma.agentInvocation.create({
|
|
42628
|
+
data: {
|
|
42629
|
+
id: randomUUID10(),
|
|
42630
|
+
sessionId: input.sessionId,
|
|
42631
|
+
projectId: input.projectId,
|
|
42632
|
+
specId: input.specId ?? null,
|
|
42633
|
+
runtime: input.runtime,
|
|
42634
|
+
runtimeInvocationId: input.runtimeInvocationId,
|
|
42635
|
+
customAgentId: input.customAgentId ?? null,
|
|
42636
|
+
agentName: input.agentName,
|
|
42637
|
+
model: input.model ?? null,
|
|
42638
|
+
startedAt,
|
|
42639
|
+
...evidence
|
|
42640
|
+
}
|
|
42641
|
+
});
|
|
42642
|
+
return { row, duplicate: false };
|
|
42643
|
+
}
|
|
42644
|
+
async function reconcileAgentInvocations(liveSessionIds) {
|
|
42645
|
+
const live = new Set(liveSessionIds);
|
|
42646
|
+
const open5 = await prisma.agentInvocation.findMany({ where: { status: "running" } });
|
|
42647
|
+
let changed = 0;
|
|
42648
|
+
const endedAt = /* @__PURE__ */ new Date();
|
|
42649
|
+
for (const row of open5) {
|
|
42650
|
+
if (live.has(row.sessionId)) continue;
|
|
42651
|
+
await prisma.agentInvocation.update({
|
|
42652
|
+
where: { id: row.id },
|
|
42653
|
+
data: {
|
|
42654
|
+
status: "abandoned",
|
|
42655
|
+
endedAt,
|
|
42656
|
+
durationMs: Math.max(0, endedAt.getTime() - row.startedAt.getTime())
|
|
42657
|
+
}
|
|
42658
|
+
});
|
|
42659
|
+
changed++;
|
|
42660
|
+
}
|
|
42661
|
+
return changed;
|
|
42662
|
+
}
|
|
42663
|
+
var agentInvocationView = (row) => ({
|
|
42664
|
+
id: row.id,
|
|
42665
|
+
sessionId: row.sessionId,
|
|
42666
|
+
projectId: row.projectId,
|
|
42667
|
+
specId: row.specId,
|
|
42668
|
+
runtime: row.runtime === "codex" ? "codex" : "claude",
|
|
42669
|
+
customAgentId: row.customAgentId,
|
|
42670
|
+
agentName: row.agentName,
|
|
42671
|
+
model: row.model,
|
|
42672
|
+
status: row.status,
|
|
42673
|
+
startedAt: row.startedAt.toISOString(),
|
|
42674
|
+
endedAt: row.endedAt?.toISOString() ?? null,
|
|
42675
|
+
durationMs: row.durationMs,
|
|
42676
|
+
inputTokens: row.inputTokens,
|
|
42677
|
+
outputTokens: row.outputTokens,
|
|
42678
|
+
cachedTokens: row.cachedTokens,
|
|
42679
|
+
resultExcerpt: row.resultExcerpt,
|
|
42680
|
+
resultHash: row.resultHash,
|
|
42681
|
+
workspaceChanged: row.workspaceChanged,
|
|
42682
|
+
disposition: AGENT_DISPOSITIONS.includes(row.disposition) ? row.disposition : "pending",
|
|
42683
|
+
dispositionNote: row.dispositionNote,
|
|
42684
|
+
evaluatedAt: row.evaluatedAt?.toISOString() ?? null
|
|
42685
|
+
});
|
|
42686
|
+
var median = (values) => {
|
|
42687
|
+
if (values.length === 0) return null;
|
|
42688
|
+
values.sort((a, b) => a - b);
|
|
42689
|
+
const middle = Math.floor(values.length / 2);
|
|
42690
|
+
return values.length % 2 === 1 ? values[middle] : (values[middle - 1] + values[middle]) / 2;
|
|
42691
|
+
};
|
|
42692
|
+
var availableSum = (values) => {
|
|
42693
|
+
const known = values.filter((value) => value !== null);
|
|
42694
|
+
return known.length ? known.reduce((sum, value) => sum + value, 0) : null;
|
|
42695
|
+
};
|
|
42696
|
+
async function agentMetrics(query2) {
|
|
42697
|
+
const where = {
|
|
42698
|
+
...query2.projectId ? { projectId: query2.projectId } : {},
|
|
42699
|
+
...query2.from || query2.to ? { startedAt: {
|
|
42700
|
+
...query2.from ? { gte: query2.from } : {},
|
|
42701
|
+
...query2.to ? { lte: query2.to } : {}
|
|
42702
|
+
} } : {}
|
|
42703
|
+
};
|
|
42704
|
+
const rows = await prisma.agentInvocation.findMany({ where, orderBy: { startedAt: "desc" } });
|
|
42705
|
+
const groups = /* @__PURE__ */ new Map();
|
|
42706
|
+
for (const row of rows) groups.set(row.agentName, [...groups.get(row.agentName) ?? [], row]);
|
|
42707
|
+
const agents = [...groups.entries()].map(([agentName, invocations]) => {
|
|
42708
|
+
const dispositions = { pending: 0, accepted: 0, partial: 0, rejected: 0, falsePositive: 0 };
|
|
42709
|
+
for (const row of invocations) {
|
|
42710
|
+
if (row.disposition === "accepted") dispositions.accepted++;
|
|
42711
|
+
else if (row.disposition === "partial") dispositions.partial++;
|
|
42712
|
+
else if (row.disposition === "rejected") dispositions.rejected++;
|
|
42713
|
+
else if (row.disposition === "false-positive") dispositions.falsePositive++;
|
|
42714
|
+
else dispositions.pending++;
|
|
42715
|
+
}
|
|
42716
|
+
const evaluated = dispositions.accepted + dispositions.partial + dispositions.rejected + dispositions.falsePositive;
|
|
42717
|
+
return {
|
|
42718
|
+
agentName,
|
|
42719
|
+
invocationCount: invocations.length,
|
|
42720
|
+
medianDurationMs: median(invocations.flatMap((row) => row.durationMs === null ? [] : [row.durationMs])),
|
|
42721
|
+
inputTokens: availableSum(invocations.map((row) => row.inputTokens)),
|
|
42722
|
+
outputTokens: availableSum(invocations.map((row) => row.outputTokens)),
|
|
42723
|
+
cachedTokens: availableSum(invocations.map((row) => row.cachedTokens)),
|
|
42724
|
+
dispositions,
|
|
42725
|
+
operationalPrecision: evaluated ? (dispositions.accepted + dispositions.partial) / evaluated : null,
|
|
42726
|
+
workspaceChanged: invocations.some((row) => row.workspaceChanged)
|
|
42727
|
+
};
|
|
42728
|
+
}).sort((a, b) => a.agentName.localeCompare(b.agentName));
|
|
42729
|
+
return { agents, recent: rows.slice(0, 100).map(agentInvocationView) };
|
|
42730
|
+
}
|
|
42731
|
+
async function updateAgentInvocationDisposition(id, disposition, note) {
|
|
42732
|
+
const exists = await prisma.agentInvocation.findUnique({ where: { id } });
|
|
42733
|
+
if (!exists) return null;
|
|
42734
|
+
const row = await prisma.agentInvocation.update({
|
|
42735
|
+
where: { id },
|
|
42736
|
+
data: {
|
|
42737
|
+
disposition,
|
|
42738
|
+
dispositionNote: note?.trim() || null,
|
|
42739
|
+
evaluatedAt: /* @__PURE__ */ new Date()
|
|
42740
|
+
}
|
|
42741
|
+
});
|
|
42742
|
+
return agentInvocationView(row);
|
|
42743
|
+
}
|
|
42744
|
+
|
|
42745
|
+
// src/routes/session-events.ts
|
|
41476
42746
|
var bearer = (h) => /^Bearer (.+)$/.exec(h ?? "")?.[1] ?? "";
|
|
42747
|
+
var recordOf = (value) => value && typeof value === "object" ? value : null;
|
|
42748
|
+
var boundedString = (value, max) => typeof value === "string" && value.length > 0 && value.length <= max ? value : void 0;
|
|
41477
42749
|
async function session_events_default(app2) {
|
|
41478
42750
|
app2.post("/session-events", async (req, reply) => {
|
|
41479
42751
|
const sessionId2 = String(req.headers["x-hanoman-session"] ?? "");
|
|
@@ -41482,6 +42754,38 @@ async function session_events_default(app2) {
|
|
|
41482
42754
|
return reply.code(401).send({ error: "unauthorized" });
|
|
41483
42755
|
const s2 = await getSessionAsync(sessionId2);
|
|
41484
42756
|
if (!s2 || s2.exited) return reply.code(404).send({ error: "live session not found" });
|
|
42757
|
+
const body = recordOf(req.body);
|
|
42758
|
+
const lifecycle = body?.hook_event_name;
|
|
42759
|
+
if (body && (lifecycle === "SubagentStart" || lifecycle === "SubagentStop")) {
|
|
42760
|
+
const runtimeInvocationId = boundedString(
|
|
42761
|
+
body.agent_id ?? body.subagent_id ?? body.thread_id,
|
|
42762
|
+
500
|
|
42763
|
+
);
|
|
42764
|
+
const agentName = boundedString(body.agent_type ?? body.agent_name, 200);
|
|
42765
|
+
const meta = agentName ? (s2.agentRoster ?? []).find((agent) => agent.name === agentName) : void 0;
|
|
42766
|
+
if (!runtimeInvocationId || !meta) return reply.code(202).send({ ignored: true });
|
|
42767
|
+
const identity = {
|
|
42768
|
+
sessionId: sessionId2,
|
|
42769
|
+
projectId: s2.projectId,
|
|
42770
|
+
specId: s2.specId,
|
|
42771
|
+
runtime: s2.agent,
|
|
42772
|
+
runtimeInvocationId,
|
|
42773
|
+
customAgentId: meta.id,
|
|
42774
|
+
agentName: meta.name,
|
|
42775
|
+
model: meta.model,
|
|
42776
|
+
cwd: s2.cwd
|
|
42777
|
+
};
|
|
42778
|
+
const outcome = lifecycle === "SubagentStart" ? await startAgentInvocation(identity) : await stopAgentInvocation({
|
|
42779
|
+
...identity,
|
|
42780
|
+
status: body.status === "interrupted" ? "interrupted" : "completed",
|
|
42781
|
+
result: boundedString(body.last_assistant_message ?? body.result, 1e6),
|
|
42782
|
+
transcriptPath: boundedString(
|
|
42783
|
+
body.agent_transcript_path ?? body.transcript_path,
|
|
42784
|
+
4096
|
|
42785
|
+
)
|
|
42786
|
+
});
|
|
42787
|
+
return reply.code(202).send(outcome.duplicate ? { duplicate: true } : { accepted: true });
|
|
42788
|
+
}
|
|
41485
42789
|
const event = parseHookEvent(req.body);
|
|
41486
42790
|
if (!event) return reply.code(202).send({ ignored: true });
|
|
41487
42791
|
const r = await intakeAsk({
|
|
@@ -41575,9 +42879,9 @@ init_db();
|
|
|
41575
42879
|
init_db();
|
|
41576
42880
|
init_uploads();
|
|
41577
42881
|
init_src();
|
|
41578
|
-
import { createHash as
|
|
42882
|
+
import { createHash as createHash10, randomBytes as randomBytes6 } from "node:crypto";
|
|
41579
42883
|
function hashAccessKey(key) {
|
|
41580
|
-
return
|
|
42884
|
+
return createHash10("sha256").update(key).digest("hex");
|
|
41581
42885
|
}
|
|
41582
42886
|
function generateAccessKey() {
|
|
41583
42887
|
const key = "hnm_tkt_" + randomBytes6(24).toString("hex");
|
|
@@ -41587,13 +42891,13 @@ function generateShareToken() {
|
|
|
41587
42891
|
return "hnm_shr_" + randomBytes6(24).toString("hex");
|
|
41588
42892
|
}
|
|
41589
42893
|
async function createTicket(input) {
|
|
41590
|
-
const { key, hash:
|
|
42894
|
+
const { key, hash: hash3 } = generateAccessKey();
|
|
41591
42895
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
41592
42896
|
const max = await prisma.ticket.aggregate({ where: { projectId: input.projectId }, _max: { number: true } });
|
|
41593
42897
|
const number = (max._max.number ?? 0) + 1;
|
|
41594
42898
|
try {
|
|
41595
42899
|
const ticket = await prisma.ticket.create({
|
|
41596
|
-
data: { ...input, number, accessKeyHash:
|
|
42900
|
+
data: { ...input, number, accessKeyHash: hash3, shareToken: generateShareToken(), status: "new" }
|
|
41597
42901
|
});
|
|
41598
42902
|
return { ticket, key };
|
|
41599
42903
|
} catch (e) {
|
|
@@ -41620,6 +42924,7 @@ async function pruneOldTickets(now = Date.now()) {
|
|
|
41620
42924
|
// src/services/ticket-intake.ts
|
|
41621
42925
|
init_db();
|
|
41622
42926
|
init_notifications2();
|
|
42927
|
+
init_sync_notify();
|
|
41623
42928
|
init_uploads();
|
|
41624
42929
|
var TICKET_UPLOAD = { MAX_FILES: 3 };
|
|
41625
42930
|
async function parseTicketUpload(req) {
|
|
@@ -41783,10 +43088,12 @@ async function help_default(app2) {
|
|
|
41783
43088
|
|
|
41784
43089
|
// src/routes/tickets.ts
|
|
41785
43090
|
init_db();
|
|
43091
|
+
init_sync_notify();
|
|
41786
43092
|
init_uploads();
|
|
41787
43093
|
|
|
41788
43094
|
// src/services/ticket-accept.ts
|
|
41789
43095
|
init_db();
|
|
43096
|
+
init_sync_notify();
|
|
41790
43097
|
var attachmentData = (t, atts) => {
|
|
41791
43098
|
if (atts.length === 0) return "Tanpa lampiran.";
|
|
41792
43099
|
const list2 = atts.map(
|
|
@@ -42701,201 +44008,28 @@ async function changelog_default(app2) {
|
|
|
42701
44008
|
// src/routes/custom-agents.ts
|
|
42702
44009
|
init_src();
|
|
42703
44010
|
init_db();
|
|
42704
|
-
|
|
42705
|
-
|
|
42706
|
-
init_src();
|
|
42707
|
-
import { readFileSync as readFileSync16 } from "node:fs";
|
|
42708
|
-
import { homedir as homedir9 } from "node:os";
|
|
42709
|
-
import { join as join26 } from "node:path";
|
|
42710
|
-
var home = () => process.env.HOME || homedir9();
|
|
42711
|
-
var readJson2 = (path) => {
|
|
42712
|
-
try {
|
|
42713
|
-
return JSON.parse(readFileSync16(path, "utf8"));
|
|
42714
|
-
} catch {
|
|
42715
|
-
return null;
|
|
42716
|
-
}
|
|
42717
|
-
};
|
|
42718
|
-
var serversOf = (node) => {
|
|
42719
|
-
const ms = node?.mcpServers;
|
|
42720
|
-
if (!ms || typeof ms !== "object" || Array.isArray(ms)) return [];
|
|
42721
|
-
return Object.keys(ms);
|
|
42722
|
-
};
|
|
42723
|
-
var codexServers = () => {
|
|
42724
|
-
let text;
|
|
42725
|
-
try {
|
|
42726
|
-
text = readFileSync16(join26(home(), ".codex", "config.toml"), "utf8");
|
|
42727
|
-
} catch {
|
|
42728
|
-
return [];
|
|
42729
|
-
}
|
|
42730
|
-
const out4 = [];
|
|
42731
|
-
for (const m of text.matchAll(/^\s*\[mcp_servers\.(?:"([^"]+)"|([A-Za-z0-9_-]+))(?:\.[^\]]*)?\]/gm)) {
|
|
42732
|
-
const name2 = m[1] ?? m[2];
|
|
42733
|
-
if (name2) out4.push(name2);
|
|
42734
|
-
}
|
|
42735
|
-
return out4;
|
|
42736
|
-
};
|
|
42737
|
-
function mcpServerNames(repoDir) {
|
|
42738
|
-
const names = [];
|
|
42739
|
-
const claudeJson = readJson2(join26(home(), ".claude.json"));
|
|
42740
|
-
names.push(...serversOf(claudeJson));
|
|
42741
|
-
if (repoDir) {
|
|
42742
|
-
const projects = claudeJson?.projects;
|
|
42743
|
-
if (projects && typeof projects === "object") names.push(...serversOf(projects[repoDir]));
|
|
42744
|
-
names.push(...serversOf(readJson2(join26(repoDir, ".mcp.json"))));
|
|
42745
|
-
}
|
|
42746
|
-
names.push(...codexServers());
|
|
42747
|
-
return [...new Set(names.filter(Boolean))].sort((a, b) => a.localeCompare(b));
|
|
42748
|
-
}
|
|
42749
|
-
function agentToolCatalog(repoDir) {
|
|
42750
|
-
return [ALL_TOOLS_ENTRY, ...BUILTIN_AGENT_TOOLS, ...mcpServerNames(repoDir).map(mcpToolEntry)];
|
|
42751
|
-
}
|
|
42752
|
-
var agentToolIds = (repoDir) => agentToolCatalog(repoDir).map((t) => t.id);
|
|
42753
|
-
|
|
42754
|
-
// src/routes/custom-agents.ts
|
|
42755
|
-
init_settings3();
|
|
42756
|
-
|
|
42757
|
-
// src/services/builtin-agents.ts
|
|
42758
|
-
init_src();
|
|
42759
|
-
init_db();
|
|
44011
|
+
init_sync_notify();
|
|
44012
|
+
init_agent_tool_catalog();
|
|
42760
44013
|
init_settings3();
|
|
42761
|
-
|
|
42762
|
-
|
|
42763
|
-
var fingerprint = (name2, description, instructions, tools) => createHash9("sha256").update([name2, description, instructions, [...tools].join(",")].join(" ")).digest("hex").slice(0, 16);
|
|
42764
|
-
var builtinFingerprint = (a) => fingerprint(a.name, a.description, a.instructions, a.tools);
|
|
42765
|
-
var rowFingerprint = (r) => fingerprint(r.name, r.description, r.instructions, toolsOf(r.tools) ?? []);
|
|
42766
|
-
async function seedBuiltinAgents() {
|
|
42767
|
-
try {
|
|
42768
|
-
const setting = await getSetting();
|
|
42769
|
-
const stamps = { ...setting.builtinAgents };
|
|
42770
|
-
let changed = false;
|
|
42771
|
-
for (const a of BUILTIN_AGENTS) {
|
|
42772
|
-
const id = customAgentId(null, a.name);
|
|
42773
|
-
const fp = builtinFingerprint(a);
|
|
42774
|
-
const row = await prisma.customAgent.findUnique({ where: { id } });
|
|
42775
|
-
if (!row) {
|
|
42776
|
-
if (await findTombstone("customAgent", id)) continue;
|
|
42777
|
-
await prisma.customAgent.create({ data: {
|
|
42778
|
-
id,
|
|
42779
|
-
projectId: null,
|
|
42780
|
-
name: a.name,
|
|
42781
|
-
description: a.description,
|
|
42782
|
-
instructions: a.instructions,
|
|
42783
|
-
tools: [...a.tools],
|
|
42784
|
-
model: null,
|
|
42785
|
-
mentions: [],
|
|
42786
|
-
runtime: null,
|
|
42787
|
-
enabled: a.enabledByDefault
|
|
42788
|
-
} });
|
|
42789
|
-
await notifySynced("customAgent", id);
|
|
42790
|
-
stamps[a.name] = fp;
|
|
42791
|
-
changed = true;
|
|
42792
|
-
continue;
|
|
42793
|
-
}
|
|
42794
|
-
const stamped = stamps[a.name];
|
|
42795
|
-
if (!stamped || stamped === fp) continue;
|
|
42796
|
-
if (stamped !== rowFingerprint(row)) continue;
|
|
42797
|
-
await prisma.customAgent.update({ where: { id }, data: {
|
|
42798
|
-
description: a.description,
|
|
42799
|
-
instructions: a.instructions,
|
|
42800
|
-
tools: [...a.tools]
|
|
42801
|
-
// `enabled` TIDAK di sini. Sengaja.
|
|
42802
|
-
} });
|
|
42803
|
-
await notifySynced("customAgent", id);
|
|
42804
|
-
stamps[a.name] = fp;
|
|
42805
|
-
changed = true;
|
|
42806
|
-
}
|
|
42807
|
-
if (changed) {
|
|
42808
|
-
const data = { ...setting, builtinAgents: stamps };
|
|
42809
|
-
await prisma.setting.upsert({
|
|
42810
|
-
where: { id: 1 },
|
|
42811
|
-
update: { data },
|
|
42812
|
-
create: { id: 1, data }
|
|
42813
|
-
});
|
|
42814
|
-
}
|
|
42815
|
-
} catch {
|
|
42816
|
-
}
|
|
42817
|
-
}
|
|
42818
|
-
|
|
42819
|
-
// src/services/custom-agents.ts
|
|
42820
|
-
init_db();
|
|
42821
|
-
init_src();
|
|
42822
|
-
init_pty();
|
|
42823
|
-
var cache4 = [];
|
|
42824
|
-
var repoDirCache = /* @__PURE__ */ new Map();
|
|
42825
|
-
var asCustomAgent = (r) => ({
|
|
42826
|
-
id: r.id,
|
|
42827
|
-
projectId: r.projectId,
|
|
42828
|
-
name: r.name,
|
|
42829
|
-
description: r.description,
|
|
42830
|
-
instructions: r.instructions,
|
|
42831
|
-
tools: toolsOf(r.tools),
|
|
42832
|
-
model: r.model,
|
|
42833
|
-
mentions: mentionsOf(r.mentions),
|
|
42834
|
-
runtime: runtimeOf(r.runtime),
|
|
42835
|
-
enabled: r.enabled,
|
|
42836
|
-
createdAt: "",
|
|
42837
|
-
updatedAt: ""
|
|
42838
|
-
// tak dipakai lapis ini
|
|
42839
|
-
});
|
|
42840
|
-
async function loadCustomAgents() {
|
|
42841
|
-
try {
|
|
42842
|
-
cache4 = await prisma.customAgent.findMany();
|
|
42843
|
-
const projects = await prisma.project.findMany({ select: { id: true, repoDir: true } });
|
|
42844
|
-
const bindings = await prisma.localBinding.findMany({ select: { projectId: true, repoDir: true } });
|
|
42845
|
-
const next = /* @__PURE__ */ new Map();
|
|
42846
|
-
for (const p3 of projects) next.set(p3.id, p3.repoDir ?? null);
|
|
42847
|
-
for (const b of bindings) next.set(b.projectId, b.repoDir ?? null);
|
|
42848
|
-
repoDirCache = next;
|
|
42849
|
-
} catch {
|
|
42850
|
-
cache4 = [];
|
|
42851
|
-
repoDirCache = /* @__PURE__ */ new Map();
|
|
42852
|
-
}
|
|
42853
|
-
}
|
|
42854
|
-
function agentDefsFor(projectId, agent) {
|
|
42855
|
-
const globals = cache4.filter((r) => r.projectId === null).map(asCustomAgent);
|
|
42856
|
-
const project = cache4.filter((r) => r.projectId === projectId).map(asCustomAgent);
|
|
42857
|
-
const eff = effectiveAgents(globals, project).filter((a) => a.runtime === null || a.runtime === agent);
|
|
42858
|
-
const needsCatalog = eff.some((a) => (a.tools ?? []).includes(ALL_TOOLS));
|
|
42859
|
-
const catalogIds = needsCatalog ? agentToolIds(repoDirCache.get(projectId) ?? null) : [];
|
|
42860
|
-
return eff.map((a) => ({
|
|
42861
|
-
name: a.name,
|
|
42862
|
-
description: a.description,
|
|
42863
|
-
instructions: a.instructions,
|
|
42864
|
-
// Ekspansi terjadi DI SINI, sebelum `resolveTools` di runner: meneruskan `"*"` apa adanya
|
|
42865
|
-
// membuat claude membuangnya senyap (agen tanpa alat), sementara menerjemahkannya jadi `null`
|
|
42866
|
-
// membuat agen mewarisi SELURUH tool termasuk `Task` — lapis 2 anti-loop lenyap tanpa jejak.
|
|
42867
|
-
tools: expandTools(a.tools, catalogIds),
|
|
42868
|
-
model: a.model,
|
|
42869
|
-
mentions: a.mentions ?? []
|
|
42870
|
-
}));
|
|
42871
|
-
}
|
|
42872
|
-
function validateGraph(rows) {
|
|
42873
|
-
const projectScopes = [...new Set(rows.map((r) => r.projectId).filter((p3) => p3 !== null))];
|
|
42874
|
-
const globals = rows.filter((r) => r.projectId === null).map(asCustomAgent);
|
|
42875
|
-
for (const scope of [null, ...projectScopes]) {
|
|
42876
|
-
const project = scope === null ? [] : rows.filter((r) => r.projectId === scope).map(asCustomAgent);
|
|
42877
|
-
const nodes = effectiveAgents(globals, project).map((a) => ({ name: a.name, mentions: a.mentions ?? [] }));
|
|
42878
|
-
const cycle = detectCycle(nodes);
|
|
42879
|
-
if (cycle) return { scope: scope ?? GLOBAL_SCOPE, cycle };
|
|
42880
|
-
}
|
|
42881
|
-
return null;
|
|
42882
|
-
}
|
|
42883
|
-
function unknownMentions(row, all) {
|
|
42884
|
-
const visible = new Set(
|
|
42885
|
-
all.filter((r) => r.projectId === null || row.projectId !== null && r.projectId === row.projectId).map((r) => r.name)
|
|
42886
|
-
);
|
|
42887
|
-
return mentionsOf(row.mentions).filter((m) => !visible.has(m));
|
|
42888
|
-
}
|
|
42889
|
-
async function installCustomAgents() {
|
|
42890
|
-
await seedBuiltinAgents();
|
|
42891
|
-
await loadCustomAgents();
|
|
42892
|
-
registerCustomAgentSource((projectId, agent) => agentDefsFor(projectId, agent));
|
|
42893
|
-
}
|
|
42894
|
-
|
|
42895
|
-
// src/routes/custom-agents.ts
|
|
44014
|
+
init_builtin_agents2();
|
|
44015
|
+
init_custom_agents2();
|
|
42896
44016
|
var rowsOf = async () => await prisma.customAgent.findMany();
|
|
42897
44017
|
var stampsOf = async () => (await getSetting()).builtinAgents;
|
|
42898
|
-
var
|
|
44018
|
+
var availabilityOf = (r, requestedRuntime) => {
|
|
44019
|
+
const configuredRuntime = runtimeOf(r.runtime);
|
|
44020
|
+
if (requestedRuntime && configuredRuntime && configuredRuntime !== requestedRuntime) {
|
|
44021
|
+
return { available: false, availabilityReason: `hanya tersedia untuk runtime ${configuredRuntime}` };
|
|
44022
|
+
}
|
|
44023
|
+
const effectiveRuntime = requestedRuntime ?? configuredRuntime;
|
|
44024
|
+
if (workspacePolicyOf(r.workspacePolicy) === "isolated-worktree" && effectiveRuntime === "codex") {
|
|
44025
|
+
return {
|
|
44026
|
+
available: false,
|
|
44027
|
+
availabilityReason: "isolated-worktree belum tersedia untuk subagent Codex"
|
|
44028
|
+
};
|
|
44029
|
+
}
|
|
44030
|
+
return { available: true };
|
|
44031
|
+
};
|
|
44032
|
+
var view8 = (r, projectId, stamps = {}, requestedRuntime) => {
|
|
42899
44033
|
const builtin = r.projectId === null && BUILTIN_AGENT_NAMES.includes(r.name);
|
|
42900
44034
|
return {
|
|
42901
44035
|
id: r.id,
|
|
@@ -42907,12 +44041,18 @@ var view8 = (r, projectId, stamps = {}) => {
|
|
|
42907
44041
|
model: r.model,
|
|
42908
44042
|
mentions: mentionsOf(r.mentions),
|
|
42909
44043
|
runtime: runtimeOf(r.runtime),
|
|
44044
|
+
activation: activationOf(r.activation),
|
|
44045
|
+
effort: effortOf(r.effort),
|
|
44046
|
+
workspacePolicy: workspacePolicyOf(r.workspacePolicy),
|
|
44047
|
+
maxTurns: maxTurnsOf(r.maxTurns),
|
|
44048
|
+
timeoutSeconds: timeoutSecondsOf(r.timeoutSeconds),
|
|
42910
44049
|
enabled: r.enabled,
|
|
42911
44050
|
builtin,
|
|
42912
44051
|
// Sidik jari yang tak tercatat (baris menyeberang sync dari mesin lain, seed di sini belum
|
|
42913
44052
|
// pernah menyentuhnya) dibaca sebagai "disunting" — lebih baik menandai berlebih daripada
|
|
42914
44053
|
// menjanjikan "asli bawaan" untuk isi yang tak bisa kita buktikan.
|
|
42915
44054
|
builtinEdited: builtin ? stamps[r.name] !== rowFingerprint(r) : false,
|
|
44055
|
+
...availabilityOf(r, requestedRuntime),
|
|
42916
44056
|
...projectId ? { inherited: r.projectId === null } : {}
|
|
42917
44057
|
};
|
|
42918
44058
|
};
|
|
@@ -42930,6 +44070,11 @@ function modelProblem(model, runtime) {
|
|
|
42930
44070
|
const ok2 = modelsForRuntime(runtime).some((m) => m.id === model);
|
|
42931
44071
|
return ok2 ? null : { error: "model tak dikenal untuk runtime ini", model, runtime };
|
|
42932
44072
|
}
|
|
44073
|
+
function effortProblem(effort, runtime, model) {
|
|
44074
|
+
if (!effort) return null;
|
|
44075
|
+
const ok2 = effortsForRuntimeModel(runtime, model).includes(effort);
|
|
44076
|
+
return ok2 ? null : { error: "effort tak didukung runtime/model ini", effort, runtime, model };
|
|
44077
|
+
}
|
|
42933
44078
|
async function custom_agents_default(app2) {
|
|
42934
44079
|
app2.get("/custom-agents/catalog", async (req) => {
|
|
42935
44080
|
const projectId = req.query.projectId;
|
|
@@ -42939,8 +44084,13 @@ async function custom_agents_default(app2) {
|
|
|
42939
44084
|
runtimes: AGENT_RUNTIMES.map((id) => ({ id, label: AGENT_RUNTIME_LABELS[id] }))
|
|
42940
44085
|
};
|
|
42941
44086
|
});
|
|
42942
|
-
app2.get("/custom-agents", async (req) => {
|
|
42943
|
-
const
|
|
44087
|
+
app2.get("/custom-agents", async (req, reply) => {
|
|
44088
|
+
const query2 = req.query;
|
|
44089
|
+
const projectId = query2.projectId;
|
|
44090
|
+
const requestedRuntime = query2.runtime;
|
|
44091
|
+
if (requestedRuntime && !AGENT_RUNTIMES.includes(requestedRuntime)) {
|
|
44092
|
+
return reply.code(400).send({ error: "runtime harus claude atau codex" });
|
|
44093
|
+
}
|
|
42944
44094
|
const rows = await prisma.customAgent.findMany({
|
|
42945
44095
|
where: projectId ? { OR: [{ projectId: null }, { projectId }] } : { projectId: null },
|
|
42946
44096
|
orderBy: { name: "asc" }
|
|
@@ -42949,7 +44099,7 @@ async function custom_agents_default(app2) {
|
|
|
42949
44099
|
for (const r of rows) if (r.projectId === null) byName.set(r.name, r);
|
|
42950
44100
|
for (const r of rows) if (r.projectId !== null) byName.set(r.name, r);
|
|
42951
44101
|
const stamps = await stampsOf();
|
|
42952
|
-
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name)).map((r) => view8(r, projectId, stamps));
|
|
44102
|
+
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name)).map((r) => view8(r, projectId, stamps, requestedRuntime));
|
|
42953
44103
|
});
|
|
42954
44104
|
app2.post("/custom-agents", async (req, reply) => {
|
|
42955
44105
|
const parsed = zCreateCustomAgent.safeParse(req.body);
|
|
@@ -42962,6 +44112,8 @@ async function custom_agents_default(app2) {
|
|
|
42962
44112
|
if (tp) return reply.code(400).send(tp);
|
|
42963
44113
|
const mp = modelProblem(p3.model ?? null, p3.runtime ?? null);
|
|
42964
44114
|
if (mp) return reply.code(400).send(mp);
|
|
44115
|
+
const ep = effortProblem(p3.effort ?? null, p3.runtime ?? null, p3.model ?? null);
|
|
44116
|
+
if (ep) return reply.code(400).send(ep);
|
|
42965
44117
|
const id = customAgentId(projectId, p3.name);
|
|
42966
44118
|
if (await prisma.customAgent.findUnique({ where: { id } }))
|
|
42967
44119
|
return reply.code(409).send({ error: "nama sudah dipakai di scope ini", id });
|
|
@@ -42975,6 +44127,11 @@ async function custom_agents_default(app2) {
|
|
|
42975
44127
|
model: p3.model ?? null,
|
|
42976
44128
|
mentions: p3.mentions ?? [],
|
|
42977
44129
|
runtime: p3.runtime ?? null,
|
|
44130
|
+
activation: p3.activation ?? "always",
|
|
44131
|
+
effort: p3.effort ?? null,
|
|
44132
|
+
workspacePolicy: p3.workspacePolicy ?? "inherit",
|
|
44133
|
+
maxTurns: p3.maxTurns ?? null,
|
|
44134
|
+
timeoutSeconds: p3.timeoutSeconds ?? null,
|
|
42978
44135
|
enabled: p3.enabled ?? true
|
|
42979
44136
|
};
|
|
42980
44137
|
const all = [...await rowsOf(), candidate];
|
|
@@ -42992,6 +44149,11 @@ async function custom_agents_default(app2) {
|
|
|
42992
44149
|
model: candidate.model,
|
|
42993
44150
|
mentions: candidate.mentions,
|
|
42994
44151
|
runtime: candidate.runtime,
|
|
44152
|
+
activation: candidate.activation,
|
|
44153
|
+
effort: candidate.effort,
|
|
44154
|
+
workspacePolicy: candidate.workspacePolicy,
|
|
44155
|
+
maxTurns: candidate.maxTurns,
|
|
44156
|
+
timeoutSeconds: candidate.timeoutSeconds,
|
|
42995
44157
|
enabled: candidate.enabled
|
|
42996
44158
|
} });
|
|
42997
44159
|
await loadCustomAgents();
|
|
@@ -43009,6 +44171,12 @@ async function custom_agents_default(app2) {
|
|
|
43009
44171
|
if (!existing) return reply.code(404).send({ error: "not found" });
|
|
43010
44172
|
const before2 = existing;
|
|
43011
44173
|
const effRuntime = "runtime" in parsed.data ? parsed.data.runtime ?? null : runtimeOf(before2.runtime);
|
|
44174
|
+
const effWorkspacePolicy = "workspacePolicy" in parsed.data ? parsed.data.workspacePolicy ?? "inherit" : workspacePolicyOf(before2.workspacePolicy);
|
|
44175
|
+
if (("runtime" in parsed.data || "workspacePolicy" in parsed.data) && effWorkspacePolicy === "isolated-worktree" && effRuntime !== "claude") {
|
|
44176
|
+
return reply.code(400).send({
|
|
44177
|
+
error: "isolated-worktree hanya tersedia untuk agen ber-runtime Claude Code"
|
|
44178
|
+
});
|
|
44179
|
+
}
|
|
43012
44180
|
if (parsed.data.tools !== void 0) {
|
|
43013
44181
|
const tp = toolsProblem(parsed.data.tools, agentToolIds(await repoDirOf2(before2.projectId)));
|
|
43014
44182
|
if (tp) return reply.code(400).send(tp);
|
|
@@ -43020,12 +44188,25 @@ async function custom_agents_default(app2) {
|
|
|
43020
44188
|
);
|
|
43021
44189
|
if (mp) return reply.code(400).send(mp);
|
|
43022
44190
|
}
|
|
44191
|
+
if (parsed.data.effort !== void 0 || parsed.data.model !== void 0 || "runtime" in parsed.data) {
|
|
44192
|
+
const ep = effortProblem(
|
|
44193
|
+
parsed.data.effort !== void 0 ? parsed.data.effort : effortOf(before2.effort),
|
|
44194
|
+
effRuntime,
|
|
44195
|
+
parsed.data.model !== void 0 ? parsed.data.model : before2.model
|
|
44196
|
+
);
|
|
44197
|
+
if (ep) return reply.code(400).send(ep);
|
|
44198
|
+
}
|
|
43023
44199
|
const candidate = {
|
|
43024
44200
|
...before2,
|
|
43025
44201
|
...parsed.data,
|
|
43026
44202
|
mentions: parsed.data.mentions ?? mentionsOf(before2.mentions),
|
|
43027
44203
|
tools: parsed.data.tools !== void 0 ? parsed.data.tools : toolsOf(before2.tools),
|
|
43028
|
-
runtime: effRuntime
|
|
44204
|
+
runtime: effRuntime,
|
|
44205
|
+
activation: parsed.data.activation ?? activationOf(before2.activation),
|
|
44206
|
+
effort: parsed.data.effort !== void 0 ? parsed.data.effort : effortOf(before2.effort),
|
|
44207
|
+
workspacePolicy: effWorkspacePolicy,
|
|
44208
|
+
maxTurns: parsed.data.maxTurns !== void 0 ? parsed.data.maxTurns : maxTurnsOf(before2.maxTurns),
|
|
44209
|
+
timeoutSeconds: parsed.data.timeoutSeconds !== void 0 ? parsed.data.timeoutSeconds : timeoutSecondsOf(before2.timeoutSeconds)
|
|
43029
44210
|
};
|
|
43030
44211
|
const all = (await rowsOf()).map((r) => r.id === id ? candidate : r);
|
|
43031
44212
|
const unknown = unknownMentions(candidate, all);
|
|
@@ -43039,6 +44220,11 @@ async function custom_agents_default(app2) {
|
|
|
43039
44220
|
model: candidate.model,
|
|
43040
44221
|
mentions: candidate.mentions,
|
|
43041
44222
|
runtime: candidate.runtime,
|
|
44223
|
+
activation: candidate.activation,
|
|
44224
|
+
effort: candidate.effort,
|
|
44225
|
+
workspacePolicy: candidate.workspacePolicy,
|
|
44226
|
+
maxTurns: candidate.maxTurns,
|
|
44227
|
+
timeoutSeconds: candidate.timeoutSeconds,
|
|
43042
44228
|
enabled: candidate.enabled
|
|
43043
44229
|
} });
|
|
43044
44230
|
await loadCustomAgents();
|
|
@@ -43065,9 +44251,39 @@ async function custom_agents_default(app2) {
|
|
|
43065
44251
|
});
|
|
43066
44252
|
}
|
|
43067
44253
|
|
|
44254
|
+
// src/routes/custom-agent-metrics.ts
|
|
44255
|
+
init_zod();
|
|
44256
|
+
var zQuery = external_exports.object({
|
|
44257
|
+
projectId: external_exports.string().min(1).optional(),
|
|
44258
|
+
from: external_exports.string().datetime({ offset: true }).optional(),
|
|
44259
|
+
to: external_exports.string().datetime({ offset: true }).optional()
|
|
44260
|
+
});
|
|
44261
|
+
var zPatch = external_exports.object({
|
|
44262
|
+
disposition: external_exports.enum(["accepted", "partial", "rejected", "false-positive"]),
|
|
44263
|
+
note: external_exports.string().max(500).nullable().optional()
|
|
44264
|
+
}).strict();
|
|
44265
|
+
async function custom_agent_metrics_default(app2) {
|
|
44266
|
+
app2.get("/custom-agents/metrics", async (req, reply) => {
|
|
44267
|
+
const parsed = zQuery.safeParse(req.query);
|
|
44268
|
+
if (!parsed.success) return reply.code(400).send({ error: parsed.error.issues[0]?.message });
|
|
44269
|
+
const from = parsed.data.from ? new Date(parsed.data.from) : void 0;
|
|
44270
|
+
const to = parsed.data.to ? new Date(parsed.data.to) : void 0;
|
|
44271
|
+
if (from && to && from > to) return reply.code(400).send({ error: "from harus sebelum to" });
|
|
44272
|
+
return agentMetrics({ projectId: parsed.data.projectId, from, to });
|
|
44273
|
+
});
|
|
44274
|
+
app2.patch("/custom-agents/invocations/:id", async (req, reply) => {
|
|
44275
|
+
const body = zPatch.safeParse(req.body);
|
|
44276
|
+
if (!body.success) return reply.code(400).send({ error: body.error.issues[0]?.message });
|
|
44277
|
+
const id = String(req.params.id ?? "");
|
|
44278
|
+
const view13 = await updateAgentInvocationDisposition(id, body.data.disposition, body.data.note);
|
|
44279
|
+
return view13 ?? reply.code(404).send({ error: "invocation tidak ditemukan" });
|
|
44280
|
+
});
|
|
44281
|
+
}
|
|
44282
|
+
|
|
43068
44283
|
// src/routes/members.ts
|
|
43069
44284
|
init_src();
|
|
43070
44285
|
init_db();
|
|
44286
|
+
init_sync_notify();
|
|
43071
44287
|
var view9 = (m) => ({
|
|
43072
44288
|
id: m.id,
|
|
43073
44289
|
name: m.name,
|
|
@@ -43124,10 +44340,12 @@ async function members_default(app2) {
|
|
|
43124
44340
|
// src/routes/tasks.ts
|
|
43125
44341
|
init_src();
|
|
43126
44342
|
init_db();
|
|
44343
|
+
init_sync_notify();
|
|
43127
44344
|
|
|
43128
44345
|
// src/services/task-escalate.ts
|
|
43129
44346
|
init_src();
|
|
43130
44347
|
init_db();
|
|
44348
|
+
init_sync_notify();
|
|
43131
44349
|
var day = (d) => d ? d.toISOString().slice(0, 10) : null;
|
|
43132
44350
|
function contextOf(task, member, backlink) {
|
|
43133
44351
|
const lines2 = [
|
|
@@ -43408,7 +44626,7 @@ function issuesFromRest(raw) {
|
|
|
43408
44626
|
}
|
|
43409
44627
|
var GH_FIELDS = "number,title,body,author,labels,url,state,createdAt,updatedAt";
|
|
43410
44628
|
var API2 = "https://api.github.com";
|
|
43411
|
-
var defaultRunGh = (args, env) => new Promise((
|
|
44629
|
+
var defaultRunGh = (args, env) => new Promise((resolve22, reject2) => {
|
|
43412
44630
|
execFile16(
|
|
43413
44631
|
args[0],
|
|
43414
44632
|
args.slice(1),
|
|
@@ -43416,7 +44634,7 @@ var defaultRunGh = (args, env) => new Promise((resolve21, reject2) => {
|
|
|
43416
44634
|
(err, stdout, stderr) => {
|
|
43417
44635
|
const e = err;
|
|
43418
44636
|
if (e && (e.code === "ENOENT" || e.code === "EACCES")) return reject2(e);
|
|
43419
|
-
|
|
44637
|
+
resolve22({ code: err ? Number(err.code ?? 1) : 0, stdout, stderr });
|
|
43420
44638
|
}
|
|
43421
44639
|
);
|
|
43422
44640
|
});
|
|
@@ -43511,6 +44729,7 @@ async function fetchIssues(repo, opts, deps = {}) {
|
|
|
43511
44729
|
}
|
|
43512
44730
|
|
|
43513
44731
|
// src/services/github-issues.ts
|
|
44732
|
+
init_sync_notify();
|
|
43514
44733
|
var issueRowId = (projectId, slug, number) => `${projectId}:${slug}#${number}`;
|
|
43515
44734
|
async function pullIssues(projectId, opts = {}, deps = {}) {
|
|
43516
44735
|
const resolved = await resolveGithubRepo(projectId);
|
|
@@ -43559,6 +44778,7 @@ async function pullIssues(projectId, opts = {}, deps = {}) {
|
|
|
43559
44778
|
// src/services/github-accept.ts
|
|
43560
44779
|
init_src();
|
|
43561
44780
|
init_db();
|
|
44781
|
+
init_sync_notify();
|
|
43562
44782
|
var backlinkOf = (i) => `Dari GitHub issue ${i.repoSlug}#${i.number} (${i.url}).`;
|
|
43563
44783
|
async function acceptGithubIssue(issue2, opts) {
|
|
43564
44784
|
if (issue2.specId) {
|
|
@@ -43615,6 +44835,7 @@ ${backlink}`;
|
|
|
43615
44835
|
}
|
|
43616
44836
|
|
|
43617
44837
|
// src/routes/github-issues.ts
|
|
44838
|
+
init_sync_notify();
|
|
43618
44839
|
var zPull = external_exports.object({
|
|
43619
44840
|
state: external_exports.enum(["open", "all"]).optional(),
|
|
43620
44841
|
limit: external_exports.number().int().min(1).max(1e3).optional()
|
|
@@ -44451,10 +45672,10 @@ function sanitizeClientText(text) {
|
|
|
44451
45672
|
return text.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, "").slice(0, MAX_PESAN);
|
|
44452
45673
|
}
|
|
44453
45674
|
function wrapClientMessage(text, nonce) {
|
|
44454
|
-
const
|
|
45675
|
+
const open5 = `<pesan-klien-${nonce}>`;
|
|
44455
45676
|
const close = `</pesan-klien-${nonce}>`;
|
|
44456
45677
|
const jinak = sanitizeClientText(text).replaceAll("</pesan-klien", "<\u200B/pesan-klien").replaceAll("<pesan-klien", "<\u200Bpesan-klien");
|
|
44457
|
-
return `${
|
|
45678
|
+
return `${open5}
|
|
44458
45679
|
${jinak}
|
|
44459
45680
|
${close}`;
|
|
44460
45681
|
}
|
|
@@ -44553,9 +45774,9 @@ ${baru}`;
|
|
|
44553
45774
|
// src/services/portal-chat/workspace.ts
|
|
44554
45775
|
init_src();
|
|
44555
45776
|
init_db();
|
|
44556
|
-
import { mkdirSync as mkdirSync12, mkdtempSync as mkdtempSync2, rmSync as
|
|
44557
|
-
import { tmpdir as
|
|
44558
|
-
import { join as
|
|
45777
|
+
import { mkdirSync as mkdirSync12, mkdtempSync as mkdtempSync2, rmSync as rmSync8, writeFileSync as writeFileSync9 } from "node:fs";
|
|
45778
|
+
import { tmpdir as tmpdir6 } from "node:os";
|
|
45779
|
+
import { join as join30 } from "node:path";
|
|
44559
45780
|
var STAGE_LABEL = {
|
|
44560
45781
|
brainstorming: "Dirumuskan",
|
|
44561
45782
|
objective: "Dirumuskan",
|
|
@@ -44610,7 +45831,7 @@ ${c.body}
|
|
|
44610
45831
|
${baris.join("\n")}`;
|
|
44611
45832
|
}
|
|
44612
45833
|
async function buildChatWorkspace(projectId) {
|
|
44613
|
-
const dir2 = mkdtempSync2(
|
|
45834
|
+
const dir2 = mkdtempSync2(join30(tmpdir6(), "hanoman-portal-chat-"));
|
|
44614
45835
|
try {
|
|
44615
45836
|
const project = await prisma.project.findUnique({
|
|
44616
45837
|
where: { id: projectId },
|
|
@@ -44631,7 +45852,7 @@ async function buildChatWorkspace(projectId) {
|
|
|
44631
45852
|
});
|
|
44632
45853
|
const files = [];
|
|
44633
45854
|
const tulis = (rel, isi) => {
|
|
44634
|
-
|
|
45855
|
+
writeFileSync9(join30(dir2, rel), isi, { mode: 384 });
|
|
44635
45856
|
files.push(rel);
|
|
44636
45857
|
};
|
|
44637
45858
|
tulis("project.md", renderProjectDoc(project));
|
|
@@ -44640,16 +45861,16 @@ async function buildChatWorkspace(projectId) {
|
|
|
44640
45861
|
tulis("catatan-rilis.md", renderChangelogDoc(changelogs));
|
|
44641
45862
|
const prds = await listPrds(projectId);
|
|
44642
45863
|
if (prds.length) {
|
|
44643
|
-
mkdirSync12(
|
|
45864
|
+
mkdirSync12(join30(dir2, "dokumen"), { mode: 448 });
|
|
44644
45865
|
for (const prd of prds) {
|
|
44645
45866
|
const isi = await readPrd(projectId, prd.path);
|
|
44646
45867
|
if (!isi) continue;
|
|
44647
|
-
tulis(
|
|
45868
|
+
tulis(join30("dokumen", `${prd.slug.replaceAll("/", "-")}.md`), isi);
|
|
44648
45869
|
}
|
|
44649
45870
|
}
|
|
44650
|
-
return { dir: dir2, files, cleanup: () =>
|
|
45871
|
+
return { dir: dir2, files, cleanup: () => rmSync8(dir2, { recursive: true, force: true }) };
|
|
44651
45872
|
} catch (error) {
|
|
44652
|
-
|
|
45873
|
+
rmSync8(dir2, { recursive: true, force: true });
|
|
44653
45874
|
throw error;
|
|
44654
45875
|
}
|
|
44655
45876
|
}
|
|
@@ -44683,7 +45904,7 @@ function portalChatArgv(o) {
|
|
|
44683
45904
|
o.prompt
|
|
44684
45905
|
];
|
|
44685
45906
|
}
|
|
44686
|
-
var
|
|
45907
|
+
var shellQuote4 = (v) => `'${v.replace(/'/g, `'"'"'`)}'`;
|
|
44687
45908
|
function portalChatProcess(o, env = process.env) {
|
|
44688
45909
|
const file = effectiveStr("HANOMAN_CLAUDE_BIN") ?? "claude";
|
|
44689
45910
|
const args = portalChatArgv(o);
|
|
@@ -44693,7 +45914,7 @@ function portalChatProcess(o, env = process.env) {
|
|
|
44693
45914
|
throw new Error("chat portal menolak jalan: sandbox sesi wajib saat hardening menyala");
|
|
44694
45915
|
return { file, args, cwd: o.workspace };
|
|
44695
45916
|
}
|
|
44696
|
-
const command = [file, ...args].map(
|
|
45917
|
+
const command = [file, ...args].map(shellQuote4).join(" ");
|
|
44697
45918
|
const sandbox = sandboxArgvFromEnv({
|
|
44698
45919
|
command,
|
|
44699
45920
|
worktree: o.workspace,
|
|
@@ -44727,7 +45948,7 @@ function chatFailureReason(err, stdout, stderr, timeoutMs) {
|
|
|
44727
45948
|
return `chat portal gagal (${bagaimana}): ${detail}`;
|
|
44728
45949
|
}
|
|
44729
45950
|
function runProcess(p3, timeoutMs) {
|
|
44730
|
-
return new Promise((
|
|
45951
|
+
return new Promise((resolve22, reject2) => {
|
|
44731
45952
|
const child = execFile17(p3.file, p3.args, {
|
|
44732
45953
|
cwd: p3.cwd,
|
|
44733
45954
|
timeout: timeoutMs,
|
|
@@ -44739,7 +45960,7 @@ function runProcess(p3, timeoutMs) {
|
|
|
44739
45960
|
stdout,
|
|
44740
45961
|
stderr,
|
|
44741
45962
|
timeoutMs
|
|
44742
|
-
))) :
|
|
45963
|
+
))) : resolve22(stdout));
|
|
44743
45964
|
child.stdin?.end();
|
|
44744
45965
|
});
|
|
44745
45966
|
}
|
|
@@ -44963,7 +46184,7 @@ init_zod();
|
|
|
44963
46184
|
init_db();
|
|
44964
46185
|
init_settings3();
|
|
44965
46186
|
var zPrd = external_exports.object({ slug: external_exports.string().regex(/^[a-z0-9]([a-z0-9-]{0,60}[a-z0-9])?$/) });
|
|
44966
|
-
var
|
|
46187
|
+
var zQuery2 = external_exports.object({ project: external_exports.string().min(1) });
|
|
44967
46188
|
var sessionRow = (s2) => ({
|
|
44968
46189
|
id: s2.id,
|
|
44969
46190
|
projectId: s2.projectId,
|
|
@@ -44991,7 +46212,7 @@ var messageRow = (m) => ({
|
|
|
44991
46212
|
});
|
|
44992
46213
|
async function portal_chat_admin_default(app2) {
|
|
44993
46214
|
app2.get("/portal-chat/sessions", async (req, reply) => {
|
|
44994
|
-
const parsed =
|
|
46215
|
+
const parsed = zQuery2.safeParse(req.query);
|
|
44995
46216
|
if (!parsed.success) return reply.code(400).send({ error: "project wajib" });
|
|
44996
46217
|
const { page, limit } = req.query;
|
|
44997
46218
|
const rows = await prisma.portalChatSession.findMany({
|
|
@@ -45035,7 +46256,7 @@ async function portal_chat_admin_default(app2) {
|
|
|
45035
46256
|
return reply.code(201).send({ path });
|
|
45036
46257
|
});
|
|
45037
46258
|
app2.get("/portal-chat/export", async (req, reply) => {
|
|
45038
|
-
const parsed =
|
|
46259
|
+
const parsed = zQuery2.safeParse(req.query);
|
|
45039
46260
|
if (!parsed.success) return reply.code(400).send({ error: "project wajib" });
|
|
45040
46261
|
const { from, to } = req.query;
|
|
45041
46262
|
const rows = await prisma.portalChatSession.findMany({
|
|
@@ -45155,15 +46376,15 @@ init_src();
|
|
|
45155
46376
|
init_db();
|
|
45156
46377
|
|
|
45157
46378
|
// src/services/bootstrap.ts
|
|
45158
|
-
import { createHash as
|
|
46379
|
+
import { createHash as createHash11, randomBytes as randomBytes10, timingSafeEqual as timingSafeEqual4 } from "node:crypto";
|
|
45159
46380
|
import { chmod, lstat as lstat3, mkdir as mkdir6, readFile as readFile5, unlink as unlink5, writeFile as writeFile5 } from "node:fs/promises";
|
|
45160
|
-
import { join as
|
|
46381
|
+
import { join as join31 } from "node:path";
|
|
45161
46382
|
var TTL_MS5 = 15 * 6e4;
|
|
45162
46383
|
var SETUP_TOKEN_FILE = "setup.token";
|
|
45163
46384
|
var BootstrapError = class extends Error {
|
|
45164
46385
|
code = "BOOTSTRAP_PROOF";
|
|
45165
46386
|
};
|
|
45166
|
-
var tokenPath = (home3) =>
|
|
46387
|
+
var tokenPath = (home3) => join31(home3, SETUP_TOKEN_FILE);
|
|
45167
46388
|
async function readStored(home3) {
|
|
45168
46389
|
try {
|
|
45169
46390
|
const info = await lstat3(tokenPath(home3));
|
|
@@ -45208,8 +46429,8 @@ ${new Date(expiresAt).toISOString()}
|
|
|
45208
46429
|
}
|
|
45209
46430
|
async function verifySetupToken(candidate, home3, now = Date.now()) {
|
|
45210
46431
|
const stored = await readStored(home3);
|
|
45211
|
-
const got =
|
|
45212
|
-
const want =
|
|
46432
|
+
const got = createHash11("sha256").update(candidate).digest();
|
|
46433
|
+
const want = createHash11("sha256").update(stored.token).digest();
|
|
45213
46434
|
if (stored.expiresAt <= now || !timingSafeEqual4(got, want)) throw new BootstrapError("invalid setup proof");
|
|
45214
46435
|
}
|
|
45215
46436
|
async function consumeSetupToken(home3) {
|
|
@@ -45488,7 +46709,10 @@ function capabilityForRoute(method, path) {
|
|
|
45488
46709
|
return rw("settings");
|
|
45489
46710
|
}
|
|
45490
46711
|
if (top === "lead") return rw("lead");
|
|
45491
|
-
if (top === "custom-agents")
|
|
46712
|
+
if (top === "custom-agents") {
|
|
46713
|
+
if (seg[1] === "metrics" || seg[1] === "invocations") return "COOKIE_ONLY";
|
|
46714
|
+
return rw("agents");
|
|
46715
|
+
}
|
|
45492
46716
|
if (top === "telegram") {
|
|
45493
46717
|
const sub = seg[1] ?? "";
|
|
45494
46718
|
if (sub === "settings" || sub === "test" || sub === "credentials") return "COOKIE_ONLY";
|
|
@@ -45775,6 +46999,7 @@ function buildApp({ requireAuth = true, agentDocFile, env = process.env } = {})
|
|
|
45775
46999
|
await api.register(methods);
|
|
45776
47000
|
await api.register(lead_default);
|
|
45777
47001
|
await api.register(custom_agents_default);
|
|
47002
|
+
await api.register(custom_agent_metrics_default);
|
|
45778
47003
|
await api.register(githubIssues);
|
|
45779
47004
|
await api.register(telegramRoutes);
|
|
45780
47005
|
await api.register(webhooks_default);
|
|
@@ -45975,6 +47200,7 @@ init_db();
|
|
|
45975
47200
|
init_src2();
|
|
45976
47201
|
init_notifications2();
|
|
45977
47202
|
init_stage_machine();
|
|
47203
|
+
init_sync_notify();
|
|
45978
47204
|
init_pty();
|
|
45979
47205
|
init_session_phases();
|
|
45980
47206
|
var FAIL_REASON = "sesi berakhir sebelum mencapai done (gagal/limit)";
|
|
@@ -46141,6 +47367,7 @@ function registerTriaseSource() {
|
|
|
46141
47367
|
}
|
|
46142
47368
|
|
|
46143
47369
|
// src/server.ts
|
|
47370
|
+
init_custom_agents2();
|
|
46144
47371
|
init_pty();
|
|
46145
47372
|
init_bootstrap();
|
|
46146
47373
|
|
|
@@ -46150,7 +47377,7 @@ init_tap();
|
|
|
46150
47377
|
// src/services/webhooks/emit.ts
|
|
46151
47378
|
init_src();
|
|
46152
47379
|
init_db();
|
|
46153
|
-
import { randomUUID as
|
|
47380
|
+
import { randomUUID as randomUUID11 } from "node:crypto";
|
|
46154
47381
|
function skipped(def2, row) {
|
|
46155
47382
|
if (!def2.skipWhen || !row) return false;
|
|
46156
47383
|
return row[def2.skipWhen.field] === def2.skipWhen.equals;
|
|
@@ -46216,7 +47443,7 @@ async function emitWebhook(i) {
|
|
|
46216
47443
|
const targets = matchingEndpoints(type, projectId);
|
|
46217
47444
|
if (!targets.length) return;
|
|
46218
47445
|
const name2 = projectId ? (await prisma.project.findUnique({ where: { id: projectId }, select: { name: true } }))?.name ?? null : null;
|
|
46219
|
-
const env = buildEnvelope(i, name2, (/* @__PURE__ */ new Date()).toISOString(), `evt_${
|
|
47446
|
+
const env = buildEnvelope(i, name2, (/* @__PURE__ */ new Date()).toISOString(), `evt_${randomUUID11().replace(/-/g, "")}`);
|
|
46220
47447
|
if (!env) return;
|
|
46221
47448
|
await enqueueEnvelope(env, targets);
|
|
46222
47449
|
} catch (e) {
|
|
@@ -46529,13 +47756,13 @@ init_src2();
|
|
|
46529
47756
|
|
|
46530
47757
|
// src/services/secure-home.ts
|
|
46531
47758
|
import { chmod as chmod2, lstat as lstat4, mkdir as mkdir7 } from "node:fs/promises";
|
|
46532
|
-
import { isAbsolute as
|
|
47759
|
+
import { isAbsolute as isAbsolute8, resolve as resolve21 } from "node:path";
|
|
46533
47760
|
var HomePermissionError = class extends Error {
|
|
46534
47761
|
code = "HOME_SYMLINK";
|
|
46535
47762
|
};
|
|
46536
47763
|
async function assertNoSymlink(path, allowMissing) {
|
|
46537
47764
|
try {
|
|
46538
|
-
const info = await lstat4(
|
|
47765
|
+
const info = await lstat4(resolve21(path));
|
|
46539
47766
|
if (info.isSymbolicLink()) throw new HomePermissionError(`symlink ditolak: ${path}`);
|
|
46540
47767
|
} catch (error) {
|
|
46541
47768
|
if (error.code === "ENOENT" && allowMissing) return;
|
|
@@ -46543,7 +47770,7 @@ async function assertNoSymlink(path, allowMissing) {
|
|
|
46543
47770
|
}
|
|
46544
47771
|
}
|
|
46545
47772
|
async function secureHanomanHome(opts) {
|
|
46546
|
-
if (!
|
|
47773
|
+
if (!isAbsolute8(opts.home)) throw new HomePermissionError("HANOMAN_HOME harus absolut");
|
|
46547
47774
|
await assertNoSymlink(opts.home, true);
|
|
46548
47775
|
await mkdir7(opts.home, { recursive: true, mode: 448 });
|
|
46549
47776
|
await assertNoSymlink(opts.home, false);
|
|
@@ -46733,10 +47960,106 @@ function startRetentionSweep() {
|
|
|
46733
47960
|
|
|
46734
47961
|
// src/server.ts
|
|
46735
47962
|
init_uploads();
|
|
47963
|
+
|
|
47964
|
+
// src/services/session-event-relay.ts
|
|
47965
|
+
init_session_event_token();
|
|
47966
|
+
init_session_event_spool();
|
|
47967
|
+
import { constants as constants3 } from "node:fs";
|
|
47968
|
+
import { mkdir as mkdir8, open as open4, readdir as readdir7, rm as rm6 } from "node:fs/promises";
|
|
47969
|
+
import { join as join32 } from "node:path";
|
|
47970
|
+
var MAX_EVENT_BYTES = 1e6;
|
|
47971
|
+
var MAX_FILES_PER_DRAIN = 1e3;
|
|
47972
|
+
var SESSION_ID_RE = /^[a-z0-9_-]+$/;
|
|
47973
|
+
async function drainSessionEventSpool(app2, root = sessionEventSpoolRoot()) {
|
|
47974
|
+
await mkdir8(root, { recursive: true, mode: 448 });
|
|
47975
|
+
let delivered = 0;
|
|
47976
|
+
let examined = 0;
|
|
47977
|
+
let readBuffer;
|
|
47978
|
+
for (const session of await readdir7(root, { withFileTypes: true })) {
|
|
47979
|
+
if (!session.isDirectory() || !SESSION_ID_RE.test(session.name)) continue;
|
|
47980
|
+
const dir2 = join32(root, session.name);
|
|
47981
|
+
let entries3;
|
|
47982
|
+
try {
|
|
47983
|
+
entries3 = await readdir7(dir2, { withFileTypes: true });
|
|
47984
|
+
} catch {
|
|
47985
|
+
continue;
|
|
47986
|
+
}
|
|
47987
|
+
for (const entry of entries3) {
|
|
47988
|
+
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
47989
|
+
if (++examined > MAX_FILES_PER_DRAIN) return delivered;
|
|
47990
|
+
const path = join32(dir2, entry.name);
|
|
47991
|
+
let payload;
|
|
47992
|
+
let handle;
|
|
47993
|
+
try {
|
|
47994
|
+
handle = await open4(path, constants3.O_RDONLY | constants3.O_NOFOLLOW);
|
|
47995
|
+
const stat4 = await handle.stat();
|
|
47996
|
+
if (!stat4.isFile() || stat4.size > MAX_EVENT_BYTES) throw new Error("payload terlalu besar");
|
|
47997
|
+
readBuffer ??= Buffer.allocUnsafe(MAX_EVENT_BYTES + 1);
|
|
47998
|
+
const { bytesRead } = await handle.read(readBuffer, 0, readBuffer.length, 0);
|
|
47999
|
+
if (bytesRead > MAX_EVENT_BYTES) throw new Error("payload terlalu besar");
|
|
48000
|
+
const parsed = JSON.parse(readBuffer.subarray(0, bytesRead).toString("utf8"));
|
|
48001
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
48002
|
+
throw new Error("payload event bukan object");
|
|
48003
|
+
}
|
|
48004
|
+
payload = parsed;
|
|
48005
|
+
} catch {
|
|
48006
|
+
await rm6(path, { force: true }).catch(() => {
|
|
48007
|
+
});
|
|
48008
|
+
continue;
|
|
48009
|
+
} finally {
|
|
48010
|
+
await handle?.close().catch(() => {
|
|
48011
|
+
});
|
|
48012
|
+
}
|
|
48013
|
+
try {
|
|
48014
|
+
const response = await app2.inject({
|
|
48015
|
+
method: "POST",
|
|
48016
|
+
url: "/api/session-events",
|
|
48017
|
+
headers: {
|
|
48018
|
+
authorization: `Bearer ${sessionEventToken(session.name)}`,
|
|
48019
|
+
"x-hanoman-session": session.name
|
|
48020
|
+
},
|
|
48021
|
+
payload
|
|
48022
|
+
});
|
|
48023
|
+
if (response.statusCode === 429 || response.statusCode >= 500) continue;
|
|
48024
|
+
await rm6(path, { force: true }).catch(() => {
|
|
48025
|
+
});
|
|
48026
|
+
if (response.statusCode >= 200 && response.statusCode < 300) delivered++;
|
|
48027
|
+
} catch {
|
|
48028
|
+
}
|
|
48029
|
+
}
|
|
48030
|
+
}
|
|
48031
|
+
return delivered;
|
|
48032
|
+
}
|
|
48033
|
+
function startSessionEventRelay(app2, options2 = {}) {
|
|
48034
|
+
const root = options2.root ?? sessionEventSpoolRoot();
|
|
48035
|
+
let running = false;
|
|
48036
|
+
const tick6 = async () => {
|
|
48037
|
+
if (running) return;
|
|
48038
|
+
running = true;
|
|
48039
|
+
try {
|
|
48040
|
+
await drainSessionEventSpool({ inject: async (request) => app2.inject(request) }, root);
|
|
48041
|
+
} catch (error) {
|
|
48042
|
+
console.error("session event relay gagal:", error);
|
|
48043
|
+
} finally {
|
|
48044
|
+
running = false;
|
|
48045
|
+
}
|
|
48046
|
+
};
|
|
48047
|
+
const timer9 = setInterval(() => {
|
|
48048
|
+
void tick6();
|
|
48049
|
+
}, options2.intervalMs ?? 250);
|
|
48050
|
+
timer9.unref();
|
|
48051
|
+
app2.addHook("onClose", async () => {
|
|
48052
|
+
clearInterval(timer9);
|
|
48053
|
+
});
|
|
48054
|
+
void tick6();
|
|
48055
|
+
}
|
|
48056
|
+
|
|
48057
|
+
// src/server.ts
|
|
46736
48058
|
var app = buildApp();
|
|
46737
48059
|
var port = Number(process.env.PORT ?? 8787);
|
|
46738
48060
|
var host = process.env.HOST ?? "127.0.0.1";
|
|
46739
48061
|
assertRuntimeBoundary(process.env, { uid: process.getuid?.(), host });
|
|
48062
|
+
startSessionEventRelay(app);
|
|
46740
48063
|
process.on("unhandledRejection", (err) => console.error("unhandledRejection:", err));
|
|
46741
48064
|
process.on("uncaughtException", (err) => console.error("uncaughtException:", err));
|
|
46742
48065
|
async function shutdown(sig) {
|
|
@@ -46765,9 +48088,19 @@ var bootstrapReady = secureHanomanHome({
|
|
|
46765
48088
|
const proof = await ensureSetupToken(resolveHome());
|
|
46766
48089
|
console.log(`setup admin memerlukan token di ${proof.path}; kedaluwarsa ${new Date(proof.expiresAt).toISOString()}`);
|
|
46767
48090
|
});
|
|
46768
|
-
bootstrapReady.then(
|
|
46769
|
-
|
|
48091
|
+
bootstrapReady.then(async () => {
|
|
48092
|
+
try {
|
|
48093
|
+
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config2(), config_exports));
|
|
48094
|
+
const { applyConfigOnBoot: applyConfigOnBoot2 } = await Promise.resolve().then(() => (init_config_apply(), config_apply_exports));
|
|
48095
|
+
await loadConfig2();
|
|
48096
|
+
await applyConfigOnBoot2();
|
|
48097
|
+
} catch (e) {
|
|
48098
|
+
console.error("config runtime gagal dimuat \u2014 memakai env/default:", e);
|
|
48099
|
+
}
|
|
46770
48100
|
await installCustomAgents();
|
|
48101
|
+
return app.listen({ port, host });
|
|
48102
|
+
}).then(async () => {
|
|
48103
|
+
console.log(`hanoman api ${host}:${port}`);
|
|
46771
48104
|
await installWebhooks();
|
|
46772
48105
|
installSessionHistory();
|
|
46773
48106
|
try {
|
|
@@ -46775,17 +48108,12 @@ bootstrapReady.then(() => app.listen({ port, host })).then(async () => {
|
|
|
46775
48108
|
void reconcileHistory(liveIds).then((n2) => {
|
|
46776
48109
|
if (n2) console.log(`riwayat sesi: ${n2} baris berjalan direkonsiliasi`);
|
|
46777
48110
|
}).catch((e) => console.error("rekonsiliasi riwayat sesi:", e));
|
|
48111
|
+
void reconcileAgentInvocations(liveIds).then((n2) => {
|
|
48112
|
+
if (n2) console.log(`custom agent: ${n2} invocation ditandai abandoned`);
|
|
48113
|
+
}).catch((e) => console.error("rekonsiliasi invocation custom agent:", e));
|
|
46778
48114
|
} catch (e) {
|
|
46779
48115
|
console.error("rekonsiliasi riwayat sesi dilewati \u2014 tmux tak terbaca:", e);
|
|
46780
48116
|
}
|
|
46781
|
-
try {
|
|
46782
|
-
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config2(), config_exports));
|
|
46783
|
-
const { applyConfigOnBoot: applyConfigOnBoot2 } = await Promise.resolve().then(() => (init_config_apply(), config_apply_exports));
|
|
46784
|
-
await loadConfig2();
|
|
46785
|
-
await applyConfigOnBoot2();
|
|
46786
|
-
} catch (e) {
|
|
46787
|
-
console.error("config runtime gagal dimuat \u2014 memakai env/default:", e);
|
|
46788
|
-
}
|
|
46789
48117
|
const boundPort = app.server.address().port;
|
|
46790
48118
|
await installTelegramGateway(app, { apiBase: `http://127.0.0.1:${boundPort}` });
|
|
46791
48119
|
startVpsMonitor();
|