hanoman 0.2.3 → 0.2.5
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 +232 -34
- package/dist/server.js +1964 -590
- 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-B06OYVqX.css +41 -0
- package/web/assets/{index-BllpAcXg.js → index-D6R-u1nz.js} +1555 -1555
- package/web/index.html +2 -2
- package/web/assets/index-DNaw5xBe.css +0 -41
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)
|
|
@@ -11651,6 +11775,16 @@ var init_presence = __esm({
|
|
|
11651
11775
|
}
|
|
11652
11776
|
});
|
|
11653
11777
|
|
|
11778
|
+
// ../shared/src/pending.ts
|
|
11779
|
+
var EMPTY_PENDING, OPEN_LEAD_FLOW_STATUSES;
|
|
11780
|
+
var init_pending = __esm({
|
|
11781
|
+
"../shared/src/pending.ts"() {
|
|
11782
|
+
"use strict";
|
|
11783
|
+
EMPTY_PENDING = { triage: 0, backlog: 0, prd: 0, lead: 0 };
|
|
11784
|
+
OPEN_LEAD_FLOW_STATUSES = ["menunggu", "sebagian"];
|
|
11785
|
+
}
|
|
11786
|
+
});
|
|
11787
|
+
|
|
11654
11788
|
// ../shared/src/index.ts
|
|
11655
11789
|
var init_src = __esm({
|
|
11656
11790
|
"../shared/src/index.ts"() {
|
|
@@ -11691,6 +11825,7 @@ var init_src = __esm({
|
|
|
11691
11825
|
init_session_dialog();
|
|
11692
11826
|
init_session_ask();
|
|
11693
11827
|
init_presence();
|
|
11828
|
+
init_pending();
|
|
11694
11829
|
init_team();
|
|
11695
11830
|
}
|
|
11696
11831
|
});
|
|
@@ -12321,8 +12456,8 @@ var init_git = __esm({
|
|
|
12321
12456
|
// di dalam repo induk, yang menjawab "true" untuk pertanyaan pertama. cwd yang tak ada membuat
|
|
12322
12457
|
// spawnSync gagal (`status` null), dan itu sudah tertangkap `!== 0`.
|
|
12323
12458
|
worktreeAlive: (path) => {
|
|
12324
|
-
const
|
|
12325
|
-
if (
|
|
12459
|
+
const inside2 = spawnSync("git", ["rev-parse", "--is-inside-work-tree"], { cwd: path, encoding: "utf8" });
|
|
12460
|
+
if (inside2.status !== 0 || inside2.stdout.trim() !== "true") return false;
|
|
12326
12461
|
const top = spawnSync("git", ["rev-parse", "--show-toplevel"], { cwd: path, encoding: "utf8" });
|
|
12327
12462
|
return top.status === 0 && samePath(top.stdout.trim(), path);
|
|
12328
12463
|
},
|
|
@@ -12353,17 +12488,27 @@ var init_git = __esm({
|
|
|
12353
12488
|
});
|
|
12354
12489
|
|
|
12355
12490
|
// ../runner/src/settings.ts
|
|
12356
|
-
var EVENT_HOOK_COMMAND, guardSettings;
|
|
12491
|
+
var EVENT_SPOOL_SCRIPT, EVENT_HOOK_COMMAND, guardSettings;
|
|
12357
12492
|
var init_settings2 = __esm({
|
|
12358
12493
|
"../runner/src/settings.ts"() {
|
|
12359
12494
|
"use strict";
|
|
12495
|
+
EVENT_SPOOL_SCRIPT = [
|
|
12496
|
+
'const fs=require("node:fs"),p=require("node:path"),c=require("node:crypto")',
|
|
12497
|
+
'let d=""',
|
|
12498
|
+
'process.stdin.setEncoding("utf8")',
|
|
12499
|
+
'process.stdin.on("data",x=>d+=x)',
|
|
12500
|
+
'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{}}})'
|
|
12501
|
+
].join(";");
|
|
12360
12502
|
EVENT_HOOK_COMMAND = [
|
|
12503
|
+
'if [ -n "${HANOMAN_EVENT_DIR:-}" ]; then',
|
|
12504
|
+
`node -e '${EVENT_SPOOL_SCRIPT}' >/dev/null 2>&1;`,
|
|
12505
|
+
"else",
|
|
12361
12506
|
'curl -sS -m 2 -X POST "$HANOMAN_EVENT_URL"',
|
|
12362
12507
|
"-H 'content-type: application/json'",
|
|
12363
12508
|
'-H "authorization: Bearer $HANOMAN_EVENT_TOKEN"',
|
|
12364
12509
|
'-H "x-hanoman-session: $HANOMAN_SESSION_ID"',
|
|
12365
12510
|
'${HANOMAN_EVENT_HOST:+-H "host: $HANOMAN_EVENT_HOST"}',
|
|
12366
|
-
"--data-binary @- >/dev/null 2>&1; exit 0"
|
|
12511
|
+
"--data-binary @- >/dev/null 2>&1; fi; exit 0"
|
|
12367
12512
|
].join(" ");
|
|
12368
12513
|
guardSettings = (decisionFile, goal, eventHook) => {
|
|
12369
12514
|
const hooks2 = {};
|
|
@@ -12380,6 +12525,8 @@ var init_settings2 = __esm({
|
|
|
12380
12525
|
matcher: "AskUserQuestion",
|
|
12381
12526
|
hooks: [{ type: "command", command: EVENT_HOOK_COMMAND }]
|
|
12382
12527
|
}];
|
|
12528
|
+
hooks2.SubagentStart = [{ hooks: [{ type: "command", command: EVENT_HOOK_COMMAND }] }];
|
|
12529
|
+
hooks2.SubagentStop = [{ hooks: [{ type: "command", command: EVENT_HOOK_COMMAND }] }];
|
|
12383
12530
|
}
|
|
12384
12531
|
if (goal) hooks2.Stop = [{ hooks: [{ type: "prompt", prompt: goal }] }];
|
|
12385
12532
|
return { hooks: hooks2 };
|
|
@@ -12458,6 +12605,10 @@ function codexHookArgs(o) {
|
|
|
12458
12605
|
const args = [];
|
|
12459
12606
|
if (stop.length) args.push("-c", `hooks.Stop=${group(stop)}`);
|
|
12460
12607
|
if (submit.length) args.push("-c", `hooks.UserPromptSubmit=${group(submit)}`);
|
|
12608
|
+
if (o.eventHook) {
|
|
12609
|
+
args.push("-c", `hooks.SubagentStart=${group([EVENT_HOOK_COMMAND])}`);
|
|
12610
|
+
args.push("-c", `hooks.SubagentStop=${group([EVENT_HOOK_COMMAND])}`);
|
|
12611
|
+
}
|
|
12461
12612
|
return args;
|
|
12462
12613
|
}
|
|
12463
12614
|
function codexGoalScript(o) {
|
|
@@ -12554,9 +12705,12 @@ var init_agent_cli = __esm({
|
|
|
12554
12705
|
// ../runner/src/custom-agents.ts
|
|
12555
12706
|
function agentPromptOf(def2, roster) {
|
|
12556
12707
|
const can = liveMentions(def2, roster);
|
|
12708
|
+
const instructions = def2.timeoutSeconds ? `${def2.instructions}
|
|
12709
|
+
|
|
12710
|
+
Batas waktu Hanoman untuk pekerjaan ini ${def2.timeoutSeconds} detik. Prioritaskan putusan dan bukti sebelum batas itu.` : def2.instructions;
|
|
12557
12711
|
if (can.length === 0) {
|
|
12558
12712
|
return [
|
|
12559
|
-
|
|
12713
|
+
instructions,
|
|
12560
12714
|
"",
|
|
12561
12715
|
"---",
|
|
12562
12716
|
"Kamu TIDAK boleh mendelegasikan ke agen lain. Selesaikan sendiri lalu laporkan hasilnya.",
|
|
@@ -12566,7 +12720,7 @@ function agentPromptOf(def2, roster) {
|
|
|
12566
12720
|
}
|
|
12567
12721
|
const list2 = can.map((m) => `@${m}`).join(", ");
|
|
12568
12722
|
return [
|
|
12569
|
-
|
|
12723
|
+
instructions,
|
|
12570
12724
|
"",
|
|
12571
12725
|
"---",
|
|
12572
12726
|
`Kamu boleh mendelegasikan HANYA ke: ${list2}. Panggil lewat ${MENTION_TOOL} dengan nama agennya.`,
|
|
@@ -12576,44 +12730,37 @@ function agentPromptOf(def2, roster) {
|
|
|
12576
12730
|
CODE_STYLE_CLAUSE
|
|
12577
12731
|
].join("\n");
|
|
12578
12732
|
}
|
|
12579
|
-
function renderAgentsJson(defs) {
|
|
12733
|
+
function renderAgentsJson(defs, options2 = {}) {
|
|
12580
12734
|
if (defs.length === 0) return "";
|
|
12581
12735
|
const out4 = {};
|
|
12582
12736
|
for (const d of defs) {
|
|
12737
|
+
const resolvedTools = resolveTools({ tools: d.tools, mentions: d.mentions });
|
|
12738
|
+
const readOnly = d.workspacePolicy === "read-only";
|
|
12583
12739
|
out4[d.name] = {
|
|
12584
12740
|
description: d.description,
|
|
12585
12741
|
prompt: agentPromptOf(d, defs),
|
|
12586
|
-
tools:
|
|
12587
|
-
...d.model ? { model: d.model } : {}
|
|
12742
|
+
tools: readOnly ? resolvedTools.filter((tool) => READ_ONLY_TOOLS.has(tool)) : resolvedTools,
|
|
12743
|
+
...d.model ? { model: d.model } : {},
|
|
12744
|
+
...d.effort ? { effort: d.effort } : {},
|
|
12745
|
+
...typeof d.maxTurns === "number" ? { maxTurns: d.maxTurns } : {},
|
|
12746
|
+
...d.workspacePolicy === "isolated-worktree" ? { isolation: "worktree" } : {},
|
|
12747
|
+
...readOnly ? { permissionMode: "plan" } : {},
|
|
12748
|
+
...readOnly && options2.readOnlyHookCommand ? {
|
|
12749
|
+
hooks: {
|
|
12750
|
+
PreToolUse: [{
|
|
12751
|
+
hooks: [{
|
|
12752
|
+
type: "command",
|
|
12753
|
+
command: options2.readOnlyHookCommand,
|
|
12754
|
+
timeout: 5
|
|
12755
|
+
}]
|
|
12756
|
+
}]
|
|
12757
|
+
}
|
|
12758
|
+
} : {}
|
|
12588
12759
|
};
|
|
12589
12760
|
}
|
|
12590
12761
|
return JSON.stringify(out4);
|
|
12591
12762
|
}
|
|
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) {
|
|
12763
|
+
function agentDelegationClause(defs, runtime = "claude") {
|
|
12617
12764
|
if (defs.length === 0) return "";
|
|
12618
12765
|
return [
|
|
12619
12766
|
"",
|
|
@@ -12625,35 +12772,343 @@ function agentDelegationClause(defs) {
|
|
|
12625
12772
|
"",
|
|
12626
12773
|
...defs.map((d) => `- **${d.name}** \u2014 ${d.description}`),
|
|
12627
12774
|
"",
|
|
12628
|
-
`Panggil lewat tool ${MENTION_TOOL} dengan nama agennya
|
|
12775
|
+
runtime === "codex" ? "Panggil target bernama persis lewat `spawn_agent`." : `Panggil lewat tool ${MENTION_TOOL} dengan nama agennya.`,
|
|
12776
|
+
"Mereka tak bisa mendelegasikan lagi,",
|
|
12629
12777
|
"jadi tak ada rantai panggilan yang perlu kamu jaga. Laporan mereka adalah MASUKAN \u2014 kamu yang",
|
|
12630
12778
|
"memutuskan, dan kamu yang bertanggung jawab atas hasilnya.",
|
|
12631
12779
|
""
|
|
12632
12780
|
].join("\n");
|
|
12633
12781
|
}
|
|
12634
|
-
var liveMentions;
|
|
12782
|
+
var liveMentions, READ_ONLY_TOOLS;
|
|
12635
12783
|
var init_custom_agents = __esm({
|
|
12636
12784
|
"../runner/src/custom-agents.ts"() {
|
|
12637
12785
|
"use strict";
|
|
12638
12786
|
init_src();
|
|
12639
12787
|
init_code_style();
|
|
12640
12788
|
liveMentions = (def2, roster) => {
|
|
12789
|
+
if (def2.workspacePolicy === "read-only") return [];
|
|
12641
12790
|
const names = new Set(roster.map((r) => r.name));
|
|
12642
12791
|
return def2.mentions.filter((m) => names.has(m) && m !== def2.name);
|
|
12643
12792
|
};
|
|
12793
|
+
READ_ONLY_TOOLS = /* @__PURE__ */ new Set(["Read", "Glob", "Grep", "Bash", "WebFetch", "WebSearch"]);
|
|
12794
|
+
}
|
|
12795
|
+
});
|
|
12796
|
+
|
|
12797
|
+
// ../runner/src/agent-readonly.ts
|
|
12798
|
+
import { chmodSync, writeFileSync } from "node:fs";
|
|
12799
|
+
import { join } from "node:path";
|
|
12800
|
+
function denyReadOnly(detail) {
|
|
12801
|
+
return { allowed: false, reason: `Hanoman read-only policy: ${detail}` };
|
|
12802
|
+
}
|
|
12803
|
+
function tokenizeReadOnlyCommand(command) {
|
|
12804
|
+
const tokens = [];
|
|
12805
|
+
let token = "";
|
|
12806
|
+
let quote2 = "";
|
|
12807
|
+
let escaped = false;
|
|
12808
|
+
let active = false;
|
|
12809
|
+
for (let index = 0; index < command.length; index++) {
|
|
12810
|
+
const char = command[index];
|
|
12811
|
+
if (escaped) {
|
|
12812
|
+
token += char;
|
|
12813
|
+
escaped = false;
|
|
12814
|
+
active = true;
|
|
12815
|
+
continue;
|
|
12816
|
+
}
|
|
12817
|
+
if (quote2) {
|
|
12818
|
+
if (char === quote2) quote2 = "";
|
|
12819
|
+
else if (char === "\\" && quote2 === '"') escaped = true;
|
|
12820
|
+
else token += char;
|
|
12821
|
+
active = true;
|
|
12822
|
+
continue;
|
|
12823
|
+
}
|
|
12824
|
+
if (char === "'" || char === '"') {
|
|
12825
|
+
quote2 = char;
|
|
12826
|
+
active = true;
|
|
12827
|
+
} else if (char === "\\") {
|
|
12828
|
+
escaped = true;
|
|
12829
|
+
active = true;
|
|
12830
|
+
} else if (/\s/.test(char)) {
|
|
12831
|
+
if (active) {
|
|
12832
|
+
tokens.push(token);
|
|
12833
|
+
token = "";
|
|
12834
|
+
active = false;
|
|
12835
|
+
}
|
|
12836
|
+
} else {
|
|
12837
|
+
token += char;
|
|
12838
|
+
active = true;
|
|
12839
|
+
}
|
|
12840
|
+
}
|
|
12841
|
+
if (quote2 || escaped) return null;
|
|
12842
|
+
if (active) tokens.push(token);
|
|
12843
|
+
return tokens;
|
|
12844
|
+
}
|
|
12845
|
+
function evaluateReadOnlyPayload(payload, policy, environment) {
|
|
12846
|
+
if (!payload || typeof payload !== "object") return denyReadOnly("payload hook tidak sah");
|
|
12847
|
+
const event = payload;
|
|
12848
|
+
const tool = typeof event.tool_name === "string" ? event.tool_name : typeof event.toolName === "string" ? event.toolName : "";
|
|
12849
|
+
if (!tool) return denyReadOnly("nama tool tidak tersedia");
|
|
12850
|
+
if (policy.directTools.includes(tool)) return { allowed: true };
|
|
12851
|
+
if (policy.deniedTools.includes(tool) || tool.startsWith("mcp__")) {
|
|
12852
|
+
return denyReadOnly(`tool ${tool} dapat mengubah state`);
|
|
12853
|
+
}
|
|
12854
|
+
if (!policy.shellTools.includes(tool)) return denyReadOnly(`tool ${tool} tidak terbukti read-only`);
|
|
12855
|
+
const rawInput = event.tool_input;
|
|
12856
|
+
const input = rawInput && typeof rawInput === "object" ? rawInput : {};
|
|
12857
|
+
const command = typeof input.command === "string" ? input.command : typeof input.cmd === "string" ? input.cmd : "";
|
|
12858
|
+
const trimmed = command.trim();
|
|
12859
|
+
if (!trimmed) return denyReadOnly("perintah shell kosong atau tidak dikenal");
|
|
12860
|
+
if (/\r|\n|;|&&|\|\||\||[<>]|\$|`/.test(trimmed)) {
|
|
12861
|
+
return denyReadOnly("operator shell yang dapat merangkai atau menulis dilarang");
|
|
12862
|
+
}
|
|
12863
|
+
const tokens = tokenizeReadOnlyCommand(trimmed);
|
|
12864
|
+
if (!tokens?.length) return denyReadOnly("perintah shell tidak dapat diparse dengan aman");
|
|
12865
|
+
const first = tokens[0] ?? "";
|
|
12866
|
+
const commandName = first.split("/").pop() ?? "";
|
|
12867
|
+
if (first !== commandName) {
|
|
12868
|
+
return denyReadOnly("executable ber-path tidak diizinkan; gunakan command allowlist dari PATH");
|
|
12869
|
+
}
|
|
12870
|
+
if (commandName === "git") {
|
|
12871
|
+
const subcommand = tokens[1] ?? "";
|
|
12872
|
+
if (!policy.gitCommands.includes(subcommand)) {
|
|
12873
|
+
return denyReadOnly(`git ${subcommand || "<kosong>"} bukan operasi baca yang diizinkan`);
|
|
12874
|
+
}
|
|
12875
|
+
const args = tokens.slice(2);
|
|
12876
|
+
if (args.some((arg) => arg === "--output" || arg.startsWith("--output=") || arg === "--ext-diff" || arg === "--textconv")) {
|
|
12877
|
+
return denyReadOnly("opsi git dapat menulis atau menjalankan helper eksternal");
|
|
12878
|
+
}
|
|
12879
|
+
if (subcommand !== "status" && (!args.includes("--no-ext-diff") || !args.includes("--no-textconv"))) {
|
|
12880
|
+
return denyReadOnly("git diff/show/log wajib menonaktifkan helper eksternal dan textconv");
|
|
12881
|
+
}
|
|
12882
|
+
return { allowed: true };
|
|
12883
|
+
}
|
|
12884
|
+
if (!policy.shellCommands.includes(commandName)) {
|
|
12885
|
+
return denyReadOnly(`perintah ${commandName || "<kosong>"} tidak terbukti read-only`);
|
|
12886
|
+
}
|
|
12887
|
+
if (commandName === "rg" && environment.RIPGREP_CONFIG_PATH?.trim()) {
|
|
12888
|
+
return denyReadOnly("RIPGREP_CONFIG_PATH dapat menyuntikkan preprocessor eksternal");
|
|
12889
|
+
}
|
|
12890
|
+
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="))) {
|
|
12891
|
+
return denyReadOnly("opsi rg dapat menjalankan helper eksternal");
|
|
12892
|
+
}
|
|
12893
|
+
if (commandName === "sed") {
|
|
12894
|
+
const quiet = tokens[1] === "-n" || tokens[1] === "--quiet" || tokens[1] === "--silent";
|
|
12895
|
+
const printOnly = /^\d+(?:,\d+)?p$/.test(tokens[2] ?? "");
|
|
12896
|
+
const files = tokens.slice(3);
|
|
12897
|
+
if (!quiet || !printOnly || files.length === 0 || files.some((arg) => arg.startsWith("-"))) {
|
|
12898
|
+
return denyReadOnly("hanya sed -n '<baris>[,<baris>]p' <berkas> yang diizinkan");
|
|
12899
|
+
}
|
|
12900
|
+
}
|
|
12901
|
+
return { allowed: true };
|
|
12902
|
+
}
|
|
12903
|
+
function readOnlyHookSource() {
|
|
12904
|
+
return [
|
|
12905
|
+
'"use strict";',
|
|
12906
|
+
`const denyReadOnly = ${denyReadOnly.toString()};`,
|
|
12907
|
+
`const tokenizeReadOnlyCommand = ${tokenizeReadOnlyCommand.toString()};`,
|
|
12908
|
+
`const evaluate = ${evaluateReadOnlyPayload.toString()};`,
|
|
12909
|
+
`const policy = ${JSON.stringify(POLICY)};`,
|
|
12910
|
+
"let input = '';",
|
|
12911
|
+
"process.stdin.setEncoding('utf8');",
|
|
12912
|
+
"process.stdin.on('data', chunk => { input += chunk; });",
|
|
12913
|
+
"process.stdin.on('end', () => {",
|
|
12914
|
+
" let payload;",
|
|
12915
|
+
" try { payload = JSON.parse(input); } catch { payload = null; }",
|
|
12916
|
+
" const decision = evaluate(payload, policy, process.env);",
|
|
12917
|
+
" if (!decision.allowed) { process.stderr.write(decision.reason + '\\n'); process.exitCode = 2; }",
|
|
12918
|
+
"});",
|
|
12919
|
+
"process.stdin.resume();",
|
|
12920
|
+
""
|
|
12921
|
+
].join("\n");
|
|
12922
|
+
}
|
|
12923
|
+
function writeReadOnlyHook(dir2) {
|
|
12924
|
+
const path = join(dir2, "custom-agent-readonly.cjs");
|
|
12925
|
+
writeFileSync(path, readOnlyHookSource(), { mode: 384 });
|
|
12926
|
+
chmodSync(path, 384);
|
|
12927
|
+
return { path, command: `node ${shellQuote(path)}` };
|
|
12928
|
+
}
|
|
12929
|
+
var POLICY, shellQuote;
|
|
12930
|
+
var init_agent_readonly = __esm({
|
|
12931
|
+
"../runner/src/agent-readonly.ts"() {
|
|
12932
|
+
"use strict";
|
|
12933
|
+
POLICY = {
|
|
12934
|
+
directTools: ["Read", "Glob", "Grep", "WebFetch", "WebSearch"],
|
|
12935
|
+
shellTools: ["Bash", "local_shell", "exec_command"],
|
|
12936
|
+
deniedTools: ["Write", "Edit", "Task", "apply_patch", "spawn_agent"],
|
|
12937
|
+
shellCommands: ["rg", "sed", "head", "tail", "wc", "ls"],
|
|
12938
|
+
gitCommands: ["diff", "show", "status", "log"]
|
|
12939
|
+
};
|
|
12940
|
+
shellQuote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
|
|
12941
|
+
}
|
|
12942
|
+
});
|
|
12943
|
+
|
|
12944
|
+
// ../runner/src/runtime-profile.ts
|
|
12945
|
+
function resolveHardening(env) {
|
|
12946
|
+
if (env.HANOMAN_HARDENING === "1") return true;
|
|
12947
|
+
return env.HANOMAN_SESSION_SANDBOX === "podman" || filled(env.HANOMAN_PUBLIC_ORIGINS) || filled(env.HANOMAN_TRUST_PROXY);
|
|
12948
|
+
}
|
|
12949
|
+
function resolveDeployment(env) {
|
|
12950
|
+
if (resolveHardening(env)) return "public";
|
|
12951
|
+
return env.HANOMAN_DEPLOYMENT === "public" ? "public" : "local";
|
|
12952
|
+
}
|
|
12953
|
+
var filled;
|
|
12954
|
+
var init_runtime_profile = __esm({
|
|
12955
|
+
"../runner/src/runtime-profile.ts"() {
|
|
12956
|
+
"use strict";
|
|
12957
|
+
filled = (v) => !!v && v.trim() !== "";
|
|
12958
|
+
}
|
|
12959
|
+
});
|
|
12960
|
+
|
|
12961
|
+
// ../runner/src/codex-agent-config.ts
|
|
12962
|
+
import { chmodSync as chmodSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
12963
|
+
import { join as join2 } from "node:path";
|
|
12964
|
+
function codexNativeAgentsSupported(version) {
|
|
12965
|
+
const parsed = version ? /(\d+)\.(\d+)\.(\d+)/.exec(version)?.[0] : null;
|
|
12966
|
+
return parsed ? cmpVersion(parsed, CODEX_NATIVE_AGENTS_MIN_CLIENT) >= 0 : false;
|
|
12967
|
+
}
|
|
12968
|
+
function codexNativeVersionProbe(env, codexBin3 = env.HANOMAN_CODEX_BIN ?? "codex") {
|
|
12969
|
+
const sandbox = env.HANOMAN_SESSION_SANDBOX ?? (resolveHardening(env) ? "required" : "off");
|
|
12970
|
+
if (sandbox !== "podman") return { bin: codexBin3, args: ["--version"] };
|
|
12971
|
+
return {
|
|
12972
|
+
bin: env.HANOMAN_PODMAN_BIN ?? "podman",
|
|
12973
|
+
args: [
|
|
12974
|
+
"run",
|
|
12975
|
+
"--rm",
|
|
12976
|
+
"--read-only",
|
|
12977
|
+
"--cap-drop=ALL",
|
|
12978
|
+
"--userns=keep-id",
|
|
12979
|
+
"--network",
|
|
12980
|
+
"none",
|
|
12981
|
+
env.HANOMAN_SESSION_IMAGE ?? "hanoman-agent:latest",
|
|
12982
|
+
"/bin/sh",
|
|
12983
|
+
"-lc",
|
|
12984
|
+
`${shellQuote2(codexBin3)} --version`
|
|
12985
|
+
]
|
|
12986
|
+
};
|
|
12987
|
+
}
|
|
12988
|
+
function renderCodexAgentToml(def2, roster, options2 = {}) {
|
|
12989
|
+
const lines2 = [
|
|
12990
|
+
`name = ${tomlString(def2.name)}`,
|
|
12991
|
+
`description = ${tomlString(def2.description)}`,
|
|
12992
|
+
`developer_instructions = ${tomlString(agentPromptOf(def2, roster))}`,
|
|
12993
|
+
...def2.model ? [`model = ${tomlString(def2.model)}`] : [],
|
|
12994
|
+
...def2.effort ? [`model_reasoning_effort = ${tomlString(def2.effort)}`] : [],
|
|
12995
|
+
...def2.workspacePolicy === "read-only" ? ['sandbox_mode = "read-only"'] : []
|
|
12996
|
+
];
|
|
12997
|
+
if (def2.workspacePolicy === "read-only" && options2.readOnlyHookCommand) {
|
|
12998
|
+
lines2.push(
|
|
12999
|
+
"",
|
|
13000
|
+
"[[hooks.PreToolUse]]",
|
|
13001
|
+
"",
|
|
13002
|
+
"[[hooks.PreToolUse.hooks]]",
|
|
13003
|
+
'type = "command"',
|
|
13004
|
+
`command = ${tomlString(options2.readOnlyHookCommand)}`,
|
|
13005
|
+
"timeout = 5"
|
|
13006
|
+
);
|
|
13007
|
+
}
|
|
13008
|
+
return `${lines2.join("\n")}
|
|
13009
|
+
`;
|
|
13010
|
+
}
|
|
13011
|
+
function materializeCodexAgents(defs, tempDir, options2 = {}) {
|
|
13012
|
+
if (defs.length === 0) {
|
|
13013
|
+
return { args: [], delegationClause: "", configPaths: [], warnings: [], liveDefs: [] };
|
|
13014
|
+
}
|
|
13015
|
+
if ("clientVersion" in options2 && !codexNativeAgentsSupported(options2.clientVersion ?? null)) {
|
|
13016
|
+
const seen2 = options2.clientVersion ?? "tak terdeteksi";
|
|
13017
|
+
return {
|
|
13018
|
+
args: [],
|
|
13019
|
+
delegationClause: "",
|
|
13020
|
+
configPaths: [],
|
|
13021
|
+
liveDefs: [],
|
|
13022
|
+
warnings: defs.map((def2) => ({
|
|
13023
|
+
agentName: def2.name,
|
|
13024
|
+
reason: `Codex ${seen2} tidak mendukung custom agent native; butuh >= ${CODEX_NATIVE_AGENTS_MIN_CLIENT}`
|
|
13025
|
+
}))
|
|
13026
|
+
};
|
|
13027
|
+
}
|
|
13028
|
+
const write2 = options2.writeFile ?? ((path, content) => writeFileSync2(path, content, { mode: 384 }));
|
|
13029
|
+
const chmod3 = options2.chmod ?? chmodSync2;
|
|
13030
|
+
const successful = [];
|
|
13031
|
+
const warnings = [];
|
|
13032
|
+
for (const [index, def2] of defs.entries()) {
|
|
13033
|
+
if (def2.workspacePolicy === "isolated-worktree") {
|
|
13034
|
+
warnings.push({
|
|
13035
|
+
agentName: def2.name,
|
|
13036
|
+
reason: "isolated-worktree belum tersedia untuk subagent Codex"
|
|
13037
|
+
});
|
|
13038
|
+
continue;
|
|
13039
|
+
}
|
|
13040
|
+
const path = join2(tempDir, `${String(index).padStart(2, "0")}-${safeFilename(def2.name)}.toml`);
|
|
13041
|
+
try {
|
|
13042
|
+
write2(path, renderCodexAgentToml(def2, defs, options2));
|
|
13043
|
+
chmod3(path, 384);
|
|
13044
|
+
successful.push({ def: def2, path });
|
|
13045
|
+
} catch (error) {
|
|
13046
|
+
warnings.push({
|
|
13047
|
+
agentName: def2.name,
|
|
13048
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
13049
|
+
});
|
|
13050
|
+
}
|
|
13051
|
+
}
|
|
13052
|
+
if (successful.length === 0) {
|
|
13053
|
+
return { args: [], delegationClause: "", configPaths: [], warnings, liveDefs: [] };
|
|
13054
|
+
}
|
|
13055
|
+
const args = [
|
|
13056
|
+
"-c",
|
|
13057
|
+
"agents.enabled=true",
|
|
13058
|
+
"-c",
|
|
13059
|
+
"agents.max_concurrent_threads_per_session=3"
|
|
13060
|
+
];
|
|
13061
|
+
for (const { def: def2, path } of successful) {
|
|
13062
|
+
const key = `agents.${tomlKey(def2.name)}`;
|
|
13063
|
+
args.push("-c", `${key}.description=${tomlString(def2.description)}`);
|
|
13064
|
+
args.push("-c", `${key}.config_file=${tomlString(path)}`);
|
|
13065
|
+
}
|
|
13066
|
+
const liveDefs = successful.map((entry) => entry.def);
|
|
13067
|
+
return {
|
|
13068
|
+
args,
|
|
13069
|
+
delegationClause: agentDelegationClause(liveDefs, "codex"),
|
|
13070
|
+
configPaths: successful.map((entry) => entry.path),
|
|
13071
|
+
warnings,
|
|
13072
|
+
liveDefs
|
|
13073
|
+
};
|
|
13074
|
+
}
|
|
13075
|
+
var CODEX_NATIVE_AGENTS_MIN_CLIENT, shellQuote2, tomlString, tomlKey, safeFilename;
|
|
13076
|
+
var init_codex_agent_config = __esm({
|
|
13077
|
+
"../runner/src/codex-agent-config.ts"() {
|
|
13078
|
+
"use strict";
|
|
13079
|
+
init_src();
|
|
13080
|
+
init_custom_agents();
|
|
13081
|
+
init_runtime_profile();
|
|
13082
|
+
CODEX_NATIVE_AGENTS_MIN_CLIENT = "0.151.0";
|
|
13083
|
+
shellQuote2 = (value) => `'${value.replaceAll("'", "'\\''")}'`;
|
|
13084
|
+
tomlString = (value) => JSON.stringify(value);
|
|
13085
|
+
tomlKey = (value) => JSON.stringify(value);
|
|
13086
|
+
safeFilename = (name2) => name2.replace(/[^a-z0-9-]/gi, "-");
|
|
13087
|
+
}
|
|
13088
|
+
});
|
|
13089
|
+
|
|
13090
|
+
// ../runner/src/custom-agent-eval.ts
|
|
13091
|
+
var init_custom_agent_eval = __esm({
|
|
13092
|
+
"../runner/src/custom-agent-eval.ts"() {
|
|
13093
|
+
"use strict";
|
|
13094
|
+
init_src();
|
|
13095
|
+
init_codex_agent_config();
|
|
13096
|
+
init_codex_settings();
|
|
13097
|
+
init_custom_agents();
|
|
13098
|
+
init_settings2();
|
|
12644
13099
|
}
|
|
12645
13100
|
});
|
|
12646
13101
|
|
|
12647
13102
|
// ../runner/src/paths.ts
|
|
12648
13103
|
import { homedir } from "node:os";
|
|
12649
|
-
import { dirname as dirname2, isAbsolute as isAbsolute2, join, resolve as resolve5 } from "node:path";
|
|
13104
|
+
import { dirname as dirname2, isAbsolute as isAbsolute2, join as join3, resolve as resolve5 } from "node:path";
|
|
12650
13105
|
function resolveHome(env = process.env, home3 = homedir()) {
|
|
12651
13106
|
const v = env.HANOMAN_HOME?.trim();
|
|
12652
|
-
return v ? v :
|
|
13107
|
+
return v ? v : join3(home3, ".hanoman");
|
|
12653
13108
|
}
|
|
12654
13109
|
function resolveDataDirs(env = process.env, home3 = homedir()) {
|
|
12655
13110
|
const root = resolveHome(env, home3);
|
|
12656
|
-
const dir2 = (key, name2) => env[key]?.trim() ||
|
|
13111
|
+
const dir2 = (key, name2) => env[key]?.trim() || join3(root, name2);
|
|
12657
13112
|
return {
|
|
12658
13113
|
home: root,
|
|
12659
13114
|
transcripts: dir2("HANOMAN_TRANSCRIPT_DIR", "transcripts"),
|
|
@@ -12675,7 +13130,7 @@ function resolveDbUrl(env, schemaDir2) {
|
|
|
12675
13130
|
return absoluteFileUrl(own, schemaDir2);
|
|
12676
13131
|
}
|
|
12677
13132
|
const raw = env.DATABASE_URL?.trim();
|
|
12678
|
-
if (!raw || !raw.startsWith("file:")) return `file:${
|
|
13133
|
+
if (!raw || !raw.startsWith("file:")) return `file:${join3(resolveHome(env), "hanoman.db")}`;
|
|
12679
13134
|
return absoluteFileUrl(raw, schemaDir2);
|
|
12680
13135
|
}
|
|
12681
13136
|
function absoluteFileUrl(raw, schemaDir2) {
|
|
@@ -12703,7 +13158,7 @@ var init_paths = __esm({
|
|
|
12703
13158
|
|
|
12704
13159
|
// ../runner/src/skills.ts
|
|
12705
13160
|
import { homedir as homedir2 } from "node:os";
|
|
12706
|
-
import { basename as basename2, dirname as dirname3, join as
|
|
13161
|
+
import { basename as basename2, dirname as dirname3, join as join4, resolve as resolve6 } from "node:path";
|
|
12707
13162
|
import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync2 } from "node:fs";
|
|
12708
13163
|
function agentSkillHome(agent, env = process.env, osHome = homedir2()) {
|
|
12709
13164
|
const own = (agent === "codex" ? env.HANOMAN_CODEX_HOME : env.HANOMAN_CLAUDE_HOME)?.trim();
|
|
@@ -12712,10 +13167,10 @@ function agentSkillHome(agent, env = process.env, osHome = homedir2()) {
|
|
|
12712
13167
|
const cx = env.CODEX_HOME?.trim();
|
|
12713
13168
|
if (cx) return cx;
|
|
12714
13169
|
}
|
|
12715
|
-
return
|
|
13170
|
+
return join4(osHome, agent === "codex" ? ".codex" : ".claude");
|
|
12716
13171
|
}
|
|
12717
13172
|
function agentsSkillHome(env = process.env, osHome = homedir2()) {
|
|
12718
|
-
return env.HANOMAN_AGENTS_HOME?.trim() ||
|
|
13173
|
+
return env.HANOMAN_AGENTS_HOME?.trim() || join4(osHome, ".agents");
|
|
12719
13174
|
}
|
|
12720
13175
|
function dirsIn(dir2) {
|
|
12721
13176
|
try {
|
|
@@ -12732,7 +13187,7 @@ function readJson(file) {
|
|
|
12732
13187
|
}
|
|
12733
13188
|
}
|
|
12734
13189
|
function isSkillDir(dir2) {
|
|
12735
|
-
return existsSync3(
|
|
13190
|
+
return existsSync3(join4(dir2, "SKILL.md"));
|
|
12736
13191
|
}
|
|
12737
13192
|
function asSkill(dir2, pkg, name2 = basename2(dir2)) {
|
|
12738
13193
|
return { id: pkg ? `${pkg}:${name2}` : name2, name: name2, pkg, dir: dir2 };
|
|
@@ -12740,7 +13195,7 @@ function asSkill(dir2, pkg, name2 = basename2(dir2)) {
|
|
|
12740
13195
|
function skillsUnder(dir2, pkg, depth = 2) {
|
|
12741
13196
|
const out4 = [];
|
|
12742
13197
|
for (const name2 of dirsIn(dir2)) {
|
|
12743
|
-
const sub =
|
|
13198
|
+
const sub = join4(dir2, name2);
|
|
12744
13199
|
if (isSkillDir(sub)) out4.push(asSkill(sub, pkg, name2));
|
|
12745
13200
|
else if (depth > 1) out4.push(...skillsUnder(sub, pkg, depth - 1));
|
|
12746
13201
|
}
|
|
@@ -12748,7 +13203,7 @@ function skillsUnder(dir2, pkg, depth = 2) {
|
|
|
12748
13203
|
}
|
|
12749
13204
|
function manifestSkills(installPath, pkg) {
|
|
12750
13205
|
for (const marker of [".claude-plugin", ".codex-plugin"]) {
|
|
12751
|
-
const j = readJson(
|
|
13206
|
+
const j = readJson(join4(installPath, marker, "plugin.json"));
|
|
12752
13207
|
const declared = j?.skills;
|
|
12753
13208
|
if (!Array.isArray(declared)) continue;
|
|
12754
13209
|
const out4 = [];
|
|
@@ -12764,11 +13219,11 @@ function manifestSkills(installPath, pkg) {
|
|
|
12764
13219
|
}
|
|
12765
13220
|
function pluginSkills(installPath, pkg) {
|
|
12766
13221
|
const declared = manifestSkills(installPath, pkg);
|
|
12767
|
-
return declared.length ? declared : skillsUnder(
|
|
13222
|
+
return declared.length ? declared : skillsUnder(join4(installPath, "skills"), pkg);
|
|
12768
13223
|
}
|
|
12769
13224
|
function lockPluginNames(agentsHome) {
|
|
12770
13225
|
const out4 = /* @__PURE__ */ new Map();
|
|
12771
|
-
const j = readJson(
|
|
13226
|
+
const j = readJson(join4(agentsHome, ".skill-lock.json"));
|
|
12772
13227
|
const skills = j?.skills;
|
|
12773
13228
|
if (!skills || typeof skills !== "object") return out4;
|
|
12774
13229
|
for (const [name2, entry] of Object.entries(skills)) {
|
|
@@ -12782,7 +13237,7 @@ function splitPluginKey(key) {
|
|
|
12782
13237
|
return at > 0 ? { pkg: key.slice(0, at), marketplace: key.slice(at + 1) } : { pkg: key, marketplace: "" };
|
|
12783
13238
|
}
|
|
12784
13239
|
function manifestRoots(home3) {
|
|
12785
|
-
const j = readJson(
|
|
13240
|
+
const j = readJson(join4(home3, "plugins", "installed_plugins.json"));
|
|
12786
13241
|
const plugins = j?.plugins;
|
|
12787
13242
|
if (!plugins || typeof plugins !== "object") return [];
|
|
12788
13243
|
const out4 = [];
|
|
@@ -12796,18 +13251,18 @@ function manifestRoots(home3) {
|
|
|
12796
13251
|
return out4;
|
|
12797
13252
|
}
|
|
12798
13253
|
function cacheRoots(home3) {
|
|
12799
|
-
const cache6 =
|
|
13254
|
+
const cache6 = join4(home3, "plugins", "cache");
|
|
12800
13255
|
const out4 = [];
|
|
12801
13256
|
for (const marketplace of dirsIn(cache6))
|
|
12802
|
-
for (const pkg of dirsIn(
|
|
12803
|
-
for (const version of dirsIn(
|
|
12804
|
-
out4.push({ pkg, marketplace, dir:
|
|
13257
|
+
for (const pkg of dirsIn(join4(cache6, marketplace)))
|
|
13258
|
+
for (const version of dirsIn(join4(cache6, marketplace, pkg)))
|
|
13259
|
+
out4.push({ pkg, marketplace, dir: join4(cache6, marketplace, pkg, version) });
|
|
12805
13260
|
return out4;
|
|
12806
13261
|
}
|
|
12807
13262
|
function disabledPlugins(agent, home3) {
|
|
12808
13263
|
const out4 = /* @__PURE__ */ new Set();
|
|
12809
13264
|
if (agent === "claude") {
|
|
12810
|
-
const en = readJson(
|
|
13265
|
+
const en = readJson(join4(home3, "settings.json"))?.enabledPlugins;
|
|
12811
13266
|
if (en && typeof en === "object") {
|
|
12812
13267
|
for (const [k, v] of Object.entries(en)) if (v === false) out4.add(k);
|
|
12813
13268
|
}
|
|
@@ -12815,7 +13270,7 @@ function disabledPlugins(agent, home3) {
|
|
|
12815
13270
|
}
|
|
12816
13271
|
let toml;
|
|
12817
13272
|
try {
|
|
12818
|
-
toml = readFileSync2(
|
|
13273
|
+
toml = readFileSync2(join4(home3, "config.toml"), "utf8");
|
|
12819
13274
|
} catch {
|
|
12820
13275
|
return out4;
|
|
12821
13276
|
}
|
|
@@ -12851,18 +13306,18 @@ function scanAgentSkills(agent, env = process.env, osHome = homedir2()) {
|
|
|
12851
13306
|
skills.push(s2);
|
|
12852
13307
|
}
|
|
12853
13308
|
};
|
|
12854
|
-
const userDir =
|
|
13309
|
+
const userDir = join4(home3, "skills");
|
|
12855
13310
|
add(userDir, skillsUnder(userDir, null));
|
|
12856
13311
|
for (const r of [...manifestRoots(home3), ...cacheRoots(home3)]) {
|
|
12857
13312
|
if (disabled.has(`${r.pkg}@${r.marketplace}`)) continue;
|
|
12858
13313
|
const found = pluginSkills(r.dir, r.pkg);
|
|
12859
13314
|
if (!found.length) continue;
|
|
12860
13315
|
packages.add(r.pkg);
|
|
12861
|
-
add(
|
|
13316
|
+
add(join4(r.dir, "skills"), found);
|
|
12862
13317
|
}
|
|
12863
13318
|
if (agent === "codex") {
|
|
12864
13319
|
const agentsHome = agentsSkillHome(env, osHome);
|
|
12865
|
-
const dir2 =
|
|
13320
|
+
const dir2 = join4(agentsHome, "skills");
|
|
12866
13321
|
const names = lockPluginNames(agentsHome);
|
|
12867
13322
|
const flat = skillsUnder(dir2, null);
|
|
12868
13323
|
const withPkg = flat.flatMap((s2) => {
|
|
@@ -12953,12 +13408,12 @@ var init_telegram_operator = __esm({
|
|
|
12953
13408
|
});
|
|
12954
13409
|
|
|
12955
13410
|
// ../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
|
|
13411
|
+
import { readdirSync as readdirSync2, existsSync as existsSync4, statSync, chmodSync as chmodSync3 } from "node:fs";
|
|
13412
|
+
import { dirname as dirname4, join as join5 } from "node:path";
|
|
12958
13413
|
function spawnHelperPaths(ptyDir, listDir, exists) {
|
|
12959
|
-
const dirs = [
|
|
12960
|
-
for (const name2 of listDir(
|
|
12961
|
-
return dirs.map((d) =>
|
|
13414
|
+
const dirs = [join5(ptyDir, "build", "Release")];
|
|
13415
|
+
for (const name2 of listDir(join5(ptyDir, "prebuilds"))) dirs.push(join5(ptyDir, "prebuilds", name2));
|
|
13416
|
+
return dirs.map((d) => join5(d, "spawn-helper")).filter(exists);
|
|
12962
13417
|
}
|
|
12963
13418
|
function ensureSpawnHelpersExecutable(paths2, ops) {
|
|
12964
13419
|
const fixed = [];
|
|
@@ -12973,10 +13428,10 @@ function ensureSpawnHelpersExecutable(paths2, ops) {
|
|
|
12973
13428
|
}
|
|
12974
13429
|
return fixed;
|
|
12975
13430
|
}
|
|
12976
|
-
function repairSpawnHelper(
|
|
13431
|
+
function repairSpawnHelper(resolve22, notify) {
|
|
12977
13432
|
let ptyDir;
|
|
12978
13433
|
try {
|
|
12979
|
-
ptyDir = dirname4(
|
|
13434
|
+
ptyDir = dirname4(resolve22("node-pty/package.json"));
|
|
12980
13435
|
} catch {
|
|
12981
13436
|
return [];
|
|
12982
13437
|
}
|
|
@@ -12989,15 +13444,15 @@ function repairSpawnHelper(resolve21, notify) {
|
|
|
12989
13444
|
}, existsSync4);
|
|
12990
13445
|
const fixed = ensureSpawnHelpersExecutable(paths2, {
|
|
12991
13446
|
mode: (p3) => statSync(p3).mode,
|
|
12992
|
-
chmod: (p3, m) =>
|
|
13447
|
+
chmod: (p3, m) => chmodSync3(p3, m)
|
|
12993
13448
|
});
|
|
12994
13449
|
if (fixed.length) notify?.("hanoman \xB7 memperbaiki izin `spawn-helper` node-pty (sekali per instalasi)\n");
|
|
12995
13450
|
return fixed;
|
|
12996
13451
|
}
|
|
12997
|
-
function ensureSpawnHelperOnce(
|
|
13452
|
+
function ensureSpawnHelperOnce(resolve22, notify) {
|
|
12998
13453
|
if (repaired) return [];
|
|
12999
13454
|
repaired = true;
|
|
13000
|
-
return repairSpawnHelper(
|
|
13455
|
+
return repairSpawnHelper(resolve22, notify);
|
|
13001
13456
|
}
|
|
13002
13457
|
var repaired;
|
|
13003
13458
|
var init_spawn_helper = __esm({
|
|
@@ -13008,10 +13463,10 @@ var init_spawn_helper = __esm({
|
|
|
13008
13463
|
});
|
|
13009
13464
|
|
|
13010
13465
|
// ../runner/src/config-env.ts
|
|
13011
|
-
import { chmodSync as
|
|
13012
|
-
import { join as
|
|
13466
|
+
import { chmodSync as chmodSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
13467
|
+
import { join as join6 } from "node:path";
|
|
13013
13468
|
function configEnvPath(home3) {
|
|
13014
|
-
return
|
|
13469
|
+
return join6(home3, CONFIG_ENV_FILE);
|
|
13015
13470
|
}
|
|
13016
13471
|
function parseConfigEnv(text) {
|
|
13017
13472
|
const out4 = {};
|
|
@@ -13039,8 +13494,8 @@ function readConfigEnv(home3) {
|
|
|
13039
13494
|
function writeConfigEnv(home3, values) {
|
|
13040
13495
|
mkdirSync2(home3, { recursive: true, mode: 448 });
|
|
13041
13496
|
const path = configEnvPath(home3);
|
|
13042
|
-
|
|
13043
|
-
|
|
13497
|
+
writeFileSync3(path, formatConfigEnv(values), { mode: 384 });
|
|
13498
|
+
chmodSync4(path, 384);
|
|
13044
13499
|
}
|
|
13045
13500
|
var CONFIG_ENV_FILE;
|
|
13046
13501
|
var init_config_env = __esm({
|
|
@@ -13050,23 +13505,6 @@ var init_config_env = __esm({
|
|
|
13050
13505
|
}
|
|
13051
13506
|
});
|
|
13052
13507
|
|
|
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
13508
|
// ../runner/src/sandbox-probe.ts
|
|
13071
13509
|
import { execFileSync } from "node:child_process";
|
|
13072
13510
|
import { accessSync, constants } from "node:fs";
|
|
@@ -13179,6 +13617,9 @@ var init_src2 = __esm({
|
|
|
13179
13617
|
init_codex_settings();
|
|
13180
13618
|
init_agent_cli();
|
|
13181
13619
|
init_custom_agents();
|
|
13620
|
+
init_agent_readonly();
|
|
13621
|
+
init_codex_agent_config();
|
|
13622
|
+
init_custom_agent_eval();
|
|
13182
13623
|
init_verify_scope();
|
|
13183
13624
|
init_code_style();
|
|
13184
13625
|
init_paths();
|
|
@@ -13465,9 +13906,9 @@ var init_session_id = __esm({
|
|
|
13465
13906
|
});
|
|
13466
13907
|
|
|
13467
13908
|
// src/services/secret-box.ts
|
|
13468
|
-
import { chmodSync as
|
|
13909
|
+
import { chmodSync as chmodSync5, existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
|
|
13469
13910
|
import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
|
|
13470
|
-
import { dirname as dirname6, join as
|
|
13911
|
+
import { dirname as dirname6, join as join7 } from "node:path";
|
|
13471
13912
|
function isEncrypted(value) {
|
|
13472
13913
|
return value.startsWith(ENC_PREFIX);
|
|
13473
13914
|
}
|
|
@@ -13499,7 +13940,7 @@ function fromEnv(raw) {
|
|
|
13499
13940
|
return null;
|
|
13500
13941
|
}
|
|
13501
13942
|
function secretKeyPath() {
|
|
13502
|
-
return
|
|
13943
|
+
return join7(resolveHome(), "secret.key");
|
|
13503
13944
|
}
|
|
13504
13945
|
function secretKey() {
|
|
13505
13946
|
if (cached) return cached;
|
|
@@ -13519,8 +13960,8 @@ function secretKey() {
|
|
|
13519
13960
|
}
|
|
13520
13961
|
const key = randomBytes(KEY_BYTES);
|
|
13521
13962
|
mkdirSync4(dirname6(path), { recursive: true });
|
|
13522
|
-
|
|
13523
|
-
|
|
13963
|
+
writeFileSync4(path, key.toString("base64url"), { mode: 384 });
|
|
13964
|
+
chmodSync5(path, 384);
|
|
13524
13965
|
cached = key;
|
|
13525
13966
|
return key;
|
|
13526
13967
|
}
|
|
@@ -13713,7 +14154,7 @@ import { request as httpRequest } from "node:http";
|
|
|
13713
14154
|
import { request as httpsRequest } from "node:https";
|
|
13714
14155
|
import { createGunzip } from "node:zlib";
|
|
13715
14156
|
async function pinnedRequest(input) {
|
|
13716
|
-
return new Promise((
|
|
14157
|
+
return new Promise((resolve22, reject2) => {
|
|
13717
14158
|
const transport = input.url.protocol === "https:" ? httpsRequest : httpRequest;
|
|
13718
14159
|
const req = transport({
|
|
13719
14160
|
protocol: input.url.protocol,
|
|
@@ -13735,7 +14176,7 @@ async function pinnedRequest(input) {
|
|
|
13735
14176
|
const capTerurai = input.maxDecodedBytes ?? input.maxResponseBytes;
|
|
13736
14177
|
const chunks = [];
|
|
13737
14178
|
let kabel = 0, terurai = 0;
|
|
13738
|
-
const selesai = () =>
|
|
14179
|
+
const selesai = () => resolve22({
|
|
13739
14180
|
status: response.statusCode ?? 0,
|
|
13740
14181
|
headers: response.headers,
|
|
13741
14182
|
body: Buffer.concat(chunks)
|
|
@@ -13801,7 +14242,7 @@ var init_safe_outbound_request = __esm({
|
|
|
13801
14242
|
// src/services/uploads.ts
|
|
13802
14243
|
import { randomUUID } from "node:crypto";
|
|
13803
14244
|
import { mkdir, writeFile, readFile as readFile2, unlink, rm } from "node:fs/promises";
|
|
13804
|
-
import { join as
|
|
14245
|
+
import { join as join8, resolve as resolve8 } from "node:path";
|
|
13805
14246
|
function extFor(mimeType) {
|
|
13806
14247
|
return EXT[mimeType] ?? ".bin";
|
|
13807
14248
|
}
|
|
@@ -13810,12 +14251,12 @@ function uploadDir() {
|
|
|
13810
14251
|
}
|
|
13811
14252
|
function sessionUploadDir(sessionId2) {
|
|
13812
14253
|
if (!SESSION_ID.test(sessionId2)) throw new Error(`sessionId tak sah: ${sessionId2}`);
|
|
13813
|
-
return
|
|
14254
|
+
return join8(uploadDir(), "terminal", sessionId2);
|
|
13814
14255
|
}
|
|
13815
14256
|
async function saveSessionUpload(sessionId2, buf, mimeType) {
|
|
13816
14257
|
const dir2 = sessionUploadDir(sessionId2);
|
|
13817
14258
|
await mkdir(dir2, { recursive: true, mode: 448 });
|
|
13818
|
-
const path =
|
|
14259
|
+
const path = join8(dir2, `${randomUUID()}${extFor(mimeType)}`);
|
|
13819
14260
|
await writeFile(path, buf, { mode: 384 });
|
|
13820
14261
|
return { path, size: buf.length };
|
|
13821
14262
|
}
|
|
@@ -13831,11 +14272,11 @@ async function dropSessionUploads(sessionId2) {
|
|
|
13831
14272
|
}
|
|
13832
14273
|
async function readUpload(storageKey) {
|
|
13833
14274
|
const safe = storageKey.replace(/[/\\]/g, "");
|
|
13834
|
-
return readFile2(
|
|
14275
|
+
return readFile2(join8(uploadDir(), safe));
|
|
13835
14276
|
}
|
|
13836
14277
|
async function readUploadOrFetch(storageKey) {
|
|
13837
14278
|
const safe = storageKey.replace(/[/\\]/g, "");
|
|
13838
|
-
const target2 =
|
|
14279
|
+
const target2 = join8(uploadDir(), safe);
|
|
13839
14280
|
try {
|
|
13840
14281
|
return await readFile2(target2);
|
|
13841
14282
|
} catch {
|
|
@@ -13862,7 +14303,7 @@ async function readUploadOrFetch(storageKey) {
|
|
|
13862
14303
|
}
|
|
13863
14304
|
async function deleteUpload(storageKey) {
|
|
13864
14305
|
const safe = storageKey.replace(/[/\\]/g, "");
|
|
13865
|
-
await unlink(
|
|
14306
|
+
await unlink(join8(uploadDir(), safe)).catch(() => {
|
|
13866
14307
|
});
|
|
13867
14308
|
}
|
|
13868
14309
|
var EXT, SESSION_ID;
|
|
@@ -14211,11 +14652,15 @@ function sandboxArgv(input) {
|
|
|
14211
14652
|
const mounts = ["--volume", `${input.worktree}:/workspace:${input.worktreeMode ?? "rw"}`];
|
|
14212
14653
|
if (input.phaseFile) mounts.push("--volume", `${input.phaseFile}:${input.phaseFile}:rw`);
|
|
14213
14654
|
if (input.promptFile) mounts.push("--volume", `${input.promptFile}:${input.promptFile}:ro`);
|
|
14655
|
+
if (input.agentConfigDir)
|
|
14656
|
+
mounts.push("--volume", `${input.agentConfigDir}:${input.agentConfigDir}:ro`);
|
|
14657
|
+
if (input.eventDir)
|
|
14658
|
+
mounts.push("--volume", `${input.eventDir}:${input.eventDir}:rw`);
|
|
14214
14659
|
if (input.attachmentsDir)
|
|
14215
14660
|
mounts.push("--volume", `${input.attachmentsDir}:${input.attachmentsDir}:ro`);
|
|
14216
14661
|
mounts.push("--volume", `${input.credentialDir}:/agent-home:ro`);
|
|
14217
14662
|
return [
|
|
14218
|
-
"podman",
|
|
14663
|
+
input.podmanBin ?? "podman",
|
|
14219
14664
|
"run",
|
|
14220
14665
|
"--rm",
|
|
14221
14666
|
"--read-only",
|
|
@@ -14243,6 +14688,7 @@ function sandboxArgv(input) {
|
|
|
14243
14688
|
"NO_PROXY=localhost,127.0.0.1,::1",
|
|
14244
14689
|
"--env",
|
|
14245
14690
|
"HOME=/agent-home",
|
|
14691
|
+
...input.eventDir ? ["--env", `HANOMAN_EVENT_DIR=${input.eventDir}`] : [],
|
|
14246
14692
|
...mounts,
|
|
14247
14693
|
input.image,
|
|
14248
14694
|
"/bin/sh",
|
|
@@ -14262,6 +14708,7 @@ function sandboxArgvFromEnv(input) {
|
|
|
14262
14708
|
...input,
|
|
14263
14709
|
credentialDir,
|
|
14264
14710
|
proxy,
|
|
14711
|
+
podmanBin: env.HANOMAN_PODMAN_BIN ?? "podman",
|
|
14265
14712
|
image: env.HANOMAN_SESSION_IMAGE ?? "hanoman-agent:latest",
|
|
14266
14713
|
network: env.HANOMAN_SESSION_NETWORK ?? "hanoman-egress"
|
|
14267
14714
|
});
|
|
@@ -14280,18 +14727,30 @@ var init_session_sandbox = __esm({
|
|
|
14280
14727
|
}
|
|
14281
14728
|
});
|
|
14282
14729
|
|
|
14730
|
+
// src/services/session-event-spool.ts
|
|
14731
|
+
import { tmpdir } from "node:os";
|
|
14732
|
+
import { join as join9 } from "node:path";
|
|
14733
|
+
var sessionEventSpoolRoot, sessionEventDir;
|
|
14734
|
+
var init_session_event_spool = __esm({
|
|
14735
|
+
"src/services/session-event-spool.ts"() {
|
|
14736
|
+
"use strict";
|
|
14737
|
+
sessionEventSpoolRoot = () => join9(tmpdir(), "hanoman-session-events");
|
|
14738
|
+
sessionEventDir = (sessionId2) => join9(sessionEventSpoolRoot(), sessionId2);
|
|
14739
|
+
}
|
|
14740
|
+
});
|
|
14741
|
+
|
|
14283
14742
|
// src/services/pty.ts
|
|
14284
14743
|
import { spawn } from "node-pty";
|
|
14285
14744
|
import { execFile, execFileSync as execFileSync2 } from "node:child_process";
|
|
14286
14745
|
import { createRequire } from "node:module";
|
|
14287
14746
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
14288
|
-
import { mkdirSync as mkdirSync5, readFileSync as readFileSync6, statSync as statSync2, writeFileSync as
|
|
14747
|
+
import { chmodSync as chmodSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync6, rmSync as rmSync2, statSync as statSync2, writeFileSync as writeFileSync5 } from "node:fs";
|
|
14289
14748
|
import { dirname as dirname7 } from "node:path";
|
|
14290
|
-
import { tmpdir } from "node:os";
|
|
14749
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
14291
14750
|
function noTtyPromptEnv() {
|
|
14292
14751
|
const path = askpassDenyPath();
|
|
14293
14752
|
mkdirSync5(dirname7(path), { recursive: true, mode: 448 });
|
|
14294
|
-
|
|
14753
|
+
writeFileSync5(path, ASKPASS_DENY, { mode: 448 });
|
|
14295
14754
|
return { SSH_ASKPASS: path, SSH_ASKPASS_REQUIRE: "force", GIT_TERMINAL_PROMPT: "0" };
|
|
14296
14755
|
}
|
|
14297
14756
|
function tmux(...args) {
|
|
@@ -14310,14 +14769,14 @@ function tmux(...args) {
|
|
|
14310
14769
|
}
|
|
14311
14770
|
}
|
|
14312
14771
|
function tmuxAsync(...args) {
|
|
14313
|
-
return new Promise((
|
|
14772
|
+
return new Promise((resolve22, reject2) => {
|
|
14314
14773
|
execFile(
|
|
14315
14774
|
"tmux",
|
|
14316
14775
|
["-L", socket(), "-f", "/dev/null", ...args],
|
|
14317
14776
|
{ encoding: "utf8" },
|
|
14318
14777
|
(e, stdout, stderr) => {
|
|
14319
14778
|
if (!e) {
|
|
14320
|
-
|
|
14779
|
+
resolve22(stdout);
|
|
14321
14780
|
return;
|
|
14322
14781
|
}
|
|
14323
14782
|
const err = e;
|
|
@@ -14368,7 +14827,8 @@ function parsePanes(out4) {
|
|
|
14368
14827
|
alternate,
|
|
14369
14828
|
activity,
|
|
14370
14829
|
eventHook,
|
|
14371
|
-
created
|
|
14830
|
+
created,
|
|
14831
|
+
agentRoster
|
|
14372
14832
|
] = line.split(" ");
|
|
14373
14833
|
if (!n2?.startsWith(PREFIX)) return [];
|
|
14374
14834
|
const exited = dead === "1";
|
|
@@ -14398,10 +14858,30 @@ function parsePanes(out4) {
|
|
|
14398
14858
|
// SPEC-919 · sesi yang lahir di tmux yang tak menjawab field ini → 0 (epoch).
|
|
14399
14859
|
startedAt: Number(created) || 0,
|
|
14400
14860
|
// SPEC-909 · ADR-0146 · sesi yang lahir sebelum pembaruan tak punya opsi ini → false.
|
|
14401
|
-
eventHook: eventHook === "1"
|
|
14861
|
+
eventHook: eventHook === "1",
|
|
14862
|
+
agentRoster: parseAgentRoster(agentRoster)
|
|
14402
14863
|
}];
|
|
14403
14864
|
});
|
|
14404
14865
|
}
|
|
14866
|
+
function parseAgentRoster(value) {
|
|
14867
|
+
try {
|
|
14868
|
+
const parsed = JSON.parse(value || "[]");
|
|
14869
|
+
if (!Array.isArray(parsed)) return [];
|
|
14870
|
+
return parsed.flatMap((entry) => {
|
|
14871
|
+
if (!entry || typeof entry !== "object") return [];
|
|
14872
|
+
const row = entry;
|
|
14873
|
+
if (typeof row.name !== "string") return [];
|
|
14874
|
+
return [{
|
|
14875
|
+
name: row.name,
|
|
14876
|
+
...typeof row.id === "string" ? { id: row.id } : {},
|
|
14877
|
+
...typeof row.model === "string" ? { model: row.model } : {},
|
|
14878
|
+
...typeof row.timeoutSeconds === "number" ? { timeoutSeconds: row.timeoutSeconds } : {}
|
|
14879
|
+
}];
|
|
14880
|
+
});
|
|
14881
|
+
} catch {
|
|
14882
|
+
return [];
|
|
14883
|
+
}
|
|
14884
|
+
}
|
|
14405
14885
|
function sessionEventEnv(sessionId2, env = process.env) {
|
|
14406
14886
|
const port2 = Number(env.PORT ?? 8787);
|
|
14407
14887
|
const host2 = controlHost(loadIngressPolicy(env));
|
|
@@ -14409,6 +14889,7 @@ function sessionEventEnv(sessionId2, env = process.env) {
|
|
|
14409
14889
|
HANOMAN_SESSION_ID: sessionId2,
|
|
14410
14890
|
HANOMAN_EVENT_URL: `http://127.0.0.1:${port2}/api/session-events`,
|
|
14411
14891
|
HANOMAN_EVENT_TOKEN: sessionEventToken(sessionId2),
|
|
14892
|
+
HANOMAN_EVENT_DIR: sessionEventDir(sessionId2),
|
|
14412
14893
|
...host2 ? { HANOMAN_EVENT_HOST: host2 } : {}
|
|
14413
14894
|
};
|
|
14414
14895
|
}
|
|
@@ -14418,6 +14899,24 @@ function registerSessionHooks(h) {
|
|
|
14418
14899
|
function registerCustomAgentSource(fn) {
|
|
14419
14900
|
customAgentSource = fn;
|
|
14420
14901
|
}
|
|
14902
|
+
function registerCodexNativeAgentSupport(fn) {
|
|
14903
|
+
codexNativeAgentSupport = fn;
|
|
14904
|
+
}
|
|
14905
|
+
function collectChangedFiles(cwd, baseSha, run4 = runGitDiff) {
|
|
14906
|
+
try {
|
|
14907
|
+
const files = /* @__PURE__ */ new Set();
|
|
14908
|
+
const add = (args) => {
|
|
14909
|
+
for (const path of run4(cwd, args).split("\0")) if (path) files.add(path);
|
|
14910
|
+
};
|
|
14911
|
+
if (baseSha) add(["diff", "--name-only", "-z", `${baseSha}...HEAD`]);
|
|
14912
|
+
add(["diff", "--name-only", "-z"]);
|
|
14913
|
+
add(["diff", "--cached", "--name-only", "-z"]);
|
|
14914
|
+
add(["ls-files", "--others", "--exclude-standard", "-z"]);
|
|
14915
|
+
return [...files].sort();
|
|
14916
|
+
} catch {
|
|
14917
|
+
return [];
|
|
14918
|
+
}
|
|
14919
|
+
}
|
|
14421
14920
|
function sessionKind(o, projectId, cwd) {
|
|
14422
14921
|
if (o.specId) return "spec";
|
|
14423
14922
|
if (o.flow === "reverse" || o.flow === "prd" || o.flow === "scaffold" || o.flow === "breakdown") return o.flow;
|
|
@@ -14440,15 +14939,62 @@ function createSession(projectId, cwd, opts = {}) {
|
|
|
14440
14939
|
const existing = getSession(id);
|
|
14441
14940
|
if (existing && !existing.exited) return existing;
|
|
14442
14941
|
if (existing) killSession(id);
|
|
14942
|
+
const eventDir = opts.command ? void 0 : sessionEventDir(id);
|
|
14943
|
+
if (eventDir) {
|
|
14944
|
+
mkdirSync5(eventDir, { recursive: true, mode: 448 });
|
|
14945
|
+
chmodSync6(eventDir, 448);
|
|
14946
|
+
}
|
|
14443
14947
|
const agentForDefs = opts.agent ?? "claude";
|
|
14444
|
-
const
|
|
14445
|
-
|
|
14948
|
+
const selectionContext = {
|
|
14949
|
+
projectId,
|
|
14950
|
+
runtime: agentForDefs,
|
|
14951
|
+
flow: opts.flow,
|
|
14952
|
+
cwd,
|
|
14953
|
+
baseSha: opts.env?.HANOMAN_BASE_SHA,
|
|
14954
|
+
prompt: opts.prompt,
|
|
14955
|
+
changedFiles: opts.command ? [] : collectChangedFiles(cwd, opts.env?.HANOMAN_BASE_SHA)
|
|
14956
|
+
};
|
|
14957
|
+
const customDefs = opts.command ? [] : customAgentsFor(selectionContext);
|
|
14958
|
+
let rosterBlock = "";
|
|
14959
|
+
let codexAgentArgs = [];
|
|
14960
|
+
let agentsFile;
|
|
14961
|
+
let agentConfigDir;
|
|
14962
|
+
let liveAgentDefs = [];
|
|
14963
|
+
if (customDefs.length > 0) {
|
|
14964
|
+
const tempDir = agentTempDir(id);
|
|
14965
|
+
agentConfigDir = tempDir;
|
|
14966
|
+
mkdirSync5(tempDir, { recursive: true, mode: 448 });
|
|
14967
|
+
const readOnlyHook = customDefs.some((def2) => def2.workspacePolicy === "read-only") ? writeReadOnlyHook(tempDir) : void 0;
|
|
14968
|
+
if (agentForDefs === "claude") {
|
|
14969
|
+
const json = renderAgentsJson(customDefs, { readOnlyHookCommand: readOnlyHook?.command });
|
|
14970
|
+
if (json) {
|
|
14971
|
+
agentsFile = agentsFilePath(id);
|
|
14972
|
+
writeFileSync5(agentsFile, json, { mode: 384 });
|
|
14973
|
+
rosterBlock = agentDelegationClause(customDefs, "claude");
|
|
14974
|
+
liveAgentDefs = customDefs;
|
|
14975
|
+
}
|
|
14976
|
+
} else {
|
|
14977
|
+
const materialized = materializeCodexAgents(customDefs, tempDir, {
|
|
14978
|
+
readOnlyHookCommand: readOnlyHook?.command,
|
|
14979
|
+
clientVersion: codexNativeAgentSupport().version
|
|
14980
|
+
});
|
|
14981
|
+
codexAgentArgs = materialized.args;
|
|
14982
|
+
rosterBlock = materialized.delegationClause;
|
|
14983
|
+
liveAgentDefs = materialized.liveDefs;
|
|
14984
|
+
for (const warning of materialized.warnings) {
|
|
14985
|
+
process.stderr.write(
|
|
14986
|
+
`hanoman: custom agent ${warning.agentName} tidak dimaterialisasi: ${warning.reason}
|
|
14987
|
+
`
|
|
14988
|
+
);
|
|
14989
|
+
}
|
|
14990
|
+
}
|
|
14991
|
+
}
|
|
14446
14992
|
let promptArg = "";
|
|
14447
14993
|
let promptFile;
|
|
14448
14994
|
if (!opts.command && opts.prompt) {
|
|
14449
14995
|
promptFile = promptFilePath(id);
|
|
14450
14996
|
mkdirSync5(dirname7(promptFile), { recursive: true, mode: 448 });
|
|
14451
|
-
|
|
14997
|
+
writeFileSync5(promptFile, opts.prompt + rosterBlock, { mode: 384 });
|
|
14452
14998
|
promptArg = `"$(cat ${sq(promptFile)})"`;
|
|
14453
14999
|
}
|
|
14454
15000
|
const agent = opts.agent ?? "claude";
|
|
@@ -14460,7 +15006,7 @@ function createSession(projectId, cwd, opts = {}) {
|
|
|
14460
15006
|
if (agent === "codex" && opts.goal && opts.flow && opts.specId) {
|
|
14461
15007
|
goalGate = goalGatePath(id);
|
|
14462
15008
|
mkdirSync5(dirname7(goalGate), { recursive: true, mode: 448 });
|
|
14463
|
-
|
|
15009
|
+
writeFileSync5(goalGate, codexGoalScript({
|
|
14464
15010
|
flow: opts.flow,
|
|
14465
15011
|
specId: opts.specId,
|
|
14466
15012
|
condition: opts.goal,
|
|
@@ -14470,15 +15016,6 @@ function createSession(projectId, cwd, opts = {}) {
|
|
|
14470
15016
|
}), { mode: 448 });
|
|
14471
15017
|
}
|
|
14472
15018
|
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
15019
|
const flags = agentFlags({
|
|
14483
15020
|
agent,
|
|
14484
15021
|
model: opts.model,
|
|
@@ -14491,7 +15028,8 @@ function createSession(projectId, cwd, opts = {}) {
|
|
|
14491
15028
|
eventHook: true
|
|
14492
15029
|
}).map(sq).join(" ");
|
|
14493
15030
|
const agentsArg = agentsFile ? `--agents "$(cat ${sq(agentsFile)})"` : "";
|
|
14494
|
-
|
|
15031
|
+
const nativeAgentArgs = agent === "codex" ? codexAgentArgs.map(sq).join(" ") : "";
|
|
15032
|
+
argv = [sq(agentBin(agent)), promptArg, flags, agentsArg, nativeAgentArgs].filter(Boolean).join(" ");
|
|
14495
15033
|
}
|
|
14496
15034
|
const envPairs = [];
|
|
14497
15035
|
if (!opts.command && agent === "claude") {
|
|
@@ -14510,16 +15048,16 @@ function createSession(projectId, cwd, opts = {}) {
|
|
|
14510
15048
|
if (opts.attachmentsDir) envPairs.push(`HANOMAN_ATTACHMENTS_DIR=${sq(opts.attachmentsDir)}`);
|
|
14511
15049
|
for (const [k, v] of Object.entries(opts.env ?? {})) envPairs.push(`${k}=${sq(v)}`);
|
|
14512
15050
|
let cmd = envPairs.length ? `${envPairs.join(" ")} ${argv}` : argv;
|
|
14513
|
-
let sandboxed = false;
|
|
14514
15051
|
if (!opts.command) {
|
|
14515
15052
|
const wrapped = sandboxCommand({
|
|
14516
15053
|
command: cmd,
|
|
14517
15054
|
worktree: cwd,
|
|
14518
15055
|
phaseFile: opts.phaseFile,
|
|
14519
15056
|
promptFile,
|
|
15057
|
+
agentConfigDir,
|
|
15058
|
+
eventDir,
|
|
14520
15059
|
attachmentsDir: opts.attachmentsDir
|
|
14521
15060
|
});
|
|
14522
|
-
sandboxed = wrapped !== cmd;
|
|
14523
15061
|
cmd = wrapped;
|
|
14524
15062
|
}
|
|
14525
15063
|
if (opts.decisionFile) mkdirSync5(dirname7(opts.decisionFile), { recursive: true });
|
|
@@ -14594,9 +15132,18 @@ function createSession(projectId, cwd, opts = {}) {
|
|
|
14594
15132
|
if (opts.flow) tmux("set-option", "-t", name(id), "@hanoman_flow", opts.flow);
|
|
14595
15133
|
if (opts.branch) tmux("set-option", "-t", name(id), "@hanoman_branch", opts.branch);
|
|
14596
15134
|
tmux("set-option", "-t", name(id), "@hanoman_agent", agent);
|
|
15135
|
+
if (liveAgentDefs.length > 0) {
|
|
15136
|
+
const roster = liveAgentDefs.map((def2) => ({
|
|
15137
|
+
...def2.id ? { id: def2.id } : {},
|
|
15138
|
+
name: def2.name,
|
|
15139
|
+
...def2.model ? { model: def2.model } : {},
|
|
15140
|
+
...def2.timeoutSeconds ? { timeoutSeconds: def2.timeoutSeconds } : {}
|
|
15141
|
+
}));
|
|
15142
|
+
tmux("set-option", "-t", name(id), "@hanoman_agent_roster", JSON.stringify(roster));
|
|
15143
|
+
}
|
|
14597
15144
|
if (opts.phaseFile) tmux("set-option", "-t", name(id), "@hanoman_phase_file", opts.phaseFile);
|
|
14598
15145
|
if (opts.decisionFile) tmux("set-option", "-t", name(id), "@hanoman_decision_file", opts.decisionFile);
|
|
14599
|
-
if (!opts.command
|
|
15146
|
+
if (!opts.command) tmux("set-option", "-t", name(id), "@hanoman_event_hook", "1");
|
|
14600
15147
|
if (opts.goal && !opts.command) void armGoalInTui(id, opts.goal, { agent }).catch(() => {
|
|
14601
15148
|
});
|
|
14602
15149
|
emitBirth({
|
|
@@ -14787,10 +15334,10 @@ function startPoll() {
|
|
|
14787
15334
|
poll = setInterval(() => {
|
|
14788
15335
|
if (polling) return;
|
|
14789
15336
|
polling = true;
|
|
14790
|
-
const
|
|
15337
|
+
const snapshot3 = [...attached.entries()];
|
|
14791
15338
|
listPanesAsync().then((panes) => {
|
|
14792
15339
|
const live = new Map(panes.map((p3) => [p3.id, p3]));
|
|
14793
|
-
for (const [id, a] of
|
|
15340
|
+
for (const [id, a] of snapshot3) {
|
|
14794
15341
|
if (attached.get(id) !== a) continue;
|
|
14795
15342
|
const p3 = live.get(id);
|
|
14796
15343
|
if (!p3) end(id, 0);
|
|
@@ -14853,6 +15400,14 @@ function killSession(id) {
|
|
|
14853
15400
|
const transcript = captureTranscript(id);
|
|
14854
15401
|
drop(id);
|
|
14855
15402
|
tmux("kill-session", "-t", name(id));
|
|
15403
|
+
try {
|
|
15404
|
+
rmSync2(agentTempDir(id), { recursive: true, force: true });
|
|
15405
|
+
} catch {
|
|
15406
|
+
}
|
|
15407
|
+
try {
|
|
15408
|
+
rmSync2(sessionEventDir(id), { recursive: true, force: true });
|
|
15409
|
+
} catch {
|
|
15410
|
+
}
|
|
14856
15411
|
emitDeath({ sessionId: id, exitCode: p3.exited ? p3.code : null, transcript });
|
|
14857
15412
|
void dropSessionUploads(id).catch(() => {
|
|
14858
15413
|
});
|
|
@@ -14878,7 +15433,7 @@ function spawnPty(...args) {
|
|
|
14878
15433
|
);
|
|
14879
15434
|
}
|
|
14880
15435
|
}
|
|
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;
|
|
15436
|
+
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
15437
|
var init_pty = __esm({
|
|
14883
15438
|
"src/services/pty.ts"() {
|
|
14884
15439
|
"use strict";
|
|
@@ -14892,6 +15447,8 @@ var init_pty = __esm({
|
|
|
14892
15447
|
init_ingress_policy();
|
|
14893
15448
|
init_session_event_token();
|
|
14894
15449
|
init_session_sandbox();
|
|
15450
|
+
init_session_event_spool();
|
|
15451
|
+
init_session_event_spool();
|
|
14895
15452
|
socket = () => effectiveStr("HANOMAN_TMUX_SOCKET") ?? "hanoman";
|
|
14896
15453
|
PREFIX = "hanoman-";
|
|
14897
15454
|
MAX_SCROLLBACK = 256 * 1024;
|
|
@@ -14906,7 +15463,7 @@ var init_pty = __esm({
|
|
|
14906
15463
|
};
|
|
14907
15464
|
clearMarker = (f) => {
|
|
14908
15465
|
try {
|
|
14909
|
-
|
|
15466
|
+
writeFileSync5(f, "");
|
|
14910
15467
|
} catch {
|
|
14911
15468
|
}
|
|
14912
15469
|
};
|
|
@@ -14934,11 +15491,12 @@ var init_pty = __esm({
|
|
|
14934
15491
|
rootBypassEnv = (uid = process.getuid?.()) => uid === 0 ? { IS_SANDBOX: "1" } : {};
|
|
14935
15492
|
frame = (f) => JSON.stringify(f);
|
|
14936
15493
|
name = (id) => PREFIX + id;
|
|
14937
|
-
promptFilePath = (id) => `${
|
|
14938
|
-
goalGatePath = (id) => `${
|
|
14939
|
-
goalStatePath = (id) => `${
|
|
14940
|
-
|
|
14941
|
-
|
|
15494
|
+
promptFilePath = (id) => `${tmpdir2()}/hanoman-prompts/${id}`;
|
|
15495
|
+
goalGatePath = (id) => `${tmpdir2()}/hanoman-goal-gates/${id}.sh`;
|
|
15496
|
+
goalStatePath = (id) => `${tmpdir2()}/hanoman-goal-gates/${id}.count`;
|
|
15497
|
+
agentTempDir = (id) => `${tmpdir2()}/hanoman-agents/${id}`;
|
|
15498
|
+
agentsFilePath = (id) => `${agentTempDir(id)}/claude.json`;
|
|
15499
|
+
askpassDenyPath = () => `${tmpdir2()}/hanoman-askpass/deny.sh`;
|
|
14942
15500
|
ASKPASS_DENY = `#!/bin/sh
|
|
14943
15501
|
echo "hanoman: tak ada manusia di pane ini \u2014 permintaan ketikan ditolak: $1" >&2
|
|
14944
15502
|
echo "hanoman: buka kuncinya di luar sesi (mis. ssh-add ~/.ssh/id_rsa), lalu ulangi." >&2
|
|
@@ -14970,7 +15528,8 @@ exit 1
|
|
|
14970
15528
|
// `pty-parse.test.ts` mengunci panjang FMT terhadap destructuring `parsePanes`.
|
|
14971
15529
|
"#{window_activity}",
|
|
14972
15530
|
"#{@hanoman_event_hook}",
|
|
14973
|
-
"#{session_created}"
|
|
15531
|
+
"#{session_created}",
|
|
15532
|
+
"#{@hanoman_agent_roster}"
|
|
14974
15533
|
].join(" ");
|
|
14975
15534
|
toSessionInfo = ({
|
|
14976
15535
|
id,
|
|
@@ -15028,13 +15587,18 @@ exit 1
|
|
|
15028
15587
|
}
|
|
15029
15588
|
};
|
|
15030
15589
|
customAgentSource = () => [];
|
|
15031
|
-
|
|
15590
|
+
codexNativeAgentSupport = () => ({ version: "0.151.0", ok: true });
|
|
15591
|
+
customAgentsFor = (context) => {
|
|
15032
15592
|
try {
|
|
15033
|
-
return customAgentSource(
|
|
15593
|
+
return customAgentSource(context);
|
|
15034
15594
|
} catch {
|
|
15035
15595
|
return [];
|
|
15036
15596
|
}
|
|
15037
15597
|
};
|
|
15598
|
+
runGitDiff = (cwd, args) => execFileSync2("git", ["-C", cwd, ...args], {
|
|
15599
|
+
encoding: "utf8",
|
|
15600
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
15601
|
+
});
|
|
15038
15602
|
sleep = (ms) => new Promise((r) => {
|
|
15039
15603
|
setTimeout(r, ms);
|
|
15040
15604
|
});
|
|
@@ -15260,6 +15824,11 @@ function validateSyncData(entity, data, options2 = {}) {
|
|
|
15260
15824
|
if (!Number.isSafeInteger(value)) throw new Error(`sync tipe invalid: ${entity}.${field}`);
|
|
15261
15825
|
continue;
|
|
15262
15826
|
}
|
|
15827
|
+
if (NULLABLE_NUMBER_FIELDS.has(key)) {
|
|
15828
|
+
if (value !== null && !Number.isSafeInteger(value))
|
|
15829
|
+
throw new Error(`sync tipe invalid: ${entity}.${field}`);
|
|
15830
|
+
continue;
|
|
15831
|
+
}
|
|
15263
15832
|
if (BOOLEAN_FIELDS.has(key)) {
|
|
15264
15833
|
if (typeof value !== "boolean") throw new Error(`sync tipe invalid: ${entity}.${field}`);
|
|
15265
15834
|
continue;
|
|
@@ -15527,7 +16096,7 @@ async function upsertLocal(entity, id, version, data) {
|
|
|
15527
16096
|
function setAcceptedHook(hook) {
|
|
15528
16097
|
onAccepted = hook;
|
|
15529
16098
|
}
|
|
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;
|
|
16099
|
+
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
16100
|
var init_sync = __esm({
|
|
15532
16101
|
"src/services/sync.ts"() {
|
|
15533
16102
|
"use strict";
|
|
@@ -15584,7 +16153,24 @@ var init_sync = __esm({
|
|
|
15584
16153
|
// SPEC-484 · ADR-0101 · `runtime` ikut: ia menentukan sesi mesin mana yang memakai persona ini,
|
|
15585
16154
|
// dan kolom yang terlewat di sini mendarat sebagai default palsu (= "warisi") di setiap mesin
|
|
15586
16155
|
// lain tanpa satu pun error.
|
|
15587
|
-
customAgent: [
|
|
16156
|
+
customAgent: [
|
|
16157
|
+
"projectId",
|
|
16158
|
+
"name",
|
|
16159
|
+
"description",
|
|
16160
|
+
"instructions",
|
|
16161
|
+
"tools",
|
|
16162
|
+
"model",
|
|
16163
|
+
"mentions",
|
|
16164
|
+
"runtime",
|
|
16165
|
+
"activation",
|
|
16166
|
+
"effort",
|
|
16167
|
+
"workspacePolicy",
|
|
16168
|
+
"maxTurns",
|
|
16169
|
+
"timeoutSeconds",
|
|
16170
|
+
"enabled",
|
|
16171
|
+
"createdAt",
|
|
16172
|
+
"updatedAt"
|
|
16173
|
+
],
|
|
15588
16174
|
// SPEC-471 · ADR-0095 · SELURUH kolom bermakna ikut. `status`/`specId` termasuk: keputusan
|
|
15589
16175
|
// triase adalah bagian keadaan yang harus dilihat sama oleh semua mesin — tanpa itu satu
|
|
15590
16176
|
// mesin bisa menerima ulang issue yang di mesin lain sudah jadi backlog.
|
|
@@ -15663,6 +16249,7 @@ var init_sync = __esm({
|
|
|
15663
16249
|
"ticketAttachment:size",
|
|
15664
16250
|
"githubIssue:number"
|
|
15665
16251
|
]);
|
|
16252
|
+
NULLABLE_NUMBER_FIELDS = /* @__PURE__ */ new Set(["customAgent:maxTurns", "customAgent:timeoutSeconds"]);
|
|
15666
16253
|
FLOAT_FIELDS = /* @__PURE__ */ new Set(["task:order"]);
|
|
15667
16254
|
BOOLEAN_FIELDS = /* @__PURE__ */ new Set(["vps:hardened", "customAgent:enabled", "member:active"]);
|
|
15668
16255
|
JSON_FIELDS = /* @__PURE__ */ new Set([
|
|
@@ -15697,6 +16284,33 @@ var init_sync = __esm({
|
|
|
15697
16284
|
}
|
|
15698
16285
|
});
|
|
15699
16286
|
|
|
16287
|
+
// src/services/sync-notify.ts
|
|
16288
|
+
async function notifySynced(entity, id) {
|
|
16289
|
+
try {
|
|
16290
|
+
if (!isEntity(entity)) return;
|
|
16291
|
+
await consumeTombstoneOnRecreate(entity, id);
|
|
16292
|
+
if (effectiveStr("SYNC_SERVER_URL")) await enqueueOutbox(entity, id);
|
|
16293
|
+
else await publishLocal(entity, id);
|
|
16294
|
+
} catch {
|
|
16295
|
+
}
|
|
16296
|
+
}
|
|
16297
|
+
async function notifyDeleted(entity, id) {
|
|
16298
|
+
try {
|
|
16299
|
+
if (!isEntity(entity)) return;
|
|
16300
|
+
if (effectiveStr("SYNC_SERVER_URL")) await enqueueOutbox(entity, id);
|
|
16301
|
+
else await publishDelete(entity, id);
|
|
16302
|
+
} catch {
|
|
16303
|
+
}
|
|
16304
|
+
}
|
|
16305
|
+
var init_sync_notify = __esm({
|
|
16306
|
+
"src/services/sync-notify.ts"() {
|
|
16307
|
+
"use strict";
|
|
16308
|
+
init_config2();
|
|
16309
|
+
init_outbox();
|
|
16310
|
+
init_sync();
|
|
16311
|
+
}
|
|
16312
|
+
});
|
|
16313
|
+
|
|
15700
16314
|
// src/services/settings.ts
|
|
15701
16315
|
async function getSetting() {
|
|
15702
16316
|
const raw = (await prisma.setting.findUnique({ where: { id: 1 } }))?.data;
|
|
@@ -15777,8 +16391,10 @@ var init_settings3 = __esm({
|
|
|
15777
16391
|
// SPEC-518 · agen pembuat changelog (opt-in, mati)
|
|
15778
16392
|
portalChat: PORTAL_CHAT_DEFAULTS,
|
|
15779
16393
|
// SPEC-854 · ADR-0130 · chat portal klien (opt-in, mati)
|
|
15780
|
-
builtinAgents: {}
|
|
16394
|
+
builtinAgents: {},
|
|
15781
16395
|
// SPEC-881 · ADR-0136 · sidik jari seed (lokal, tak disync)
|
|
16396
|
+
builtinAgentPolicies: {}
|
|
16397
|
+
// SPEC-950 · marker safety policy sekali-jalan (lokal)
|
|
15782
16398
|
};
|
|
15783
16399
|
RETIRED_MODELS = { "claude-opus-4-8": "claude-opus-5" };
|
|
15784
16400
|
}
|
|
@@ -16184,12 +16800,12 @@ var require_common = __commonJS({
|
|
|
16184
16800
|
createDebug.skips = [];
|
|
16185
16801
|
createDebug.formatters = {};
|
|
16186
16802
|
function selectColor(namespace) {
|
|
16187
|
-
let
|
|
16803
|
+
let hash3 = 0;
|
|
16188
16804
|
for (let i = 0; i < namespace.length; i++) {
|
|
16189
|
-
|
|
16190
|
-
|
|
16805
|
+
hash3 = (hash3 << 5) - hash3 + namespace.charCodeAt(i);
|
|
16806
|
+
hash3 |= 0;
|
|
16191
16807
|
}
|
|
16192
|
-
return createDebug.colors[Math.abs(
|
|
16808
|
+
return createDebug.colors[Math.abs(hash3) % createDebug.colors.length];
|
|
16193
16809
|
}
|
|
16194
16810
|
createDebug.selectColor = selectColor;
|
|
16195
16811
|
function createDebug(namespace) {
|
|
@@ -17335,7 +17951,7 @@ var init_gateway = __esm({
|
|
|
17335
17951
|
const message = sanitizeTelegramOutput(error.message, this.deps.exactSecrets).slice(0, 500);
|
|
17336
17952
|
updateTelegramRuntimeStatus({ running: false, readiness: "error", lastError: message });
|
|
17337
17953
|
if (error instanceof TelegramApiError && error.code === 409) return;
|
|
17338
|
-
await new Promise((
|
|
17954
|
+
await new Promise((resolve22) => setTimeout(resolve22, 1e3));
|
|
17339
17955
|
}
|
|
17340
17956
|
}
|
|
17341
17957
|
}
|
|
@@ -17498,9 +18114,9 @@ ${input.text}`;
|
|
|
17498
18114
|
}
|
|
17499
18115
|
const engine = await this.deps.defaults();
|
|
17500
18116
|
await this.deps.store.setChatEngine(input.chatId, engine);
|
|
17501
|
-
const
|
|
17502
|
-
const projectId = `telegram:${
|
|
17503
|
-
const cwd = `${this.deps.home.replace(/\/$/, "")}/telegram/${
|
|
18117
|
+
const hash3 = chatHash(input.chatId);
|
|
18118
|
+
const projectId = `telegram:${hash3}`;
|
|
18119
|
+
const cwd = `${this.deps.home.replace(/\/$/, "")}/telegram/${hash3}`;
|
|
17504
18120
|
this.deps.ensureDir(cwd);
|
|
17505
18121
|
if (engine.agent === "codex") this.deps.ensureCodexTrust(cwd);
|
|
17506
18122
|
const personality = await this.deps.personality(context.personalityAgentId, context.activeProjectId);
|
|
@@ -18049,6 +18665,57 @@ var init_bootstrap = __esm({
|
|
|
18049
18665
|
}
|
|
18050
18666
|
});
|
|
18051
18667
|
|
|
18668
|
+
// src/services/codex-version.ts
|
|
18669
|
+
import { execFile as execFile14 } from "node:child_process";
|
|
18670
|
+
import { promisify as promisify13 } from "node:util";
|
|
18671
|
+
async function probeCodexVersion(env = process.env, execute = runVersion) {
|
|
18672
|
+
const probe = codexNativeVersionProbe(env, codexBin2());
|
|
18673
|
+
try {
|
|
18674
|
+
return parseCodexVersion((await execute(probe.bin, probe.args)).stdout);
|
|
18675
|
+
} catch {
|
|
18676
|
+
return null;
|
|
18677
|
+
}
|
|
18678
|
+
}
|
|
18679
|
+
function parseCodexVersion(out4) {
|
|
18680
|
+
const m = /(\d+)\.(\d+)\.(\d+)/.exec(out4);
|
|
18681
|
+
return m ? `${m[1]}.${m[2]}.${m[3]}` : null;
|
|
18682
|
+
}
|
|
18683
|
+
async function getCodexVersion(now = Date.now()) {
|
|
18684
|
+
if (cache3 && now - cache3.at < TTL_MS4) return cache3.version;
|
|
18685
|
+
const version = await probeCodexVersion();
|
|
18686
|
+
cache3 = { at: now, version };
|
|
18687
|
+
return version;
|
|
18688
|
+
}
|
|
18689
|
+
async function codexVersionInfo() {
|
|
18690
|
+
const version = await getCodexVersion();
|
|
18691
|
+
return {
|
|
18692
|
+
version,
|
|
18693
|
+
minRequired: CODEX_MIN_CLIENT,
|
|
18694
|
+
ok: version === null || cmpVersion(version, CODEX_MIN_CLIENT) >= 0
|
|
18695
|
+
};
|
|
18696
|
+
}
|
|
18697
|
+
function _resetCodexVersionCache() {
|
|
18698
|
+
cache3 = null;
|
|
18699
|
+
}
|
|
18700
|
+
var run3, CODEX_MIN_CLIENT, TTL_MS4, cache3, codexBin2, runVersion;
|
|
18701
|
+
var init_codex_version = __esm({
|
|
18702
|
+
"src/services/codex-version.ts"() {
|
|
18703
|
+
"use strict";
|
|
18704
|
+
init_src();
|
|
18705
|
+
init_src2();
|
|
18706
|
+
init_config2();
|
|
18707
|
+
run3 = promisify13(execFile14);
|
|
18708
|
+
CODEX_MIN_CLIENT = "0.144.0";
|
|
18709
|
+
TTL_MS4 = 5 * 6e4;
|
|
18710
|
+
cache3 = null;
|
|
18711
|
+
codexBin2 = () => effectiveStr("HANOMAN_CODEX_BIN") ?? "codex";
|
|
18712
|
+
runVersion = async (bin, args) => {
|
|
18713
|
+
const { stdout } = await run3(bin, args, { timeout: 1e4 });
|
|
18714
|
+
return { stdout };
|
|
18715
|
+
};
|
|
18716
|
+
}
|
|
18717
|
+
});
|
|
18718
|
+
|
|
18052
18719
|
// src/services/presence/snapshot.ts
|
|
18053
18720
|
function paneToPresence(p3, phase) {
|
|
18054
18721
|
return {
|
|
@@ -20448,7 +21115,7 @@ var require_websocket = __commonJS({
|
|
|
20448
21115
|
var http = __require("http");
|
|
20449
21116
|
var net = __require("net");
|
|
20450
21117
|
var tls = __require("tls");
|
|
20451
|
-
var { randomBytes: randomBytes11, createHash:
|
|
21118
|
+
var { randomBytes: randomBytes11, createHash: createHash12 } = __require("crypto");
|
|
20452
21119
|
var { Duplex, Readable } = __require("stream");
|
|
20453
21120
|
var { URL: URL2 } = __require("url");
|
|
20454
21121
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -21116,8 +21783,8 @@ var require_websocket = __commonJS({
|
|
|
21116
21783
|
abortHandshake(websocket2, socket2, "Invalid Upgrade header");
|
|
21117
21784
|
return;
|
|
21118
21785
|
}
|
|
21119
|
-
const
|
|
21120
|
-
if (res.headers["sec-websocket-accept"] !==
|
|
21786
|
+
const digest2 = createHash12("sha1").update(key + GUID).digest("base64");
|
|
21787
|
+
if (res.headers["sec-websocket-accept"] !== digest2) {
|
|
21121
21788
|
abortHandshake(websocket2, socket2, "Invalid Sec-WebSocket-Accept header");
|
|
21122
21789
|
return;
|
|
21123
21790
|
}
|
|
@@ -21397,7 +22064,7 @@ var require_stream = __commonJS({
|
|
|
21397
22064
|
};
|
|
21398
22065
|
duplex._final = function(callback) {
|
|
21399
22066
|
if (ws2.readyState === ws2.CONNECTING) {
|
|
21400
|
-
ws2.once("open", function
|
|
22067
|
+
ws2.once("open", function open5() {
|
|
21401
22068
|
duplex._final(callback);
|
|
21402
22069
|
});
|
|
21403
22070
|
return;
|
|
@@ -21418,7 +22085,7 @@ var require_stream = __commonJS({
|
|
|
21418
22085
|
};
|
|
21419
22086
|
duplex._write = function(chunk, encoding, callback) {
|
|
21420
22087
|
if (ws2.readyState === ws2.CONNECTING) {
|
|
21421
|
-
ws2.once("open", function
|
|
22088
|
+
ws2.once("open", function open5() {
|
|
21422
22089
|
duplex._write(chunk, encoding, callback);
|
|
21423
22090
|
});
|
|
21424
22091
|
return;
|
|
@@ -21485,7 +22152,7 @@ var require_websocket_server = __commonJS({
|
|
|
21485
22152
|
var EventEmitter = __require("events");
|
|
21486
22153
|
var http = __require("http");
|
|
21487
22154
|
var { Duplex } = __require("stream");
|
|
21488
|
-
var { createHash:
|
|
22155
|
+
var { createHash: createHash12 } = __require("crypto");
|
|
21489
22156
|
var extension2 = require_extension();
|
|
21490
22157
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
21491
22158
|
var subprotocol2 = require_subprotocol();
|
|
@@ -21792,12 +22459,12 @@ var require_websocket_server = __commonJS({
|
|
|
21792
22459
|
);
|
|
21793
22460
|
}
|
|
21794
22461
|
if (this._state > RUNNING) return abortHandshake(socket2, 503);
|
|
21795
|
-
const
|
|
22462
|
+
const digest2 = createHash12("sha1").update(key + GUID).digest("base64");
|
|
21796
22463
|
const headers = [
|
|
21797
22464
|
"HTTP/1.1 101 Switching Protocols",
|
|
21798
22465
|
"Upgrade: websocket",
|
|
21799
22466
|
"Connection: Upgrade",
|
|
21800
|
-
`Sec-WebSocket-Accept: ${
|
|
22467
|
+
`Sec-WebSocket-Accept: ${digest2}`
|
|
21801
22468
|
];
|
|
21802
22469
|
const ws2 = new this.options.WebSocket(null, void 0, this.options);
|
|
21803
22470
|
if (protocols.size) {
|
|
@@ -22343,6 +23010,382 @@ var init_sync_client = __esm({
|
|
|
22343
23010
|
}
|
|
22344
23011
|
});
|
|
22345
23012
|
|
|
23013
|
+
// src/services/agent-tool-catalog.ts
|
|
23014
|
+
import { readFileSync as readFileSync17 } from "node:fs";
|
|
23015
|
+
import { homedir as homedir10 } from "node:os";
|
|
23016
|
+
import { join as join29 } from "node:path";
|
|
23017
|
+
function mcpServerNames(repoDir) {
|
|
23018
|
+
const names = [];
|
|
23019
|
+
const claudeJson = readJson2(join29(home(), ".claude.json"));
|
|
23020
|
+
names.push(...serversOf(claudeJson));
|
|
23021
|
+
if (repoDir) {
|
|
23022
|
+
const projects = claudeJson?.projects;
|
|
23023
|
+
if (projects && typeof projects === "object") names.push(...serversOf(projects[repoDir]));
|
|
23024
|
+
names.push(...serversOf(readJson2(join29(repoDir, ".mcp.json"))));
|
|
23025
|
+
}
|
|
23026
|
+
names.push(...codexServers());
|
|
23027
|
+
return [...new Set(names.filter(Boolean))].sort((a, b) => a.localeCompare(b));
|
|
23028
|
+
}
|
|
23029
|
+
function agentToolCatalog(repoDir) {
|
|
23030
|
+
return [ALL_TOOLS_ENTRY, ...BUILTIN_AGENT_TOOLS, ...mcpServerNames(repoDir).map(mcpToolEntry)];
|
|
23031
|
+
}
|
|
23032
|
+
var home, readJson2, serversOf, codexServers, agentToolIds;
|
|
23033
|
+
var init_agent_tool_catalog = __esm({
|
|
23034
|
+
"src/services/agent-tool-catalog.ts"() {
|
|
23035
|
+
"use strict";
|
|
23036
|
+
init_src();
|
|
23037
|
+
home = () => process.env.HOME || homedir10();
|
|
23038
|
+
readJson2 = (path) => {
|
|
23039
|
+
try {
|
|
23040
|
+
return JSON.parse(readFileSync17(path, "utf8"));
|
|
23041
|
+
} catch {
|
|
23042
|
+
return null;
|
|
23043
|
+
}
|
|
23044
|
+
};
|
|
23045
|
+
serversOf = (node) => {
|
|
23046
|
+
const ms = node?.mcpServers;
|
|
23047
|
+
if (!ms || typeof ms !== "object" || Array.isArray(ms)) return [];
|
|
23048
|
+
return Object.keys(ms);
|
|
23049
|
+
};
|
|
23050
|
+
codexServers = () => {
|
|
23051
|
+
let text;
|
|
23052
|
+
try {
|
|
23053
|
+
text = readFileSync17(join29(home(), ".codex", "config.toml"), "utf8");
|
|
23054
|
+
} catch {
|
|
23055
|
+
return [];
|
|
23056
|
+
}
|
|
23057
|
+
const out4 = [];
|
|
23058
|
+
for (const m of text.matchAll(/^\s*\[mcp_servers\.(?:"([^"]+)"|([A-Za-z0-9_-]+))(?:\.[^\]]*)?\]/gm)) {
|
|
23059
|
+
const name2 = m[1] ?? m[2];
|
|
23060
|
+
if (name2) out4.push(name2);
|
|
23061
|
+
}
|
|
23062
|
+
return out4;
|
|
23063
|
+
};
|
|
23064
|
+
agentToolIds = (repoDir) => agentToolCatalog(repoDir).map((t) => t.id);
|
|
23065
|
+
}
|
|
23066
|
+
});
|
|
23067
|
+
|
|
23068
|
+
// src/services/builtin-agents.ts
|
|
23069
|
+
import { createHash as createHash9 } from "node:crypto";
|
|
23070
|
+
async function seedBuiltinAgents() {
|
|
23071
|
+
try {
|
|
23072
|
+
const setting = await getSetting();
|
|
23073
|
+
const stamps = { ...setting.builtinAgents };
|
|
23074
|
+
const policies = { ...setting.builtinAgentPolicies };
|
|
23075
|
+
let changed = false;
|
|
23076
|
+
for (const a of BUILTIN_AGENTS) {
|
|
23077
|
+
const id = customAgentId(null, a.name);
|
|
23078
|
+
const fp = builtinFingerprint(a);
|
|
23079
|
+
const row = await prisma.customAgent.findUnique({ where: { id } });
|
|
23080
|
+
const qaPolicyPending = a.name === "qa-verifier" && policies[a.name] !== QA_SAFETY_POLICY;
|
|
23081
|
+
if (!row) {
|
|
23082
|
+
if (await findTombstone("customAgent", id)) {
|
|
23083
|
+
if (qaPolicyPending) {
|
|
23084
|
+
policies[a.name] = QA_SAFETY_POLICY;
|
|
23085
|
+
changed = true;
|
|
23086
|
+
}
|
|
23087
|
+
continue;
|
|
23088
|
+
}
|
|
23089
|
+
await prisma.customAgent.create({ data: {
|
|
23090
|
+
id,
|
|
23091
|
+
projectId: null,
|
|
23092
|
+
name: a.name,
|
|
23093
|
+
description: a.description,
|
|
23094
|
+
instructions: a.instructions,
|
|
23095
|
+
tools: [...a.tools],
|
|
23096
|
+
model: null,
|
|
23097
|
+
mentions: [],
|
|
23098
|
+
runtime: null,
|
|
23099
|
+
activation: a.activation,
|
|
23100
|
+
effort: a.effort,
|
|
23101
|
+
workspacePolicy: a.workspacePolicy,
|
|
23102
|
+
maxTurns: a.maxTurns,
|
|
23103
|
+
timeoutSeconds: a.timeoutSeconds,
|
|
23104
|
+
enabled: a.enabledByDefault
|
|
23105
|
+
} });
|
|
23106
|
+
await notifySynced("customAgent", id);
|
|
23107
|
+
stamps[a.name] = fp;
|
|
23108
|
+
changed = true;
|
|
23109
|
+
if (qaPolicyPending) policies[a.name] = QA_SAFETY_POLICY;
|
|
23110
|
+
continue;
|
|
23111
|
+
}
|
|
23112
|
+
const stamped = stamps[a.name];
|
|
23113
|
+
const unedited = Boolean(stamped) && (stamped === rowFingerprint(row) || stamped === legacyRowFingerprint(row));
|
|
23114
|
+
const data = {};
|
|
23115
|
+
if (unedited && stamped !== fp) {
|
|
23116
|
+
Object.assign(data, {
|
|
23117
|
+
description: a.description,
|
|
23118
|
+
instructions: a.instructions,
|
|
23119
|
+
tools: [...a.tools],
|
|
23120
|
+
activation: a.activation,
|
|
23121
|
+
effort: a.effort,
|
|
23122
|
+
workspacePolicy: a.workspacePolicy,
|
|
23123
|
+
maxTurns: a.maxTurns,
|
|
23124
|
+
timeoutSeconds: a.timeoutSeconds
|
|
23125
|
+
});
|
|
23126
|
+
stamps[a.name] = fp;
|
|
23127
|
+
changed = true;
|
|
23128
|
+
}
|
|
23129
|
+
if (qaPolicyPending && unedited) data.enabled = false;
|
|
23130
|
+
if (Object.keys(data).length > 0) {
|
|
23131
|
+
await prisma.customAgent.update({ where: { id }, data });
|
|
23132
|
+
await notifySynced("customAgent", id);
|
|
23133
|
+
}
|
|
23134
|
+
if (qaPolicyPending) {
|
|
23135
|
+
policies[a.name] = QA_SAFETY_POLICY;
|
|
23136
|
+
changed = true;
|
|
23137
|
+
}
|
|
23138
|
+
}
|
|
23139
|
+
if (changed) {
|
|
23140
|
+
const data = { ...setting, builtinAgents: stamps, builtinAgentPolicies: policies };
|
|
23141
|
+
await prisma.setting.upsert({
|
|
23142
|
+
where: { id: 1 },
|
|
23143
|
+
update: { data },
|
|
23144
|
+
create: { id: 1, data }
|
|
23145
|
+
});
|
|
23146
|
+
}
|
|
23147
|
+
} catch {
|
|
23148
|
+
}
|
|
23149
|
+
}
|
|
23150
|
+
var digest, legacyFingerprint, fingerprint, builtinFingerprint, rowFingerprint, legacyRowFingerprint, QA_SAFETY_POLICY;
|
|
23151
|
+
var init_builtin_agents2 = __esm({
|
|
23152
|
+
"src/services/builtin-agents.ts"() {
|
|
23153
|
+
"use strict";
|
|
23154
|
+
init_src();
|
|
23155
|
+
init_db();
|
|
23156
|
+
init_settings3();
|
|
23157
|
+
init_tombstone();
|
|
23158
|
+
init_sync_notify();
|
|
23159
|
+
digest = (parts) => createHash9("sha256").update(parts.join(" ")).digest("hex").slice(0, 16);
|
|
23160
|
+
legacyFingerprint = (name2, description, instructions, tools) => digest([name2, description, instructions, [...tools].join(",")]);
|
|
23161
|
+
fingerprint = (a) => digest([
|
|
23162
|
+
a.name,
|
|
23163
|
+
a.description,
|
|
23164
|
+
a.instructions,
|
|
23165
|
+
(toolsOf(a.tools) ?? []).join(","),
|
|
23166
|
+
activationOf(a.activation),
|
|
23167
|
+
effortOf(a.effort) ?? "",
|
|
23168
|
+
workspacePolicyOf(a.workspacePolicy),
|
|
23169
|
+
String(maxTurnsOf(a.maxTurns) ?? ""),
|
|
23170
|
+
String(timeoutSecondsOf(a.timeoutSeconds) ?? "")
|
|
23171
|
+
]);
|
|
23172
|
+
builtinFingerprint = (a) => fingerprint(a);
|
|
23173
|
+
rowFingerprint = (r) => fingerprint(r);
|
|
23174
|
+
legacyRowFingerprint = (r) => legacyFingerprint(r.name, r.description, r.instructions, toolsOf(r.tools) ?? []);
|
|
23175
|
+
QA_SAFETY_POLICY = "disable-unedited-v1";
|
|
23176
|
+
}
|
|
23177
|
+
});
|
|
23178
|
+
|
|
23179
|
+
// src/services/custom-agents.ts
|
|
23180
|
+
var custom_agents_exports = {};
|
|
23181
|
+
__export(custom_agents_exports, {
|
|
23182
|
+
agentDefsFor: () => agentDefsFor,
|
|
23183
|
+
collectChangedFiles: () => collectChangedFiles,
|
|
23184
|
+
currentCustomAgentRuntimeSupport: () => currentCustomAgentRuntimeSupport,
|
|
23185
|
+
installCustomAgents: () => installCustomAgents,
|
|
23186
|
+
loadCustomAgents: () => loadCustomAgents,
|
|
23187
|
+
refreshCustomAgentRuntimeSupport: () => refreshCustomAgentRuntimeSupport,
|
|
23188
|
+
selectAgentRows: () => selectAgentRows,
|
|
23189
|
+
toDef: () => toDef,
|
|
23190
|
+
unknownMentions: () => unknownMentions,
|
|
23191
|
+
validateGraph: () => validateGraph
|
|
23192
|
+
});
|
|
23193
|
+
function smartBuiltinSelected(row, context) {
|
|
23194
|
+
switch (row.name) {
|
|
23195
|
+
case "scout":
|
|
23196
|
+
return hasPhase(context, "Plan") || hasPhase(context, "Execute") || hasPhase(context, "Audit") || context.changedFiles.length === 0;
|
|
23197
|
+
case "blast-radius":
|
|
23198
|
+
return hasPhase(context, "Execute") || hasPhase(context, "Audit") || context.changedFiles.length > 0;
|
|
23199
|
+
case "security-reviewer":
|
|
23200
|
+
return (hasPhase(context, "Execute") || hasPhase(context, "Audit")) && touchesExternalInput(context);
|
|
23201
|
+
case "spec-auditor":
|
|
23202
|
+
return hasPhase(context, "Plan") || hasPhase(context, "Execute");
|
|
23203
|
+
case "dep-auditor":
|
|
23204
|
+
return touchesDependency(context.changedFiles);
|
|
23205
|
+
case "root-causer":
|
|
23206
|
+
return hasPhase(context, "Audit");
|
|
23207
|
+
case "qa-verifier":
|
|
23208
|
+
return context.runtime === "claude" && hasPhase(context, "Execute") && workspacePolicyOf(row.workspacePolicy) === "isolated-worktree" && touchesExecutableWork(context.changedFiles);
|
|
23209
|
+
case "edge-case-hunter":
|
|
23210
|
+
return context.runtime === "claude" && hasPhase(context, "Execute") && workspacePolicyOf(row.workspacePolicy) === "isolated-worktree";
|
|
23211
|
+
default:
|
|
23212
|
+
return true;
|
|
23213
|
+
}
|
|
23214
|
+
}
|
|
23215
|
+
function selectAgentRows(rows, context) {
|
|
23216
|
+
return rows.filter((row) => {
|
|
23217
|
+
if (!row.enabled) return false;
|
|
23218
|
+
const runtime = runtimeOf(row.runtime);
|
|
23219
|
+
if (runtime !== null && runtime !== context.runtime) return false;
|
|
23220
|
+
if (workspacePolicyOf(row.workspacePolicy) === "isolated-worktree" && context.runtime !== "claude") return false;
|
|
23221
|
+
if (activationOf(row.activation) === "always") return true;
|
|
23222
|
+
const builtin = row.projectId === null && BUILTIN_AGENT_NAMES.includes(row.name);
|
|
23223
|
+
return builtin ? smartBuiltinSelected(row, context) : true;
|
|
23224
|
+
});
|
|
23225
|
+
}
|
|
23226
|
+
function currentCustomAgentRuntimeSupport() {
|
|
23227
|
+
return { ...codexNativeSupport };
|
|
23228
|
+
}
|
|
23229
|
+
function toDef(r) {
|
|
23230
|
+
return {
|
|
23231
|
+
id: r.id,
|
|
23232
|
+
name: r.name,
|
|
23233
|
+
description: r.description,
|
|
23234
|
+
instructions: r.instructions,
|
|
23235
|
+
tools: toolsOf(r.tools),
|
|
23236
|
+
model: r.model,
|
|
23237
|
+
mentions: mentionsOf(r.mentions),
|
|
23238
|
+
activation: activationOf(r.activation),
|
|
23239
|
+
effort: effortOf(r.effort),
|
|
23240
|
+
workspacePolicy: workspacePolicyOf(r.workspacePolicy),
|
|
23241
|
+
maxTurns: maxTurnsOf(r.maxTurns),
|
|
23242
|
+
timeoutSeconds: timeoutSecondsOf(r.timeoutSeconds)
|
|
23243
|
+
};
|
|
23244
|
+
}
|
|
23245
|
+
function recommendedModel(row, runtime) {
|
|
23246
|
+
if (row.model) return row.model;
|
|
23247
|
+
if (row.projectId !== null) return null;
|
|
23248
|
+
const builtin = BUILTIN_AGENTS.find((agent) => agent.name === row.name);
|
|
23249
|
+
if (!builtin) return null;
|
|
23250
|
+
const model = builtin.models[runtime];
|
|
23251
|
+
if (runtime === "claude" && (model === "haiku" || model === "sonnet")) return model;
|
|
23252
|
+
return modelsForRuntime(runtime).some((entry) => entry.id === model) ? model : null;
|
|
23253
|
+
}
|
|
23254
|
+
function toRuntimeDef(row, runtime) {
|
|
23255
|
+
return { ...toDef(row), model: recommendedModel(row, runtime) };
|
|
23256
|
+
}
|
|
23257
|
+
async function loadCustomAgents() {
|
|
23258
|
+
try {
|
|
23259
|
+
cache4 = await prisma.customAgent.findMany();
|
|
23260
|
+
const projects = await prisma.project.findMany({ select: { id: true, repoDir: true } });
|
|
23261
|
+
const bindings = await prisma.localBinding.findMany({ select: { projectId: true, repoDir: true } });
|
|
23262
|
+
const next = /* @__PURE__ */ new Map();
|
|
23263
|
+
for (const p3 of projects) next.set(p3.id, p3.repoDir ?? null);
|
|
23264
|
+
for (const b of bindings) next.set(b.projectId, b.repoDir ?? null);
|
|
23265
|
+
repoDirCache = next;
|
|
23266
|
+
} catch {
|
|
23267
|
+
cache4 = [];
|
|
23268
|
+
repoDirCache = /* @__PURE__ */ new Map();
|
|
23269
|
+
}
|
|
23270
|
+
}
|
|
23271
|
+
function agentDefsFor(contextOrProjectId, legacyAgent) {
|
|
23272
|
+
const legacy = typeof contextOrProjectId === "string";
|
|
23273
|
+
const context = legacy ? {
|
|
23274
|
+
projectId: contextOrProjectId,
|
|
23275
|
+
runtime: legacyAgent ?? "claude",
|
|
23276
|
+
cwd: "",
|
|
23277
|
+
changedFiles: []
|
|
23278
|
+
} : contextOrProjectId;
|
|
23279
|
+
const { projectId } = context;
|
|
23280
|
+
const globals = cache4.filter((r) => r.projectId === null).map(asCustomAgent);
|
|
23281
|
+
const project = cache4.filter((r) => r.projectId === projectId).map(asCustomAgent);
|
|
23282
|
+
const effectiveIds = new Set(effectiveAgents(globals, project).map((agent) => agent.id));
|
|
23283
|
+
const effectiveRows = cache4.filter((row) => effectiveIds.has(row.id));
|
|
23284
|
+
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);
|
|
23285
|
+
const needsCatalog = eff.some((row) => (toolsOf(row.tools) ?? []).includes(ALL_TOOLS));
|
|
23286
|
+
const catalogIds = needsCatalog ? agentToolIds(repoDirCache.get(projectId) ?? null) : [];
|
|
23287
|
+
return eff.map((row) => {
|
|
23288
|
+
const a = asCustomAgent(row);
|
|
23289
|
+
return {
|
|
23290
|
+
...toRuntimeDef(row, context.runtime),
|
|
23291
|
+
// Ekspansi terjadi DI SINI, sebelum `resolveTools` di runner: meneruskan `"*"` apa adanya
|
|
23292
|
+
// membuat claude membuangnya senyap (agen tanpa alat), sementara menerjemahkannya jadi `null`
|
|
23293
|
+
// membuat agen mewarisi SELURUH tool termasuk `Task` — lapis 2 anti-loop lenyap tanpa jejak.
|
|
23294
|
+
tools: expandTools(a.tools, catalogIds),
|
|
23295
|
+
mentions: a.mentions ?? []
|
|
23296
|
+
};
|
|
23297
|
+
});
|
|
23298
|
+
}
|
|
23299
|
+
function validateGraph(rows) {
|
|
23300
|
+
const projectScopes = [...new Set(rows.map((r) => r.projectId).filter((p3) => p3 !== null))];
|
|
23301
|
+
const globals = rows.filter((r) => r.projectId === null).map(asCustomAgent);
|
|
23302
|
+
for (const scope of [null, ...projectScopes]) {
|
|
23303
|
+
const project = scope === null ? [] : rows.filter((r) => r.projectId === scope).map(asCustomAgent);
|
|
23304
|
+
const nodes = effectiveAgents(globals, project).map((a) => ({ name: a.name, mentions: a.mentions ?? [] }));
|
|
23305
|
+
const cycle = detectCycle(nodes);
|
|
23306
|
+
if (cycle) return { scope: scope ?? GLOBAL_SCOPE, cycle };
|
|
23307
|
+
}
|
|
23308
|
+
return null;
|
|
23309
|
+
}
|
|
23310
|
+
function unknownMentions(row, all) {
|
|
23311
|
+
const visible = new Set(
|
|
23312
|
+
all.filter((r) => r.projectId === null || row.projectId !== null && r.projectId === row.projectId).map((r) => r.name)
|
|
23313
|
+
);
|
|
23314
|
+
return mentionsOf(row.mentions).filter((m) => !visible.has(m));
|
|
23315
|
+
}
|
|
23316
|
+
async function refreshCustomAgentRuntimeSupport(probe = defaultCodexSupportProbe) {
|
|
23317
|
+
const refresh = codexSupportRefreshTail.then(async () => {
|
|
23318
|
+
const version = await probe();
|
|
23319
|
+
codexNativeSupport = { version, ok: codexNativeAgentsSupported(version) };
|
|
23320
|
+
});
|
|
23321
|
+
codexSupportRefreshTail = refresh.catch(() => {
|
|
23322
|
+
});
|
|
23323
|
+
return refresh;
|
|
23324
|
+
}
|
|
23325
|
+
async function installCustomAgents() {
|
|
23326
|
+
await seedBuiltinAgents();
|
|
23327
|
+
await loadCustomAgents();
|
|
23328
|
+
await refreshCustomAgentRuntimeSupport();
|
|
23329
|
+
registerCodexNativeAgentSupport(() => codexNativeSupport);
|
|
23330
|
+
registerCustomAgentSource((context) => agentDefsFor(context));
|
|
23331
|
+
if (!codexSupportRefreshTimer) {
|
|
23332
|
+
codexSupportRefreshTimer = setInterval(() => {
|
|
23333
|
+
void refreshCustomAgentRuntimeSupport().catch((error) => console.error("custom agent: probe Codex gagal:", error));
|
|
23334
|
+
}, 5 * 6e4);
|
|
23335
|
+
codexSupportRefreshTimer.unref();
|
|
23336
|
+
}
|
|
23337
|
+
}
|
|
23338
|
+
var phasesOf, hasPhase, touchesDependency, touchesExternalInput, touchesExecutableWork, cache4, codexNativeSupport, codexSupportRefreshTimer, codexSupportRefreshTail, repoDirCache, asCustomAgent, defaultCodexSupportProbe;
|
|
23339
|
+
var init_custom_agents2 = __esm({
|
|
23340
|
+
"src/services/custom-agents.ts"() {
|
|
23341
|
+
"use strict";
|
|
23342
|
+
init_db();
|
|
23343
|
+
init_src();
|
|
23344
|
+
init_src2();
|
|
23345
|
+
init_pty();
|
|
23346
|
+
init_agent_tool_catalog();
|
|
23347
|
+
init_builtin_agents2();
|
|
23348
|
+
init_codex_version();
|
|
23349
|
+
phasesOf = (flow) => flow ? PIPELINES[flow] : [];
|
|
23350
|
+
hasPhase = (context, name2) => phasesOf(context.flow).includes(name2);
|
|
23351
|
+
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));
|
|
23352
|
+
touchesExternalInput = (context) => {
|
|
23353
|
+
const surface = [context.prompt ?? "", ...context.changedFiles].join("\n");
|
|
23354
|
+
return /(?:^|[\W_/.-])(route|routes|handler|auth|oauth|api|cli|config|filesystem|upload|webhook|input)(?:$|[\W_/.-])/i.test(surface);
|
|
23355
|
+
};
|
|
23356
|
+
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));
|
|
23357
|
+
cache4 = [];
|
|
23358
|
+
codexNativeSupport = { version: null, ok: false };
|
|
23359
|
+
codexSupportRefreshTimer = null;
|
|
23360
|
+
codexSupportRefreshTail = Promise.resolve();
|
|
23361
|
+
repoDirCache = /* @__PURE__ */ new Map();
|
|
23362
|
+
asCustomAgent = (r) => ({
|
|
23363
|
+
id: r.id,
|
|
23364
|
+
projectId: r.projectId,
|
|
23365
|
+
name: r.name,
|
|
23366
|
+
description: r.description,
|
|
23367
|
+
instructions: r.instructions,
|
|
23368
|
+
tools: toolsOf(r.tools),
|
|
23369
|
+
model: r.model,
|
|
23370
|
+
mentions: mentionsOf(r.mentions),
|
|
23371
|
+
runtime: runtimeOf(r.runtime),
|
|
23372
|
+
activation: activationOf(r.activation),
|
|
23373
|
+
effort: effortOf(r.effort),
|
|
23374
|
+
workspacePolicy: workspacePolicyOf(r.workspacePolicy),
|
|
23375
|
+
maxTurns: maxTurnsOf(r.maxTurns),
|
|
23376
|
+
timeoutSeconds: timeoutSecondsOf(r.timeoutSeconds),
|
|
23377
|
+
enabled: r.enabled,
|
|
23378
|
+
createdAt: "",
|
|
23379
|
+
updatedAt: ""
|
|
23380
|
+
// tak dipakai lapis ini
|
|
23381
|
+
});
|
|
23382
|
+
defaultCodexSupportProbe = async () => {
|
|
23383
|
+
_resetCodexVersionCache();
|
|
23384
|
+
return getCodexVersion();
|
|
23385
|
+
};
|
|
23386
|
+
}
|
|
23387
|
+
});
|
|
23388
|
+
|
|
22346
23389
|
// src/services/config-apply.ts
|
|
22347
23390
|
var config_apply_exports = {};
|
|
22348
23391
|
__export(config_apply_exports, {
|
|
@@ -22370,6 +23413,10 @@ async function applyConfigSideEffect(key) {
|
|
|
22370
23413
|
return;
|
|
22371
23414
|
}
|
|
22372
23415
|
if (configEntry(key)?.inheritEnv) mirrorInheritEnv(key);
|
|
23416
|
+
if (key === "HANOMAN_CODEX_BIN") {
|
|
23417
|
+
const { refreshCustomAgentRuntimeSupport: refreshCustomAgentRuntimeSupport2 } = await Promise.resolve().then(() => (init_custom_agents2(), custom_agents_exports));
|
|
23418
|
+
await refreshCustomAgentRuntimeSupport2();
|
|
23419
|
+
}
|
|
22373
23420
|
}
|
|
22374
23421
|
async function rotateSyncOrigin(input) {
|
|
22375
23422
|
const url2 = new URL(input);
|
|
@@ -24854,14 +25901,14 @@ var require_stream_consumer = __commonJS({
|
|
|
24854
25901
|
"../node_modules/.pnpm/@fastify+multipart@10.1.0/node_modules/@fastify/multipart/lib/stream-consumer.js"(exports, module) {
|
|
24855
25902
|
"use strict";
|
|
24856
25903
|
module.exports = function streamToNull(stream) {
|
|
24857
|
-
return new Promise((
|
|
25904
|
+
return new Promise((resolve22, reject2) => {
|
|
24858
25905
|
stream.on("data", () => {
|
|
24859
25906
|
});
|
|
24860
25907
|
stream.on("close", () => {
|
|
24861
|
-
|
|
25908
|
+
resolve22();
|
|
24862
25909
|
});
|
|
24863
25910
|
stream.on("end", () => {
|
|
24864
|
-
|
|
25911
|
+
resolve22();
|
|
24865
25912
|
});
|
|
24866
25913
|
stream.on("error", (error) => {
|
|
24867
25914
|
reject2(error);
|
|
@@ -25310,10 +26357,10 @@ var require_multipart2 = __commonJS({
|
|
|
25310
26357
|
}
|
|
25311
26358
|
};
|
|
25312
26359
|
const parts = () => {
|
|
25313
|
-
return new Promise((
|
|
26360
|
+
return new Promise((resolve22, reject2) => {
|
|
25314
26361
|
handle((val) => {
|
|
25315
26362
|
if (val instanceof Error) return reject2(val);
|
|
25316
|
-
|
|
26363
|
+
resolve22(val);
|
|
25317
26364
|
});
|
|
25318
26365
|
});
|
|
25319
26366
|
};
|
|
@@ -25494,7 +26541,7 @@ var require_multipart2 = __commonJS({
|
|
|
25494
26541
|
parts = this.parts(options3);
|
|
25495
26542
|
}
|
|
25496
26543
|
this.savedRequestFiles = [];
|
|
25497
|
-
const
|
|
26544
|
+
const tmpdir7 = options3?.tmpdir || os.tmpdir();
|
|
25498
26545
|
this.tmpUploads = [];
|
|
25499
26546
|
let i = 0;
|
|
25500
26547
|
for await (const part of parts) {
|
|
@@ -25502,7 +26549,7 @@ var require_multipart2 = __commonJS({
|
|
|
25502
26549
|
if (!part.file) {
|
|
25503
26550
|
continue;
|
|
25504
26551
|
}
|
|
25505
|
-
const filepath = path.join(
|
|
26552
|
+
const filepath = path.join(tmpdir7, generateId() + path.extname(part.filename || "file" + i++));
|
|
25506
26553
|
const target2 = createWriteStream2(filepath);
|
|
25507
26554
|
try {
|
|
25508
26555
|
this.tmpUploads.push(filepath);
|
|
@@ -25679,15 +26726,15 @@ init_stage_machine();
|
|
|
25679
26726
|
init_src();
|
|
25680
26727
|
import { execFile as execFile2 } from "node:child_process";
|
|
25681
26728
|
import { promisify } from "node:util";
|
|
25682
|
-
import { existsSync as existsSync6, rmSync as
|
|
26729
|
+
import { existsSync as existsSync6, rmSync as rmSync3 } from "node:fs";
|
|
25683
26730
|
import { resolve as resolve10 } from "node:path";
|
|
25684
26731
|
|
|
25685
26732
|
// src/services/safe-repo-path.ts
|
|
25686
26733
|
import { constants as constants2 } from "node:fs";
|
|
25687
26734
|
import { lstat, mkdir as mkdir2, open as open2, realpath, rename, unlink as unlink2 } from "node:fs/promises";
|
|
25688
|
-
import { isAbsolute as isAbsolute4, join as
|
|
26735
|
+
import { isAbsolute as isAbsolute4, join as join10, relative, resolve as resolve9, sep as sep2 } from "node:path";
|
|
25689
26736
|
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
|
|
26737
|
+
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
26738
|
var PathContainmentError = class extends Error {
|
|
25692
26739
|
code = "PATH_CONTAINMENT";
|
|
25693
26740
|
};
|
|
@@ -25715,7 +26762,7 @@ async function ensureRepoParents(root, rel) {
|
|
|
25715
26762
|
const parts = components(rel).slice(0, -1);
|
|
25716
26763
|
let current = base2;
|
|
25717
26764
|
for (const part of parts) {
|
|
25718
|
-
current =
|
|
26765
|
+
current = join10(current, part);
|
|
25719
26766
|
try {
|
|
25720
26767
|
await mkdir2(current, { mode: 448 });
|
|
25721
26768
|
} catch (error) {
|
|
@@ -25732,7 +26779,7 @@ function ensureRepoParentsSync(root, rel) {
|
|
|
25732
26779
|
const parts = components(rel).slice(0, -1);
|
|
25733
26780
|
let current = base2;
|
|
25734
26781
|
for (const part of parts) {
|
|
25735
|
-
current =
|
|
26782
|
+
current = join10(current, part);
|
|
25736
26783
|
try {
|
|
25737
26784
|
mkdirSync6(current, { mode: 448 });
|
|
25738
26785
|
} catch (error) {
|
|
@@ -25748,7 +26795,7 @@ async function resolveRepoEntry(root, rel, opts = {}) {
|
|
|
25748
26795
|
const parts = components(rel);
|
|
25749
26796
|
let current = base2;
|
|
25750
26797
|
for (let i = 0; i < parts.length; i++) {
|
|
25751
|
-
current =
|
|
26798
|
+
current = join10(current, parts[i]);
|
|
25752
26799
|
const stat4 = await lstat(current).catch((error) => {
|
|
25753
26800
|
if (error.code === "ENOENT" && opts.allowMissingTail) return null;
|
|
25754
26801
|
if (error.code === "ENOENT" && opts.allowMissingFinal && i === parts.length - 1) return null;
|
|
@@ -25769,7 +26816,7 @@ function assertSafeRepoPathSync(root, rel, allowMissingFinal = false, allowMissi
|
|
|
25769
26816
|
const parts = components(rel);
|
|
25770
26817
|
let current = base2;
|
|
25771
26818
|
for (let i = 0; i < parts.length; i++) {
|
|
25772
|
-
current =
|
|
26819
|
+
current = join10(current, parts[i]);
|
|
25773
26820
|
try {
|
|
25774
26821
|
const stat4 = lstatSync(current);
|
|
25775
26822
|
if (stat4.isSymbolicLink()) denied("symlink");
|
|
@@ -25799,10 +26846,10 @@ function writeRepoFileAtomicSync(root, rel, data) {
|
|
|
25799
26846
|
const parent = resolve9(path, "..");
|
|
25800
26847
|
const parentRel = relative(realpathSync2(root), parent);
|
|
25801
26848
|
if (parentRel) assertSafeRepoPathSync(root, parentRel);
|
|
25802
|
-
const temp =
|
|
26849
|
+
const temp = join10(parent, `.hanoman-${randomUUID3()}.tmp`);
|
|
25803
26850
|
const fd = openSync(temp, constants2.O_WRONLY | constants2.O_CREAT | constants2.O_EXCL | (constants2.O_NOFOLLOW ?? 0), 384);
|
|
25804
26851
|
try {
|
|
25805
|
-
|
|
26852
|
+
writeFileSync6(fd, data);
|
|
25806
26853
|
} finally {
|
|
25807
26854
|
closeSync(fd);
|
|
25808
26855
|
}
|
|
@@ -25846,7 +26893,7 @@ async function writeRepoFileAtomic(root, rel, data) {
|
|
|
25846
26893
|
await mkdir2(entry.parent, { recursive: false, mode: 448 }).catch((error) => {
|
|
25847
26894
|
if (error.code !== "EEXIST") throw error;
|
|
25848
26895
|
});
|
|
25849
|
-
const temp =
|
|
26896
|
+
const temp = join10(entry.parent, `.hanoman-${randomUUID3()}.tmp`);
|
|
25850
26897
|
const handle = await open2(temp, constants2.O_WRONLY | constants2.O_CREAT | constants2.O_EXCL | (constants2.O_NOFOLLOW ?? 0), 384);
|
|
25851
26898
|
try {
|
|
25852
26899
|
await handle.writeFile(data);
|
|
@@ -25945,7 +26992,7 @@ function writeDocFile(repoDir, rel, content) {
|
|
|
25945
26992
|
function deleteDocFile(repoDir, rel) {
|
|
25946
26993
|
const abs = docAbsPath(repoDir, rel);
|
|
25947
26994
|
if (!existsSync6(abs)) return false;
|
|
25948
|
-
|
|
26995
|
+
rmSync3(abs);
|
|
25949
26996
|
return true;
|
|
25950
26997
|
}
|
|
25951
26998
|
|
|
@@ -26003,9 +27050,9 @@ async function toProjectView(p3, sessions, devices2) {
|
|
|
26003
27050
|
const specs = await prisma.spec.findMany({ where: { projectId: p3.id } });
|
|
26004
27051
|
const { coverage } = await scanRepoDocs(await resolveRepoDir(p3.id));
|
|
26005
27052
|
const binding = await getBinding(p3.id);
|
|
26006
|
-
const
|
|
27053
|
+
const open5 = specs.filter((s2) => s2.stage !== "done");
|
|
26007
27054
|
const { session, commit } = sessionOf(p3.id, sessions);
|
|
26008
|
-
const topStage =
|
|
27055
|
+
const topStage = open5.length ? open5.map((s2) => s2.stage).sort((a, b) => STAGES.indexOf(b) - STAGES.indexOf(a))[0] : "spec";
|
|
26009
27056
|
return {
|
|
26010
27057
|
id: p3.id,
|
|
26011
27058
|
name: p3.name,
|
|
@@ -26018,7 +27065,7 @@ async function toProjectView(p3, sessions, devices2) {
|
|
|
26018
27065
|
docStatus: docStatusFor(coverage),
|
|
26019
27066
|
coverage,
|
|
26020
27067
|
createdAt: p3.createdAt.toISOString(),
|
|
26021
|
-
backlog:
|
|
27068
|
+
backlog: open5.length,
|
|
26022
27069
|
topStage,
|
|
26023
27070
|
session,
|
|
26024
27071
|
activity: session.status === "running" ? `running \xB7 ${session.flow ?? "sesi"}` : "idle",
|
|
@@ -26037,31 +27084,13 @@ async function toProjectView(p3, sessions, devices2) {
|
|
|
26037
27084
|
};
|
|
26038
27085
|
}
|
|
26039
27086
|
|
|
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
|
-
}
|
|
27087
|
+
// src/routes/projects.ts
|
|
27088
|
+
init_sync_notify();
|
|
26061
27089
|
|
|
26062
27090
|
// src/services/sync-delete.ts
|
|
26063
27091
|
init_sync();
|
|
26064
27092
|
init_tombstone();
|
|
27093
|
+
init_sync_notify();
|
|
26065
27094
|
init_outbox();
|
|
26066
27095
|
async function deleteSynced(entity, id, deviceId) {
|
|
26067
27096
|
const snap = await snapshot(entity, id);
|
|
@@ -26305,8 +27334,8 @@ import { existsSync as existsSync9 } from "node:fs";
|
|
|
26305
27334
|
// src/services/integrate.ts
|
|
26306
27335
|
import { execFile as execFile4 } from "node:child_process";
|
|
26307
27336
|
import { promisify as promisify3 } from "node:util";
|
|
26308
|
-
import { rmSync as
|
|
26309
|
-
import { join as
|
|
27337
|
+
import { rmSync as rmSync4 } from "node:fs";
|
|
27338
|
+
import { join as join11 } from "node:path";
|
|
26310
27339
|
var sanitize = (id) => id.toLowerCase().replace(/[^a-z0-9_-]/g, "_");
|
|
26311
27340
|
var sourceBranch = (specId) => `hanoman/${sanitize(specId)}`;
|
|
26312
27341
|
var exec3 = promisify3(execFile4);
|
|
@@ -26342,7 +27371,7 @@ async function resolveTarget(repoDir, target2) {
|
|
|
26342
27371
|
async function reclaim(repoDir, wt) {
|
|
26343
27372
|
await sh(repoDir, ["worktree", "remove", "--force", wt]);
|
|
26344
27373
|
await sh(repoDir, ["worktree", "prune"]);
|
|
26345
|
-
|
|
27374
|
+
rmSync4(wt, { recursive: true, force: true });
|
|
26346
27375
|
}
|
|
26347
27376
|
async function integrateBranch(repoDir, src, op, target2) {
|
|
26348
27377
|
const source = await resolveSource(repoDir, src.branch);
|
|
@@ -26350,7 +27379,7 @@ async function integrateBranch(repoDir, src, op, target2) {
|
|
|
26350
27379
|
const tgt = await resolveTarget(repoDir, target2);
|
|
26351
27380
|
if (!tgt) return { status: "error", code: 400, error: `target "${target2}" tidak dikenal` };
|
|
26352
27381
|
await sh(repoDir, ["fetch", "origin"]);
|
|
26353
|
-
const wt =
|
|
27382
|
+
const wt = join11(repoDir, ".worktrees", `merge-${sanitize(src.mergeId)}`);
|
|
26354
27383
|
await reclaim(repoDir, wt);
|
|
26355
27384
|
const baseRef = op === "merge" ? tgt.ref : source;
|
|
26356
27385
|
const baseSha = await out(repoDir, ["rev-parse", "--verify", "--end-of-options", `${baseRef}^{commit}`]);
|
|
@@ -26411,7 +27440,7 @@ async function mergeIntoCurrent(repoDir, source, opts = {}) {
|
|
|
26411
27440
|
const src = await resolveGraphSource(repoDir, source);
|
|
26412
27441
|
if (!src) return { status: "error", code: 400, error: `source "${source}" tak dikenal` };
|
|
26413
27442
|
await sh(repoDir, ["fetch", "origin"]);
|
|
26414
|
-
const wt =
|
|
27443
|
+
const wt = join11(repoDir, ".worktrees", `merge-${sanitize(current)}`);
|
|
26415
27444
|
await reclaim(repoDir, wt);
|
|
26416
27445
|
const baseSha = await out(repoDir, ["rev-parse", "--verify", "--end-of-options", `refs/heads/${current}^{commit}`]);
|
|
26417
27446
|
if (!await ok(repoDir, ["worktree", "add", "--detach", "-q", wt, baseSha]))
|
|
@@ -26449,7 +27478,7 @@ async function replayCurrent(repoDir, source, cmd) {
|
|
|
26449
27478
|
const current = await out(repoDir, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
26450
27479
|
if (!current || current === "HEAD")
|
|
26451
27480
|
return { status: "error", code: 409, error: "HEAD detached \u2014 checkout sebuah branch dulu" };
|
|
26452
|
-
const wt =
|
|
27481
|
+
const wt = join11(repoDir, ".worktrees", `merge-${sanitize(current)}`);
|
|
26453
27482
|
await reclaim(repoDir, wt);
|
|
26454
27483
|
const baseSha = await out(repoDir, ["rev-parse", "--verify", "--end-of-options", `refs/heads/${current}^{commit}`]);
|
|
26455
27484
|
if (!await ok(repoDir, ["worktree", "add", "--detach", "-q", wt, baseSha]))
|
|
@@ -26497,16 +27526,16 @@ init_db();
|
|
|
26497
27526
|
import { execFile as execFile5 } from "node:child_process";
|
|
26498
27527
|
import { promisify as promisify4 } from "node:util";
|
|
26499
27528
|
import { mkdtemp, copyFile, rm as rm2 } from "node:fs/promises";
|
|
26500
|
-
import { tmpdir as
|
|
26501
|
-
import { join as
|
|
27529
|
+
import { tmpdir as tmpdir3 } from "node:os";
|
|
27530
|
+
import { join as join12, resolve as resolve11 } from "node:path";
|
|
26502
27531
|
var exec4 = promisify4(execFile5);
|
|
26503
27532
|
var GIT3 = { maxBuffer: 1 << 24 };
|
|
26504
27533
|
var MAX = 256 * 1024;
|
|
26505
|
-
var worktreeDir = (repoDir, specId) =>
|
|
27534
|
+
var worktreeDir = (repoDir, specId) => join12(repoDir, ".worktrees", specId.toLowerCase().replace(/[^a-z0-9_-]/g, "_"));
|
|
26506
27535
|
async function withTempIndex(wt, fn) {
|
|
26507
27536
|
const idx = (await exec4("git", ["rev-parse", "--git-path", "index"], { cwd: wt, ...GIT3 })).stdout.trim();
|
|
26508
|
-
const dir2 = await mkdtemp(
|
|
26509
|
-
const tmp =
|
|
27537
|
+
const dir2 = await mkdtemp(join12(tmpdir3(), "hanoman-idx-"));
|
|
27538
|
+
const tmp = join12(dir2, "index");
|
|
26510
27539
|
await copyFile(resolve11(wt, idx), tmp);
|
|
26511
27540
|
const env = { ...process.env, GIT_INDEX_FILE: tmp };
|
|
26512
27541
|
try {
|
|
@@ -26705,7 +27734,7 @@ function appendSourceHistory(current, entry) {
|
|
|
26705
27734
|
// src/services/spec-reset.ts
|
|
26706
27735
|
import { execFile as execFile8 } from "node:child_process";
|
|
26707
27736
|
import { existsSync as existsSync8 } from "node:fs";
|
|
26708
|
-
import { join as
|
|
27737
|
+
import { join as join14 } from "node:path";
|
|
26709
27738
|
import { promisify as promisify7 } from "node:util";
|
|
26710
27739
|
|
|
26711
27740
|
// src/services/stage-artifacts.ts
|
|
@@ -26754,7 +27783,7 @@ init_db();
|
|
|
26754
27783
|
import { execFile as execFile6 } from "node:child_process";
|
|
26755
27784
|
import { readdir, rm as rm3 } from "node:fs/promises";
|
|
26756
27785
|
import { promisify as promisify5 } from "node:util";
|
|
26757
|
-
import { join as
|
|
27786
|
+
import { join as join13, resolve as resolve12 } from "node:path";
|
|
26758
27787
|
init_notifications2();
|
|
26759
27788
|
var TICK_MS = 6e4;
|
|
26760
27789
|
var trashDirOf = (repoDir) => resolve12(repoDir, ".worktrees", ".trash");
|
|
@@ -26814,7 +27843,7 @@ async function sweepRepo(repoDir, projectId, deps = prodReaperDeps) {
|
|
|
26814
27843
|
}
|
|
26815
27844
|
let removed = 0;
|
|
26816
27845
|
for (const entry of entries3) {
|
|
26817
|
-
const path =
|
|
27846
|
+
const path = join13(dir2, entry);
|
|
26818
27847
|
const known = pending.get(path);
|
|
26819
27848
|
const row = known ?? { path, repoDir, projectId, entry, sessionId: sessionIdOf(entry), since: Date.now() };
|
|
26820
27849
|
pending.set(path, row);
|
|
@@ -27370,7 +28399,7 @@ async function planSpecReset(spec) {
|
|
|
27370
28399
|
const repoDir = await resolveRepoDir(spec.projectId);
|
|
27371
28400
|
if (!repoDir) return EMPTY;
|
|
27372
28401
|
const sid = sessionIdForSpec(spec.id);
|
|
27373
|
-
const wt =
|
|
28402
|
+
const wt = join14(repoDir, ".worktrees", sid);
|
|
27374
28403
|
const branch = `hanoman/${sid}`;
|
|
27375
28404
|
const hasBranch = await shaResolvable(repoDir, `refs/heads/${branch}`);
|
|
27376
28405
|
return {
|
|
@@ -27401,6 +28430,7 @@ async function applySpecReset(spec, plan) {
|
|
|
27401
28430
|
|
|
27402
28431
|
// src/routes/specs.ts
|
|
27403
28432
|
init_notifications2();
|
|
28433
|
+
init_sync_notify();
|
|
27404
28434
|
|
|
27405
28435
|
// src/services/spec-complete.ts
|
|
27406
28436
|
init_db();
|
|
@@ -27408,6 +28438,7 @@ init_notifications2();
|
|
|
27408
28438
|
|
|
27409
28439
|
// src/services/session-result.ts
|
|
27410
28440
|
init_db();
|
|
28441
|
+
init_sync_notify();
|
|
27411
28442
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
27412
28443
|
var WHITELIST = [
|
|
27413
28444
|
"projectId",
|
|
@@ -27433,6 +28464,7 @@ async function recordSessionResult(input) {
|
|
|
27433
28464
|
}
|
|
27434
28465
|
|
|
27435
28466
|
// src/services/spec-complete.ts
|
|
28467
|
+
init_sync_notify();
|
|
27436
28468
|
async function completeSpecManually(spec, input) {
|
|
27437
28469
|
const at = input.at ?? /* @__PURE__ */ new Date();
|
|
27438
28470
|
const manualDone = {
|
|
@@ -27468,7 +28500,7 @@ import { extname } from "node:path";
|
|
|
27468
28500
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
27469
28501
|
import { spawn as spawn2 } from "node:child_process";
|
|
27470
28502
|
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
|
|
28503
|
+
import { basename as basename3, isAbsolute as isAbsolute5, join as join15 } from "node:path";
|
|
27472
28504
|
|
|
27473
28505
|
// ../node_modules/.pnpm/strtok3@10.3.5/node_modules/strtok3/lib/stream/Errors.js
|
|
27474
28506
|
var defaultMessages = "End-Of-Stream";
|
|
@@ -29581,7 +30613,7 @@ function readByobReaderWithSignal(reader, buffer, signal) {
|
|
|
29581
30613
|
return reader.read(buffer);
|
|
29582
30614
|
}
|
|
29583
30615
|
signal.throwIfAborted();
|
|
29584
|
-
return new Promise((
|
|
30616
|
+
return new Promise((resolve22, reject2) => {
|
|
29585
30617
|
const cleanup = () => {
|
|
29586
30618
|
signal.removeEventListener("abort", onAbort);
|
|
29587
30619
|
};
|
|
@@ -29601,7 +30633,7 @@ function readByobReaderWithSignal(reader, buffer, signal) {
|
|
|
29601
30633
|
try {
|
|
29602
30634
|
const result = await reader.read(buffer);
|
|
29603
30635
|
cleanup();
|
|
29604
|
-
|
|
30636
|
+
resolve22(result);
|
|
29605
30637
|
} catch (error) {
|
|
29606
30638
|
cleanup();
|
|
29607
30639
|
reject2(error);
|
|
@@ -31360,11 +32392,11 @@ var UploadError = class extends Error {
|
|
|
31360
32392
|
}
|
|
31361
32393
|
};
|
|
31362
32394
|
function timeout(promise, ms, code) {
|
|
31363
|
-
return new Promise((
|
|
32395
|
+
return new Promise((resolve22, reject2) => {
|
|
31364
32396
|
const timer9 = setTimeout(() => reject2(new UploadError(code, "upload operation timed out")), ms);
|
|
31365
32397
|
promise.then((value) => {
|
|
31366
32398
|
clearTimeout(timer9);
|
|
31367
|
-
|
|
32399
|
+
resolve22(value);
|
|
31368
32400
|
}, (error) => {
|
|
31369
32401
|
clearTimeout(timer9);
|
|
31370
32402
|
reject2(error);
|
|
@@ -31387,7 +32419,7 @@ function scannerFromEnv(path) {
|
|
|
31387
32419
|
return Promise.resolve();
|
|
31388
32420
|
}
|
|
31389
32421
|
if (!isAbsolute5(command)) return Promise.reject(new UploadError("UPLOAD_SCAN", "scanner path must be absolute"));
|
|
31390
|
-
return new Promise((
|
|
32422
|
+
return new Promise((resolve22, reject2) => {
|
|
31391
32423
|
const child = spawn2(command, [path], { shell: false, stdio: "ignore" });
|
|
31392
32424
|
const timer9 = setTimeout(() => {
|
|
31393
32425
|
child.kill("SIGKILL");
|
|
@@ -31399,27 +32431,27 @@ function scannerFromEnv(path) {
|
|
|
31399
32431
|
});
|
|
31400
32432
|
child.once("exit", (code) => {
|
|
31401
32433
|
clearTimeout(timer9);
|
|
31402
|
-
if (code === 0)
|
|
32434
|
+
if (code === 0) resolve22();
|
|
31403
32435
|
else reject2(new UploadError("UPLOAD_SCAN", `scanner exit ${code}`));
|
|
31404
32436
|
});
|
|
31405
32437
|
});
|
|
31406
32438
|
}
|
|
31407
|
-
function
|
|
32439
|
+
function safeFilename2(input, extension2) {
|
|
31408
32440
|
const stem = basename3(input).replace(/\.[^.]*$/, "").replace(/[^a-zA-Z0-9._ -]/g, "_").slice(0, 180).trim();
|
|
31409
32441
|
return `${stem || "upload"}${extension2}`;
|
|
31410
32442
|
}
|
|
31411
32443
|
async function commitToStorage(buffer, extension2, deps, beforePromote) {
|
|
31412
32444
|
const storageDir = deps.storageDir ?? uploadDir();
|
|
31413
|
-
const quarantineDir =
|
|
32445
|
+
const quarantineDir = join15(storageDir, ".quarantine");
|
|
31414
32446
|
await mkdir3(quarantineDir, { recursive: true, mode: 448 });
|
|
31415
32447
|
await mkdir3(storageDir, { recursive: true, mode: 448 });
|
|
31416
|
-
const quarantine =
|
|
32448
|
+
const quarantine = join15(quarantineDir, `${randomUUID5()}.upload`);
|
|
31417
32449
|
const storageKey = `${randomUUID5()}${extension2}`;
|
|
31418
32450
|
await writeFile2(quarantine, buffer, { mode: 384, flag: "wx" });
|
|
31419
32451
|
try {
|
|
31420
32452
|
await timeout((deps.scanner ?? scannerFromEnv)(quarantine), UPLOAD_LIMITS.scanMs, "UPLOAD_SCAN");
|
|
31421
32453
|
await beforePromote?.();
|
|
31422
|
-
await rename2(quarantine,
|
|
32454
|
+
await rename2(quarantine, join15(storageDir, storageKey));
|
|
31423
32455
|
} catch (error) {
|
|
31424
32456
|
await unlink3(quarantine).catch(() => {
|
|
31425
32457
|
});
|
|
@@ -31466,7 +32498,7 @@ async function processUpload(input, deps = {}) {
|
|
|
31466
32498
|
});
|
|
31467
32499
|
return {
|
|
31468
32500
|
storageKey,
|
|
31469
|
-
filename:
|
|
32501
|
+
filename: safeFilename2(input.clientName, type.extension),
|
|
31470
32502
|
mimeType,
|
|
31471
32503
|
extension: type.extension,
|
|
31472
32504
|
size: normalized.byteLength,
|
|
@@ -31506,7 +32538,7 @@ async function processDocumentUpload(input, deps = {}) {
|
|
|
31506
32538
|
const storageKey = await commitToStorage(input.buffer, extension2, deps);
|
|
31507
32539
|
return {
|
|
31508
32540
|
storageKey,
|
|
31509
|
-
filename:
|
|
32541
|
+
filename: safeFilename2(input.clientName, extension2),
|
|
31510
32542
|
mimeType: input.clientMime,
|
|
31511
32543
|
extension: extension2,
|
|
31512
32544
|
size: input.buffer.byteLength
|
|
@@ -31620,10 +32652,10 @@ async function dropSpecAttachments(specId) {
|
|
|
31620
32652
|
// src/services/spec-attachment-dir.ts
|
|
31621
32653
|
init_db();
|
|
31622
32654
|
import { mkdir as mkdir4, readdir as readdir3, readFile as readFile3, rm as rm4, writeFile as writeFile3 } from "node:fs/promises";
|
|
31623
|
-
import { join as
|
|
32655
|
+
import { join as join16 } from "node:path";
|
|
31624
32656
|
init_session_id();
|
|
31625
32657
|
init_uploads();
|
|
31626
|
-
var specAttachmentsDir = (repoDir, sessionId2) =>
|
|
32658
|
+
var specAttachmentsDir = (repoDir, sessionId2) => join16(repoDir, ".worktrees", ".attachments", sessionId2);
|
|
31627
32659
|
var INDEX = "INDEX.md";
|
|
31628
32660
|
var humanSize = (n2) => n2 >= 1024 * 1024 ? `${(n2 / 1024 / 1024).toFixed(1)} MB` : n2 >= 1024 ? `${Math.round(n2 / 1024)} KB` : `${n2} B`;
|
|
31629
32661
|
function uniqueName(taken, filename) {
|
|
@@ -31673,19 +32705,19 @@ async function syncSpecAttachmentsDir(specId, projectId) {
|
|
|
31673
32705
|
for (const a of rows) {
|
|
31674
32706
|
let bytes;
|
|
31675
32707
|
try {
|
|
31676
|
-
bytes = await readFile3(
|
|
32708
|
+
bytes = await readFile3(join16(uploadDir(), a.storageKey));
|
|
31677
32709
|
} catch {
|
|
31678
32710
|
continue;
|
|
31679
32711
|
}
|
|
31680
32712
|
const filename = uniqueName(taken, a.filename);
|
|
31681
|
-
const path =
|
|
32713
|
+
const path = join16(dir2, filename);
|
|
31682
32714
|
await writeFile3(path, bytes, { mode: 384 });
|
|
31683
32715
|
items.push({ filename, mimeType: a.mimeType, size: a.size, path });
|
|
31684
32716
|
}
|
|
31685
|
-
await writeFile3(
|
|
32717
|
+
await writeFile3(join16(dir2, INDEX), renderIndex(specId, items), { mode: 384 });
|
|
31686
32718
|
const keep = /* @__PURE__ */ new Set([INDEX, ...items.map((a) => a.filename)]);
|
|
31687
32719
|
for (const name2 of await readdir3(dir2)) {
|
|
31688
|
-
if (!keep.has(name2)) await rm4(
|
|
32720
|
+
if (!keep.has(name2)) await rm4(join16(dir2, name2), { recursive: true, force: true }).catch(() => {
|
|
31689
32721
|
});
|
|
31690
32722
|
}
|
|
31691
32723
|
return items;
|
|
@@ -34242,7 +35274,7 @@ function renderDocPdf(text, name2, meta) {
|
|
|
34242
35274
|
});
|
|
34243
35275
|
const chunks = [];
|
|
34244
35276
|
doc.on("data", (c) => chunks.push(c));
|
|
34245
|
-
const done = new Promise((
|
|
35277
|
+
const done = new Promise((resolve22) => doc.on("end", () => resolve22(Buffer.concat(chunks))));
|
|
34246
35278
|
doc.font("Helvetica-Bold").fontSize(8).fillColor(BRASS).text(toWinAnsi(meta.eyebrow.toUpperCase()), { characterSpacing: 0.8 });
|
|
34247
35279
|
doc.moveDown(0.25);
|
|
34248
35280
|
doc.font("Helvetica-Bold").fontSize(17).fillColor(STRONG).text(toWinAnsi(name2.split("/").pop() ?? name2));
|
|
@@ -34317,6 +35349,7 @@ init_pty();
|
|
|
34317
35349
|
init_session_phases();
|
|
34318
35350
|
init_stage_machine();
|
|
34319
35351
|
init_notifications2();
|
|
35352
|
+
init_sync_notify();
|
|
34320
35353
|
|
|
34321
35354
|
// src/services/spec-head.ts
|
|
34322
35355
|
init_db();
|
|
@@ -35227,7 +36260,7 @@ async function deleteBranches(repoDir, names, opts) {
|
|
|
35227
36260
|
import { execFile as execFile11 } from "node:child_process";
|
|
35228
36261
|
import { realpathSync as realpathSync4 } from "node:fs";
|
|
35229
36262
|
import { stat } from "node:fs/promises";
|
|
35230
|
-
import { basename as basename4, join as
|
|
36263
|
+
import { basename as basename4, join as join17, resolve as resolve15, sep as sep4 } from "node:path";
|
|
35231
36264
|
import { promisify as promisify10 } from "node:util";
|
|
35232
36265
|
var exec10 = promisify10(execFile11);
|
|
35233
36266
|
var GIT7 = { timeout: 6e4, maxBuffer: 1 << 24, encoding: "utf8" };
|
|
@@ -35272,7 +36305,7 @@ var real = (p3) => {
|
|
|
35272
36305
|
};
|
|
35273
36306
|
async function bornAt(path) {
|
|
35274
36307
|
try {
|
|
35275
|
-
const st = await stat(
|
|
36308
|
+
const st = await stat(join17(path, ".git"));
|
|
35276
36309
|
const ms = st.birthtimeMs > 0 ? st.birthtimeMs : st.mtimeMs;
|
|
35277
36310
|
return new Date(ms).toISOString();
|
|
35278
36311
|
} catch {
|
|
@@ -35437,7 +36470,7 @@ init_session_id();
|
|
|
35437
36470
|
// src/services/repo-fs.ts
|
|
35438
36471
|
import { lstat as lstat2, rename as rename3, rm as rm5 } from "node:fs/promises";
|
|
35439
36472
|
import { createWriteStream } from "node:fs";
|
|
35440
|
-
import { join as
|
|
36473
|
+
import { join as join18 } from "node:path";
|
|
35441
36474
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
35442
36475
|
import { pipeline } from "node:stream/promises";
|
|
35443
36476
|
var EntryExistsError = class extends Error {
|
|
@@ -35498,7 +36531,7 @@ async function saveUpload(repoDir, rel, source, opts = {}) {
|
|
|
35498
36531
|
if (current && !opts.overwrite) return { status: "exists" };
|
|
35499
36532
|
if (current && !current.isFile())
|
|
35500
36533
|
throw new PathContainmentError("repository path ditolak: target bukan file regular");
|
|
35501
|
-
const temp =
|
|
36534
|
+
const temp = join18(entry.parent, `.hanoman-${randomUUID6()}.tmp`);
|
|
35502
36535
|
try {
|
|
35503
36536
|
await pipeline(source, createWriteStream(temp, { flags: "wx", mode: 384 }));
|
|
35504
36537
|
if (opts.isTruncated?.()) {
|
|
@@ -35516,13 +36549,13 @@ async function saveUpload(repoDir, rel, source, opts = {}) {
|
|
|
35516
36549
|
// src/routes/ide.ts
|
|
35517
36550
|
var activeSessions = (id) => listSessions().filter((s2) => s2.projectId === id && !s2.exited).length;
|
|
35518
36551
|
async function lockInputs(id) {
|
|
35519
|
-
const
|
|
36552
|
+
const open5 = await prisma.spec.findMany({
|
|
35520
36553
|
where: { projectId: id, stage: { not: "done" } },
|
|
35521
36554
|
select: { id: true }
|
|
35522
36555
|
});
|
|
35523
36556
|
const sessions = listSessions().filter((s2) => s2.projectId === id && !s2.exited).map((s2) => s2.branch || (s2.specId ? `hanoman/${s2.id}` : "")).filter(Boolean);
|
|
35524
36557
|
return {
|
|
35525
|
-
openSpecBranches: new Set(
|
|
36558
|
+
openSpecBranches: new Set(open5.map((s2) => sourceBranch(s2.id))),
|
|
35526
36559
|
sessionBranches: new Set(sessions)
|
|
35527
36560
|
};
|
|
35528
36561
|
}
|
|
@@ -36014,14 +37047,14 @@ async function finishGraphOp(reply, id, repoDir, r, verb) {
|
|
|
36014
37047
|
// src/routes/fs.ts
|
|
36015
37048
|
import { readdir as readdir4 } from "node:fs/promises";
|
|
36016
37049
|
import { homedir as homedir4 } from "node:os";
|
|
36017
|
-
import { resolve as resolve17, dirname as dirname9, join as
|
|
37050
|
+
import { resolve as resolve17, dirname as dirname9, join as join19 } from "node:path";
|
|
36018
37051
|
async function fs_default(app2) {
|
|
36019
37052
|
app2.get("/fs/browse", async (req, reply) => {
|
|
36020
37053
|
const q = req.query.path;
|
|
36021
37054
|
const dir2 = q && q.trim() ? resolve17(q.trim()) : homedir4();
|
|
36022
37055
|
try {
|
|
36023
37056
|
const ents = await readdir4(dir2, { withFileTypes: true });
|
|
36024
|
-
const entries3 = ents.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => ({ name: e.name, path:
|
|
37057
|
+
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));
|
|
36025
37058
|
const parent = dirname9(dir2);
|
|
36026
37059
|
return { path: dir2, parent: parent === dir2 ? null : parent, entries: entries3 };
|
|
36027
37060
|
} catch {
|
|
@@ -36503,14 +37536,14 @@ function installCommand(m, agent, shell = shellBin()) {
|
|
|
36503
37536
|
}
|
|
36504
37537
|
|
|
36505
37538
|
// src/services/terminal-diag.ts
|
|
36506
|
-
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync9, statSync as statSync3, rmSync as
|
|
36507
|
-
import { join as
|
|
37539
|
+
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync9, statSync as statSync3, rmSync as rmSync5 } from "node:fs";
|
|
37540
|
+
import { join as join20 } from "node:path";
|
|
36508
37541
|
var DIAG_MAX_BYTES = 2 * 1024 * 1024;
|
|
36509
37542
|
var KINDS = /* @__PURE__ */ new Set(["key", "comp", "data", "ack", "pred"]);
|
|
36510
37543
|
var ID = /^[A-Za-z0-9_-]{1,64}$/;
|
|
36511
37544
|
function diagFile(home3, sessionId2) {
|
|
36512
37545
|
if (!ID.test(sessionId2)) throw new Error(`id sesi tak sah untuk diag: ${sessionId2}`);
|
|
36513
|
-
return
|
|
37546
|
+
return join20(home3, "diag", `${sessionId2}.jsonl`);
|
|
36514
37547
|
}
|
|
36515
37548
|
function usable(ev) {
|
|
36516
37549
|
if (!ev || typeof ev !== "object") return false;
|
|
@@ -36521,9 +37554,9 @@ function appendDiag(home3, sessionId2, events) {
|
|
|
36521
37554
|
const file = diagFile(home3, sessionId2);
|
|
36522
37555
|
const rows = events.filter(usable);
|
|
36523
37556
|
if (!rows.length) return;
|
|
36524
|
-
mkdirSync9(
|
|
37557
|
+
mkdirSync9(join20(home3, "diag"), { recursive: true });
|
|
36525
37558
|
try {
|
|
36526
|
-
if (statSync3(file).size > DIAG_MAX_BYTES)
|
|
37559
|
+
if (statSync3(file).size > DIAG_MAX_BYTES) rmSync5(file);
|
|
36527
37560
|
} catch {
|
|
36528
37561
|
}
|
|
36529
37562
|
appendFileSync2(file, rows.map((r) => JSON.stringify(r)).join("\n") + "\n");
|
|
@@ -37078,9 +38111,9 @@ init_pty();
|
|
|
37078
38111
|
init_session_sandbox();
|
|
37079
38112
|
import { execFile as execFile13 } from "node:child_process";
|
|
37080
38113
|
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
37081
|
-
import { mkdirSync as mkdirSync10, rmSync as
|
|
37082
|
-
import { tmpdir as
|
|
37083
|
-
import { join as
|
|
38114
|
+
import { mkdirSync as mkdirSync10, rmSync as rmSync6, writeFileSync as writeFileSync7 } from "node:fs";
|
|
38115
|
+
import { tmpdir as tmpdir4 } from "node:os";
|
|
38116
|
+
import { join as join21 } from "node:path";
|
|
37084
38117
|
var binFor = (agent) => agent === "codex" ? effectiveStr("HANOMAN_CODEX_BIN") ?? "codex" : effectiveStr("HANOMAN_CLAUDE_BIN") ?? "claude";
|
|
37085
38118
|
function leadArgv(o) {
|
|
37086
38119
|
if (o.agent === "codex") {
|
|
@@ -37101,22 +38134,22 @@ function leadArgv(o) {
|
|
|
37101
38134
|
];
|
|
37102
38135
|
}
|
|
37103
38136
|
var leadEnv = (agent, base2 = process.env, uid = process.getuid?.()) => agent === "claude" ? { ...rootBypassEnv(uid), ...base2 } : { ...base2 };
|
|
37104
|
-
var
|
|
38137
|
+
var shellQuote3 = (value) => `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
37105
38138
|
function leadProcess(prompt, o, env = process.env) {
|
|
37106
38139
|
const file = binFor(o.agent);
|
|
37107
38140
|
const directArgs = leadArgv({ agent: o.agent, model: o.model, effort: o.effort, prompt });
|
|
37108
38141
|
const mode = env.HANOMAN_SESSION_SANDBOX ?? (resolveHardening(env) ? "required" : "off");
|
|
37109
38142
|
if (mode === "off") return { file, args: directArgs, cwd: o.cwd, cleanup: () => {
|
|
37110
38143
|
} };
|
|
37111
|
-
const promptDir =
|
|
38144
|
+
const promptDir = join21(tmpdir4(), "hanoman-prompts");
|
|
37112
38145
|
mkdirSync10(promptDir, { recursive: true, mode: 448 });
|
|
37113
|
-
const promptFile =
|
|
37114
|
-
|
|
37115
|
-
const workspace = o.cwd ??
|
|
38146
|
+
const promptFile = join21(promptDir, `oneshot-${randomUUID7()}`);
|
|
38147
|
+
writeFileSync7(promptFile, prompt, { flag: "wx", mode: 384 });
|
|
38148
|
+
const workspace = o.cwd ?? join21(tmpdir4(), `hanoman-oneshot-${randomUUID7()}`);
|
|
37116
38149
|
if (!o.cwd) mkdirSync10(workspace, { recursive: false, mode: 448 });
|
|
37117
38150
|
try {
|
|
37118
38151
|
const argsWithoutPrompt = directArgs.slice(0, -1);
|
|
37119
|
-
const command = [file, ...argsWithoutPrompt].map(
|
|
38152
|
+
const command = [file, ...argsWithoutPrompt].map(shellQuote3).join(" ") + ` "$(cat ${shellQuote3(promptFile)})"`;
|
|
37120
38153
|
const sandbox = sandboxArgvFromEnv({
|
|
37121
38154
|
command,
|
|
37122
38155
|
worktree: workspace,
|
|
@@ -37130,13 +38163,13 @@ function leadProcess(prompt, o, env = process.env) {
|
|
|
37130
38163
|
args: sandbox.slice(1),
|
|
37131
38164
|
promptFile,
|
|
37132
38165
|
cleanup: () => {
|
|
37133
|
-
|
|
37134
|
-
if (!o.cwd)
|
|
38166
|
+
rmSync6(promptFile, { force: true });
|
|
38167
|
+
if (!o.cwd) rmSync6(workspace, { recursive: true, force: true });
|
|
37135
38168
|
}
|
|
37136
38169
|
};
|
|
37137
38170
|
} catch (error) {
|
|
37138
|
-
|
|
37139
|
-
if (!o.cwd)
|
|
38171
|
+
rmSync6(promptFile, { force: true });
|
|
38172
|
+
if (!o.cwd) rmSync6(workspace, { recursive: true, force: true });
|
|
37140
38173
|
throw error;
|
|
37141
38174
|
}
|
|
37142
38175
|
}
|
|
@@ -37154,7 +38187,7 @@ function leadFailureReason(agent, timeoutMs, err, stdout, stderr) {
|
|
|
37154
38187
|
}
|
|
37155
38188
|
function think(prompt, o) {
|
|
37156
38189
|
const process2 = leadProcess(prompt, o);
|
|
37157
|
-
return new Promise((
|
|
38190
|
+
return new Promise((resolve22, reject2) => {
|
|
37158
38191
|
const child = execFile13(process2.file, process2.args, {
|
|
37159
38192
|
cwd: process2.cwd,
|
|
37160
38193
|
timeout: o.timeoutMs,
|
|
@@ -37168,7 +38201,7 @@ function think(prompt, o) {
|
|
|
37168
38201
|
reject2(new Error(leadFailureReason(o.agent, o.timeoutMs, err, stdout, stderr)));
|
|
37169
38202
|
return;
|
|
37170
38203
|
}
|
|
37171
|
-
|
|
38204
|
+
resolve22(stdout);
|
|
37172
38205
|
});
|
|
37173
38206
|
child.stdin?.end();
|
|
37174
38207
|
});
|
|
@@ -37205,9 +38238,9 @@ function acquire(cap, waitMs) {
|
|
|
37205
38238
|
return Promise.resolve();
|
|
37206
38239
|
}
|
|
37207
38240
|
const startedAt = Date.now();
|
|
37208
|
-
return new Promise((
|
|
38241
|
+
return new Promise((resolve22, reject2) => {
|
|
37209
38242
|
const w = {
|
|
37210
|
-
grant:
|
|
38243
|
+
grant: resolve22,
|
|
37211
38244
|
deny: reject2,
|
|
37212
38245
|
timer: setTimeout(() => {
|
|
37213
38246
|
const i = queue.indexOf(w);
|
|
@@ -38603,9 +39636,9 @@ import { readFileSync as readFileSync10 } from "node:fs";
|
|
|
38603
39636
|
// src/services/vps-ssh.ts
|
|
38604
39637
|
init_config2();
|
|
38605
39638
|
import { spawn as spawn4 } from "node:child_process";
|
|
38606
|
-
import { mkdtempSync, rmSync as
|
|
38607
|
-
import { tmpdir as
|
|
38608
|
-
import { dirname as dirname10, join as
|
|
39639
|
+
import { mkdtempSync, rmSync as rmSync7, writeFileSync as writeFileSync8 } from "node:fs";
|
|
39640
|
+
import { tmpdir as tmpdir5 } from "node:os";
|
|
39641
|
+
import { dirname as dirname10, join as join22 } from "node:path";
|
|
38609
39642
|
var sshBin = () => effectiveStr("HANOMAN_SSH_BIN") ?? "ssh";
|
|
38610
39643
|
function consoleArgv(t) {
|
|
38611
39644
|
return [
|
|
@@ -38620,9 +39653,9 @@ function consoleArgv(t) {
|
|
|
38620
39653
|
];
|
|
38621
39654
|
}
|
|
38622
39655
|
function askpassScript() {
|
|
38623
|
-
const dir2 = mkdtempSync(
|
|
38624
|
-
const path =
|
|
38625
|
-
|
|
39656
|
+
const dir2 = mkdtempSync(join22(tmpdir5(), "hanoman-askpass-"));
|
|
39657
|
+
const path = join22(dir2, "askpass.sh");
|
|
39658
|
+
writeFileSync8(path, `#!/bin/sh
|
|
38626
39659
|
printf '%s' "$HANOMAN_SSH_PASSWORD"
|
|
38627
39660
|
`, { mode: 448 });
|
|
38628
39661
|
return path;
|
|
@@ -38655,14 +39688,14 @@ function sshExec(t, remoteCmd, opts = {}) {
|
|
|
38655
39688
|
SSH_ASKPASS_REQUIRE: "force",
|
|
38656
39689
|
HANOMAN_SSH_PASSWORD: opts.password
|
|
38657
39690
|
} : process.env;
|
|
38658
|
-
return new Promise((
|
|
39691
|
+
return new Promise((resolve22) => {
|
|
38659
39692
|
const p3 = spawn4(sshBin(), args, { stdio: ["pipe", "pipe", "pipe"], env });
|
|
38660
39693
|
let out4 = "";
|
|
38661
39694
|
const timer9 = setTimeout(() => p3.kill("SIGKILL"), opts.timeoutMs ?? 6e4);
|
|
38662
39695
|
const done = (r) => {
|
|
38663
39696
|
clearTimeout(timer9);
|
|
38664
|
-
if (askpass)
|
|
38665
|
-
|
|
39697
|
+
if (askpass) rmSync7(dirname10(askpass), { recursive: true, force: true });
|
|
39698
|
+
resolve22(r);
|
|
38666
39699
|
};
|
|
38667
39700
|
p3.stdout.on("data", (d) => {
|
|
38668
39701
|
out4 += d;
|
|
@@ -38682,16 +39715,16 @@ function sshExec(t, remoteCmd, opts = {}) {
|
|
|
38682
39715
|
// src/services/vps-audit.ts
|
|
38683
39716
|
init_db();
|
|
38684
39717
|
import { existsSync as existsSync14, readFileSync as readFileSync9 } from "node:fs";
|
|
38685
|
-
import { join as
|
|
39718
|
+
import { join as join24 } from "node:path";
|
|
38686
39719
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
38687
39720
|
|
|
38688
39721
|
// src/runner/deps.ts
|
|
38689
39722
|
import { existsSync as existsSync13 } from "node:fs";
|
|
38690
|
-
import { dirname as dirname11, join as
|
|
39723
|
+
import { dirname as dirname11, join as join23 } from "node:path";
|
|
38691
39724
|
function repoRootFrom(startDir) {
|
|
38692
39725
|
let dir2 = startDir;
|
|
38693
39726
|
for (let i = 0; i < 8; i++) {
|
|
38694
|
-
if (existsSync13(
|
|
39727
|
+
if (existsSync13(join23(dir2, "pnpm-workspace.yaml"))) return dir2;
|
|
38695
39728
|
const parent = dirname11(dir2);
|
|
38696
39729
|
if (parent === dir2) break;
|
|
38697
39730
|
dir2 = parent;
|
|
@@ -38700,6 +39733,9 @@ function repoRootFrom(startDir) {
|
|
|
38700
39733
|
}
|
|
38701
39734
|
var repoRoot = (startDir = process.cwd()) => repoRootFrom(startDir);
|
|
38702
39735
|
|
|
39736
|
+
// src/services/vps-audit.ts
|
|
39737
|
+
init_sync_notify();
|
|
39738
|
+
|
|
38703
39739
|
// src/vps/scoring.ts
|
|
38704
39740
|
var pct = (fulfilled, applicable) => applicable === 0 ? 100 : Math.round(fulfilled / applicable * 100);
|
|
38705
39741
|
function scoreCompliance(probeStatus, states, items = CATALOG) {
|
|
@@ -38801,7 +39837,7 @@ var packagedScript = (f) => fileURLToPath3(new URL(`../scripts/vps/${f}`, import
|
|
|
38801
39837
|
var moduleDir = () => fileURLToPath3(new URL(".", import.meta.url));
|
|
38802
39838
|
var scriptPath = (f) => {
|
|
38803
39839
|
const packed = packagedScript(f);
|
|
38804
|
-
return existsSync14(packed) ? packed :
|
|
39840
|
+
return existsSync14(packed) ? packed : join24(repoRoot(moduleDir()), "server", "scripts", "vps", f);
|
|
38805
39841
|
};
|
|
38806
39842
|
async function itemStatesOf(vpsId) {
|
|
38807
39843
|
const rows = await prisma.vpsItemState.findMany({ where: { vpsId } });
|
|
@@ -39009,27 +40045,27 @@ async function buildChecklist(vpsId) {
|
|
|
39009
40045
|
init_src2();
|
|
39010
40046
|
init_config2();
|
|
39011
40047
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
39012
|
-
import { chmodSync as
|
|
40048
|
+
import { chmodSync as chmodSync7, copyFileSync, existsSync as existsSync15, mkdirSync as mkdirSync11, readFileSync as readFileSync12, unlinkSync as unlinkSync2 } from "node:fs";
|
|
39013
40049
|
import { homedir as homedir5 } from "node:os";
|
|
39014
|
-
import { join as
|
|
40050
|
+
import { join as join25 } from "node:path";
|
|
39015
40051
|
var keyDir = () => effectiveStr("HANOMAN_SSH_KEY_DIR") ?? resolveDataDirs().sshKeys;
|
|
39016
40052
|
var KEY_FILES = ["id_ed25519", "id_ed25519.pub"];
|
|
39017
40053
|
function adoptLegacyKey(dir2) {
|
|
39018
40054
|
if (effectiveStr("HANOMAN_SSH_KEY_DIR")) return;
|
|
39019
|
-
const legacy =
|
|
40055
|
+
const legacy = join25(homedir5(), ".hanoman");
|
|
39020
40056
|
if (legacy === dir2) return;
|
|
39021
|
-
if (!KEY_FILES.every((f) => existsSync15(
|
|
40057
|
+
if (!KEY_FILES.every((f) => existsSync15(join25(legacy, f)))) return;
|
|
39022
40058
|
mkdirSync11(dir2, { recursive: true, mode: 448 });
|
|
39023
40059
|
for (const f of KEY_FILES) {
|
|
39024
|
-
copyFileSync(
|
|
39025
|
-
|
|
40060
|
+
copyFileSync(join25(legacy, f), join25(dir2, f));
|
|
40061
|
+
chmodSync7(join25(dir2, f), 384);
|
|
39026
40062
|
}
|
|
39027
|
-
for (const f of KEY_FILES) unlinkSync2(
|
|
40063
|
+
for (const f of KEY_FILES) unlinkSync2(join25(legacy, f));
|
|
39028
40064
|
console.log(`vps: key SSH dipindah dari ${legacy} ke ${dir2} (SPEC-846 \u2014 satu batas backup)`);
|
|
39029
40065
|
}
|
|
39030
40066
|
function ensureHanomanKey() {
|
|
39031
40067
|
const dir2 = keyDir();
|
|
39032
|
-
const privPath =
|
|
40068
|
+
const privPath = join25(dir2, KEY_FILES[0]);
|
|
39033
40069
|
const pubPath = `${privPath}.pub`;
|
|
39034
40070
|
if (!existsSync15(privPath)) adoptLegacyKey(dir2);
|
|
39035
40071
|
if (!existsSync15(privPath)) {
|
|
@@ -39058,6 +40094,7 @@ async function bootstrapKey(t, password) {
|
|
|
39058
40094
|
// src/routes/vps.ts
|
|
39059
40095
|
init_pty();
|
|
39060
40096
|
init_settings3();
|
|
40097
|
+
init_sync_notify();
|
|
39061
40098
|
function keyMissing(v) {
|
|
39062
40099
|
return !!v.keyPath && !existsSync16(v.keyPath);
|
|
39063
40100
|
}
|
|
@@ -39341,7 +40378,7 @@ init_config2();
|
|
|
39341
40378
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
39342
40379
|
import { readFileSync as readFileSync14 } from "node:fs";
|
|
39343
40380
|
import { homedir as homedir7 } from "node:os";
|
|
39344
|
-
import { join as
|
|
40381
|
+
import { join as join26 } from "node:path";
|
|
39345
40382
|
var USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
|
|
39346
40383
|
var TTL_MS2 = 3e4;
|
|
39347
40384
|
var lastOk = null;
|
|
@@ -39354,7 +40391,7 @@ var LABELS = {
|
|
|
39354
40391
|
var humanize = (k) => k.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
39355
40392
|
var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
39356
40393
|
function credsFile() {
|
|
39357
|
-
return
|
|
40394
|
+
return join26(effectiveStr("CLAUDE_CONFIG_DIR") ?? join26(homedir7(), ".claude"), ".credentials.json");
|
|
39358
40395
|
}
|
|
39359
40396
|
function readAccessToken() {
|
|
39360
40397
|
if (process.platform === "darwin" && !effectiveStr("CLAUDE_CONFIG_DIR")) {
|
|
@@ -39428,14 +40465,14 @@ async function getLimits() {
|
|
|
39428
40465
|
// src/services/codex-limits.ts
|
|
39429
40466
|
import { open as open3, readdir as readdir5, stat as stat2 } from "node:fs/promises";
|
|
39430
40467
|
import { homedir as homedir8 } from "node:os";
|
|
39431
|
-
import { join as
|
|
40468
|
+
import { join as join27 } from "node:path";
|
|
39432
40469
|
var TTL_MS3 = 3e4;
|
|
39433
40470
|
var STALE_AFTER_MS = 12 * 36e5;
|
|
39434
40471
|
var TAIL_BYTES = 512 * 1024;
|
|
39435
40472
|
var MAX_FILES = 8;
|
|
39436
40473
|
var cache2 = null;
|
|
39437
40474
|
var freshUntil2 = 0;
|
|
39438
|
-
var codexSessionsDir = () =>
|
|
40475
|
+
var codexSessionsDir = () => join27(process.env.CODEX_HOME ?? join27(homedir8(), ".codex"), "sessions");
|
|
39439
40476
|
var UNAVAILABLE = { status: "unavailable", windows: [], fetchedAt: null, plan: null };
|
|
39440
40477
|
function windowLabel(minutes) {
|
|
39441
40478
|
if (minutes === 300) return "Sesi 5 jam";
|
|
@@ -39468,7 +40505,7 @@ async function recentRollouts(dir2) {
|
|
|
39468
40505
|
return [];
|
|
39469
40506
|
}
|
|
39470
40507
|
const stamped = await Promise.all(entries3.map(async (rel) => {
|
|
39471
|
-
const full =
|
|
40508
|
+
const full = join27(dir2, rel);
|
|
39472
40509
|
try {
|
|
39473
40510
|
return { full, mtime: (await stat2(full)).mtimeMs };
|
|
39474
40511
|
} catch {
|
|
@@ -39545,41 +40582,8 @@ async function limits(app2) {
|
|
|
39545
40582
|
app2.get("/limits/codex", async () => getCodexLimits());
|
|
39546
40583
|
}
|
|
39547
40584
|
|
|
39548
|
-
// src/services/codex-version.ts
|
|
39549
|
-
init_src();
|
|
39550
|
-
init_config2();
|
|
39551
|
-
import { execFile as execFile14 } from "node:child_process";
|
|
39552
|
-
import { promisify as promisify13 } from "node:util";
|
|
39553
|
-
var run3 = promisify13(execFile14);
|
|
39554
|
-
var CODEX_MIN_CLIENT = "0.144.0";
|
|
39555
|
-
var TTL_MS4 = 5 * 6e4;
|
|
39556
|
-
var cache3 = null;
|
|
39557
|
-
var codexBin2 = () => effectiveStr("HANOMAN_CODEX_BIN") ?? "codex";
|
|
39558
|
-
function parseCodexVersion(out4) {
|
|
39559
|
-
const m = /(\d+)\.(\d+)\.(\d+)/.exec(out4);
|
|
39560
|
-
return m ? `${m[1]}.${m[2]}.${m[3]}` : null;
|
|
39561
|
-
}
|
|
39562
|
-
async function getCodexVersion(now = Date.now()) {
|
|
39563
|
-
if (cache3 && now - cache3.at < TTL_MS4) return cache3.version;
|
|
39564
|
-
let version = null;
|
|
39565
|
-
try {
|
|
39566
|
-
const { stdout } = await run3(codexBin2(), ["--version"], { timeout: 5e3 });
|
|
39567
|
-
version = parseCodexVersion(stdout);
|
|
39568
|
-
} catch {
|
|
39569
|
-
}
|
|
39570
|
-
cache3 = { at: now, version };
|
|
39571
|
-
return version;
|
|
39572
|
-
}
|
|
39573
|
-
async function codexVersionInfo() {
|
|
39574
|
-
const version = await getCodexVersion();
|
|
39575
|
-
return {
|
|
39576
|
-
version,
|
|
39577
|
-
minRequired: CODEX_MIN_CLIENT,
|
|
39578
|
-
ok: version === null || cmpVersion(version, CODEX_MIN_CLIENT) >= 0
|
|
39579
|
-
};
|
|
39580
|
-
}
|
|
39581
|
-
|
|
39582
40585
|
// src/routes/codex.ts
|
|
40586
|
+
init_codex_version();
|
|
39583
40587
|
async function codex(app2) {
|
|
39584
40588
|
app2.get("/codex/version", async () => codexVersionInfo());
|
|
39585
40589
|
}
|
|
@@ -39776,6 +40780,35 @@ async function presenceView(o = {}) {
|
|
|
39776
40780
|
return { enabled: rows.length > 0, devices: devices2 };
|
|
39777
40781
|
}
|
|
39778
40782
|
|
|
40783
|
+
// src/services/pending-counts.ts
|
|
40784
|
+
init_src();
|
|
40785
|
+
init_db();
|
|
40786
|
+
var PRD_TTL_MS = 6e4;
|
|
40787
|
+
var prdCache = null;
|
|
40788
|
+
async function prdDraftCount(now, prds) {
|
|
40789
|
+
if (prdCache && now - prdCache.at < PRD_TTL_MS) return prdCache.count;
|
|
40790
|
+
try {
|
|
40791
|
+
const count = (await prds()).filter((p3) => p3.status === "draft").length;
|
|
40792
|
+
prdCache = { at: now, count };
|
|
40793
|
+
} catch {
|
|
40794
|
+
prdCache = { at: now, count: prdCache?.count ?? 0 };
|
|
40795
|
+
}
|
|
40796
|
+
return prdCache.count;
|
|
40797
|
+
}
|
|
40798
|
+
async function pendingCounts(now = Date.now(), prds = listAllPrds) {
|
|
40799
|
+
const [tickets2, issues, backlog, lead, prd] = await Promise.all([
|
|
40800
|
+
prisma.ticket.count({ where: { status: "new" } }),
|
|
40801
|
+
prisma.githubIssue.count({ where: { status: "new" } }),
|
|
40802
|
+
// `startedAt` = kapan sesi PERTAMA lahir (ADR-0090), null = belum pernah dikerjakan. Stage
|
|
40803
|
+
// `done` dikecualikan karena item bisa ditandai selesai manual tanpa sesi (SPEC-804/ADR-0120) —
|
|
40804
|
+
// tanpa gerbang itu ia terhitung selamanya sebagai pekerjaan yang belum diajukan.
|
|
40805
|
+
prisma.spec.count({ where: { startedAt: null, stage: { not: "done" } } }),
|
|
40806
|
+
prisma.leadFlow.count({ where: { status: { in: [...OPEN_LEAD_FLOW_STATUSES] } } }),
|
|
40807
|
+
prdDraftCount(now, prds)
|
|
40808
|
+
]);
|
|
40809
|
+
return { ...EMPTY_PENDING, triage: tickets2 + issues, backlog, prd, lead };
|
|
40810
|
+
}
|
|
40811
|
+
|
|
39779
40812
|
// src/services/events.ts
|
|
39780
40813
|
init_db();
|
|
39781
40814
|
init_config2();
|
|
@@ -40653,7 +41686,14 @@ var GROUPS = [
|
|
|
40653
41686
|
// dihitung). 3 dtk: presence berdenyut 30 dtk, jadi kadens lebih rapat hanya menambah build tanpa
|
|
40654
41687
|
// menambah informasi. `presenceView` menyegarkan sesi mesin ini sendiri di dalamnya — satu
|
|
40655
41688
|
// `tmux list-panes` asinkron, tak menahan event loop.
|
|
40656
|
-
{ everyTicks: 3, cookieOnly: true, last: "", build: async () => ({ t: "presence", ...await presenceView() }) }
|
|
41689
|
+
{ everyTicks: 3, cookieOnly: true, last: "", build: async () => ({ t: "presence", ...await presenceView() }) },
|
|
41690
|
+
// SPEC-961 · grup GLOBAL ke-11 · angka "butuh pengajuan" untuk badge sidebar. 5 dtk: badge bukan
|
|
41691
|
+
// board — yang dijanjikannya adalah "ada yang menunggu", bukan detik keberapa ia muncul — dan
|
|
41692
|
+
// dedup signature membuat frame lahir hanya saat salah satu angka berubah (jarang). Empat angka,
|
|
41693
|
+
// bukan empat grup: keempatnya berubah dari peristiwa yang sama (item diajukan/diputuskan) dan
|
|
41694
|
+
// dibaca satu komponen yang sama. `cookieOnly` TIDAK dipasang — muatannya jumlah agregat tanpa
|
|
41695
|
+
// id/judul, dan agent token sudah boleh membaca daftar sumbernya lewat capability masing-masing.
|
|
41696
|
+
{ everyTicks: 5, last: "", build: async () => ({ t: "pending", counts: await pendingCounts() }) }
|
|
40657
41697
|
];
|
|
40658
41698
|
function broadcast2(msg, cookieOnly = false) {
|
|
40659
41699
|
const s2 = JSON.stringify(msg);
|
|
@@ -41180,7 +42220,7 @@ init_config2();
|
|
|
41180
42220
|
init_src2();
|
|
41181
42221
|
import { randomUUID as randomUUID8 } from "node:crypto";
|
|
41182
42222
|
import { mkdir as mkdir5, writeFile as writeFile4, readFile as readFile4, unlink as unlink4, readdir as readdir6, stat as stat3 } from "node:fs/promises";
|
|
41183
|
-
import { join as
|
|
42223
|
+
import { join as join28, resolve as resolve19, basename as basename6 } from "node:path";
|
|
41184
42224
|
var MAX_TRANSCRIPT_BYTES = 1024 * 1024;
|
|
41185
42225
|
function transcriptDir() {
|
|
41186
42226
|
return resolve19(effectiveStr("HANOMAN_TRANSCRIPT_DIR")?.trim() || resolveDataDirs().transcripts);
|
|
@@ -41201,13 +42241,13 @@ async function saveTranscript(text) {
|
|
|
41201
42241
|
const dir2 = transcriptDir();
|
|
41202
42242
|
await mkdir5(dir2, { recursive: true, mode: 448 });
|
|
41203
42243
|
const key = `${randomUUID8()}.log`;
|
|
41204
|
-
await writeFile4(
|
|
42244
|
+
await writeFile4(join28(dir2, key), body, { encoding: "utf8", mode: 384 });
|
|
41205
42245
|
return { key, bytes: Buffer.byteLength(body, "utf8"), truncated };
|
|
41206
42246
|
}
|
|
41207
42247
|
async function readTranscript(key) {
|
|
41208
42248
|
if (!key) return null;
|
|
41209
42249
|
try {
|
|
41210
|
-
return await readFile4(
|
|
42250
|
+
return await readFile4(join28(transcriptDir(), basename6(key)), "utf8");
|
|
41211
42251
|
} catch {
|
|
41212
42252
|
return null;
|
|
41213
42253
|
}
|
|
@@ -41215,7 +42255,7 @@ async function readTranscript(key) {
|
|
|
41215
42255
|
async function deleteTranscript(key) {
|
|
41216
42256
|
if (!key) return;
|
|
41217
42257
|
try {
|
|
41218
|
-
await unlink4(
|
|
42258
|
+
await unlink4(join28(transcriptDir(), basename6(key)));
|
|
41219
42259
|
} catch (e) {
|
|
41220
42260
|
if (e.code !== "ENOENT") throw e;
|
|
41221
42261
|
}
|
|
@@ -41233,7 +42273,7 @@ async function listTranscripts() {
|
|
|
41233
42273
|
for (const name2 of names) {
|
|
41234
42274
|
if (!name2.endsWith(".log")) continue;
|
|
41235
42275
|
try {
|
|
41236
|
-
rows.push({ key: name2, mtimeMs: (await stat3(
|
|
42276
|
+
rows.push({ key: name2, mtimeMs: (await stat3(join28(dir2, name2))).mtimeMs });
|
|
41237
42277
|
} catch {
|
|
41238
42278
|
}
|
|
41239
42279
|
}
|
|
@@ -41287,11 +42327,11 @@ async function beginSession(b) {
|
|
|
41287
42327
|
});
|
|
41288
42328
|
}
|
|
41289
42329
|
async function finishSession(d) {
|
|
41290
|
-
const
|
|
42330
|
+
const open5 = await prisma.sessionHistory.findFirst({
|
|
41291
42331
|
where: { sessionId: d.sessionId, endedAt: null },
|
|
41292
42332
|
orderBy: { startedAt: "desc" }
|
|
41293
42333
|
});
|
|
41294
|
-
if (!
|
|
42334
|
+
if (!open5) return;
|
|
41295
42335
|
let t = { key: "", bytes: 0 };
|
|
41296
42336
|
if (d.transcript) {
|
|
41297
42337
|
try {
|
|
@@ -41301,7 +42341,7 @@ async function finishSession(d) {
|
|
|
41301
42341
|
}
|
|
41302
42342
|
}
|
|
41303
42343
|
await prisma.sessionHistory.update({
|
|
41304
|
-
where: { id:
|
|
42344
|
+
where: { id: open5.id },
|
|
41305
42345
|
data: {
|
|
41306
42346
|
endedAt: /* @__PURE__ */ new Date(),
|
|
41307
42347
|
endedReason: CLOSED,
|
|
@@ -41416,14 +42456,14 @@ async function reconcileTranscripts(opts = {}) {
|
|
|
41416
42456
|
return report;
|
|
41417
42457
|
}
|
|
41418
42458
|
async function reconcileHistory(liveSessionIds) {
|
|
41419
|
-
const
|
|
42459
|
+
const open5 = await prisma.sessionHistory.findMany({
|
|
41420
42460
|
where: { endedAt: null },
|
|
41421
42461
|
select: { id: true, sessionId: true, updatedAt: true }
|
|
41422
42462
|
});
|
|
41423
42463
|
const live = new Set(liveSessionIds);
|
|
41424
42464
|
const at = /* @__PURE__ */ new Date();
|
|
41425
42465
|
let closed = 0;
|
|
41426
|
-
for (const r of
|
|
42466
|
+
for (const r of open5) {
|
|
41427
42467
|
if (live.has(r.sessionId)) continue;
|
|
41428
42468
|
await prisma.sessionHistory.update({
|
|
41429
42469
|
where: { id: r.id },
|
|
@@ -41474,7 +42514,285 @@ async function session_history_default(app2) {
|
|
|
41474
42514
|
init_src();
|
|
41475
42515
|
init_session_event_token();
|
|
41476
42516
|
init_pty();
|
|
42517
|
+
|
|
42518
|
+
// src/services/agent-invocations.ts
|
|
42519
|
+
init_src();
|
|
42520
|
+
init_db();
|
|
42521
|
+
import { createHash as createHash8, randomUUID as randomUUID10 } from "node:crypto";
|
|
42522
|
+
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
42523
|
+
import { homedir as homedir9 } from "node:os";
|
|
42524
|
+
import { readFileSync as readFileSync16, realpathSync as realpathSync5, statSync as statSync4 } from "node:fs";
|
|
42525
|
+
import { isAbsolute as isAbsolute7, relative as relative3, resolve as resolve20 } from "node:path";
|
|
42526
|
+
var MAX_EXCERPT_BYTES = 4096;
|
|
42527
|
+
var MAX_TRANSCRIPT_BYTES2 = 10 * 1024 * 1024;
|
|
42528
|
+
var ANSI2 = /[\u001b\u009b](?:\][^\u0007]*(?:\u0007|\u001b\\)|\[[0-?]*[ -/]*[@-~]|[@-_])/g;
|
|
42529
|
+
var snapshotHashes = /* @__PURE__ */ new Map();
|
|
42530
|
+
var keyOf = (x) => `${x.sessionId}\0${x.runtimeInvocationId}`;
|
|
42531
|
+
var hash2 = (value) => createHash8("sha256").update(value).digest("hex");
|
|
42532
|
+
var defaultGitStatus = (cwd) => {
|
|
42533
|
+
try {
|
|
42534
|
+
return execFileSync5("git", ["-C", cwd, "status", "--porcelain=v1", "-z"], {
|
|
42535
|
+
encoding: "utf8",
|
|
42536
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
42537
|
+
});
|
|
42538
|
+
} catch {
|
|
42539
|
+
return null;
|
|
42540
|
+
}
|
|
42541
|
+
};
|
|
42542
|
+
function snapshot2(cwd, run4 = defaultGitStatus) {
|
|
42543
|
+
try {
|
|
42544
|
+
const value = run4(cwd);
|
|
42545
|
+
return value === null ? null : hash2(value);
|
|
42546
|
+
} catch {
|
|
42547
|
+
return null;
|
|
42548
|
+
}
|
|
42549
|
+
}
|
|
42550
|
+
var stripAnsi = (value) => value.replace(ANSI2, "");
|
|
42551
|
+
var utf8Prefix = (value, maxBytes) => {
|
|
42552
|
+
let out4 = "", bytes = 0;
|
|
42553
|
+
for (const char of value) {
|
|
42554
|
+
const next = Buffer.byteLength(char, "utf8");
|
|
42555
|
+
if (bytes + next > maxBytes) break;
|
|
42556
|
+
out4 += char;
|
|
42557
|
+
bytes += next;
|
|
42558
|
+
}
|
|
42559
|
+
return out4;
|
|
42560
|
+
};
|
|
42561
|
+
var transcriptRoots = () => [
|
|
42562
|
+
resolve20(process.env.CLAUDE_CONFIG_DIR ?? `${homedir9()}/.claude`),
|
|
42563
|
+
resolve20(process.env.CODEX_HOME ?? `${homedir9()}/.codex`)
|
|
42564
|
+
];
|
|
42565
|
+
var inside = (path, root) => {
|
|
42566
|
+
const rel = relative3(root, path);
|
|
42567
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute7(rel);
|
|
42568
|
+
};
|
|
42569
|
+
var EMPTY_USAGE = { inputTokens: null, outputTokens: null, cachedTokens: null };
|
|
42570
|
+
var nonnegativeInt = (value) => Number.isSafeInteger(value) && Number(value) >= 0 ? Number(value) : null;
|
|
42571
|
+
function usageFromText(text) {
|
|
42572
|
+
const found = [];
|
|
42573
|
+
const inspect = (value) => {
|
|
42574
|
+
if (!value || typeof value !== "object") return;
|
|
42575
|
+
const record2 = value;
|
|
42576
|
+
const usage2 = record2.usage;
|
|
42577
|
+
if (usage2 && typeof usage2 === "object") {
|
|
42578
|
+
const u = usage2;
|
|
42579
|
+
found.push([
|
|
42580
|
+
nonnegativeInt(u.input_tokens ?? u.inputTokens) ?? -1,
|
|
42581
|
+
nonnegativeInt(u.output_tokens ?? u.outputTokens) ?? -1,
|
|
42582
|
+
nonnegativeInt(u.cached_tokens ?? u.cachedTokens ?? u.cache_read_input_tokens ?? u.cacheReadInputTokens) ?? -1
|
|
42583
|
+
]);
|
|
42584
|
+
}
|
|
42585
|
+
};
|
|
42586
|
+
for (const line of text.split("\n")) {
|
|
42587
|
+
if (!line.trim()) continue;
|
|
42588
|
+
try {
|
|
42589
|
+
inspect(JSON.parse(line));
|
|
42590
|
+
} catch {
|
|
42591
|
+
}
|
|
42592
|
+
}
|
|
42593
|
+
if (found.length === 0) return EMPTY_USAGE;
|
|
42594
|
+
const max = (index) => {
|
|
42595
|
+
const values = found.map((entry) => entry[index]).filter((n2) => n2 >= 0);
|
|
42596
|
+
return values.length ? Math.max(...values) : null;
|
|
42597
|
+
};
|
|
42598
|
+
return { inputTokens: max(0), outputTokens: max(1), cachedTokens: max(2) };
|
|
42599
|
+
}
|
|
42600
|
+
function transcriptUsage(path, roots = transcriptRoots()) {
|
|
42601
|
+
if (!path) return EMPTY_USAGE;
|
|
42602
|
+
try {
|
|
42603
|
+
const real2 = realpathSync5(path);
|
|
42604
|
+
const safeRoots = roots.map((root) => realpathSync5(root));
|
|
42605
|
+
if (!safeRoots.some((root) => inside(real2, root))) return EMPTY_USAGE;
|
|
42606
|
+
const info = statSync4(real2);
|
|
42607
|
+
if (!info.isFile() || info.size > MAX_TRANSCRIPT_BYTES2) return EMPTY_USAGE;
|
|
42608
|
+
return usageFromText(readFileSync16(real2, "utf8"));
|
|
42609
|
+
} catch {
|
|
42610
|
+
return EMPTY_USAGE;
|
|
42611
|
+
}
|
|
42612
|
+
}
|
|
42613
|
+
async function startAgentInvocation(input, io = {}) {
|
|
42614
|
+
const startedAt = input.startedAt ?? /* @__PURE__ */ new Date();
|
|
42615
|
+
const existing = await prisma.agentInvocation.findUnique({
|
|
42616
|
+
where: { sessionId_runtimeInvocationId: {
|
|
42617
|
+
sessionId: input.sessionId,
|
|
42618
|
+
runtimeInvocationId: input.runtimeInvocationId
|
|
42619
|
+
} }
|
|
42620
|
+
});
|
|
42621
|
+
const row = await prisma.agentInvocation.upsert({
|
|
42622
|
+
where: { sessionId_runtimeInvocationId: {
|
|
42623
|
+
sessionId: input.sessionId,
|
|
42624
|
+
runtimeInvocationId: input.runtimeInvocationId
|
|
42625
|
+
} },
|
|
42626
|
+
update: {},
|
|
42627
|
+
create: {
|
|
42628
|
+
id: randomUUID10(),
|
|
42629
|
+
sessionId: input.sessionId,
|
|
42630
|
+
projectId: input.projectId,
|
|
42631
|
+
specId: input.specId ?? null,
|
|
42632
|
+
runtime: input.runtime,
|
|
42633
|
+
runtimeInvocationId: input.runtimeInvocationId,
|
|
42634
|
+
customAgentId: input.customAgentId ?? null,
|
|
42635
|
+
agentName: input.agentName,
|
|
42636
|
+
model: input.model ?? null,
|
|
42637
|
+
status: "running",
|
|
42638
|
+
startedAt
|
|
42639
|
+
}
|
|
42640
|
+
});
|
|
42641
|
+
const before2 = snapshot2(input.cwd, io.gitStatus);
|
|
42642
|
+
if (before2 !== null) snapshotHashes.set(keyOf(input), before2);
|
|
42643
|
+
return { row, duplicate: existing !== null };
|
|
42644
|
+
}
|
|
42645
|
+
async function stopAgentInvocation(input, io = {}) {
|
|
42646
|
+
const unique = { sessionId_runtimeInvocationId: {
|
|
42647
|
+
sessionId: input.sessionId,
|
|
42648
|
+
runtimeInvocationId: input.runtimeInvocationId
|
|
42649
|
+
} };
|
|
42650
|
+
const existing = await prisma.agentInvocation.findUnique({ where: unique });
|
|
42651
|
+
if (existing?.endedAt) return { row: existing, duplicate: true };
|
|
42652
|
+
const endedAt = input.endedAt ?? /* @__PURE__ */ new Date();
|
|
42653
|
+
const startedAt = existing?.startedAt ?? endedAt;
|
|
42654
|
+
const cleanResult = input.result === void 0 ? null : stripAnsi(input.result);
|
|
42655
|
+
const usage2 = transcriptUsage(input.transcriptPath, io.transcriptRoots);
|
|
42656
|
+
const before2 = snapshotHashes.get(keyOf(input));
|
|
42657
|
+
const after = snapshot2(input.cwd, io.gitStatus);
|
|
42658
|
+
snapshotHashes.delete(keyOf(input));
|
|
42659
|
+
const evidence = {
|
|
42660
|
+
// Stop tanpa start lazim setelah restart server di tengah invocation. Waktu start dan status
|
|
42661
|
+
// runtime sudah hilang; simpan baris sintetis yang dapat diaudit tanpa mengarang durasi 0 ms.
|
|
42662
|
+
status: existing ? input.status ?? "completed" : "completed",
|
|
42663
|
+
endedAt,
|
|
42664
|
+
durationMs: existing ? Math.max(0, endedAt.getTime() - startedAt.getTime()) : null,
|
|
42665
|
+
...usage2,
|
|
42666
|
+
resultExcerpt: cleanResult === null ? null : utf8Prefix(cleanResult, MAX_EXCERPT_BYTES),
|
|
42667
|
+
resultHash: cleanResult === null ? null : hash2(cleanResult),
|
|
42668
|
+
workspaceChanged: before2 !== void 0 && after !== null && before2 !== after
|
|
42669
|
+
};
|
|
42670
|
+
if (existing) {
|
|
42671
|
+
const row2 = await prisma.agentInvocation.update({ where: { id: existing.id }, data: evidence });
|
|
42672
|
+
return { row: row2, duplicate: false };
|
|
42673
|
+
}
|
|
42674
|
+
const row = await prisma.agentInvocation.create({
|
|
42675
|
+
data: {
|
|
42676
|
+
id: randomUUID10(),
|
|
42677
|
+
sessionId: input.sessionId,
|
|
42678
|
+
projectId: input.projectId,
|
|
42679
|
+
specId: input.specId ?? null,
|
|
42680
|
+
runtime: input.runtime,
|
|
42681
|
+
runtimeInvocationId: input.runtimeInvocationId,
|
|
42682
|
+
customAgentId: input.customAgentId ?? null,
|
|
42683
|
+
agentName: input.agentName,
|
|
42684
|
+
model: input.model ?? null,
|
|
42685
|
+
startedAt,
|
|
42686
|
+
...evidence
|
|
42687
|
+
}
|
|
42688
|
+
});
|
|
42689
|
+
return { row, duplicate: false };
|
|
42690
|
+
}
|
|
42691
|
+
async function reconcileAgentInvocations(liveSessionIds) {
|
|
42692
|
+
const live = new Set(liveSessionIds);
|
|
42693
|
+
const open5 = await prisma.agentInvocation.findMany({ where: { status: "running" } });
|
|
42694
|
+
let changed = 0;
|
|
42695
|
+
const endedAt = /* @__PURE__ */ new Date();
|
|
42696
|
+
for (const row of open5) {
|
|
42697
|
+
if (live.has(row.sessionId)) continue;
|
|
42698
|
+
await prisma.agentInvocation.update({
|
|
42699
|
+
where: { id: row.id },
|
|
42700
|
+
data: {
|
|
42701
|
+
status: "abandoned",
|
|
42702
|
+
endedAt,
|
|
42703
|
+
durationMs: Math.max(0, endedAt.getTime() - row.startedAt.getTime())
|
|
42704
|
+
}
|
|
42705
|
+
});
|
|
42706
|
+
changed++;
|
|
42707
|
+
}
|
|
42708
|
+
return changed;
|
|
42709
|
+
}
|
|
42710
|
+
var agentInvocationView = (row) => ({
|
|
42711
|
+
id: row.id,
|
|
42712
|
+
sessionId: row.sessionId,
|
|
42713
|
+
projectId: row.projectId,
|
|
42714
|
+
specId: row.specId,
|
|
42715
|
+
runtime: row.runtime === "codex" ? "codex" : "claude",
|
|
42716
|
+
customAgentId: row.customAgentId,
|
|
42717
|
+
agentName: row.agentName,
|
|
42718
|
+
model: row.model,
|
|
42719
|
+
status: row.status,
|
|
42720
|
+
startedAt: row.startedAt.toISOString(),
|
|
42721
|
+
endedAt: row.endedAt?.toISOString() ?? null,
|
|
42722
|
+
durationMs: row.durationMs,
|
|
42723
|
+
inputTokens: row.inputTokens,
|
|
42724
|
+
outputTokens: row.outputTokens,
|
|
42725
|
+
cachedTokens: row.cachedTokens,
|
|
42726
|
+
resultExcerpt: row.resultExcerpt,
|
|
42727
|
+
resultHash: row.resultHash,
|
|
42728
|
+
workspaceChanged: row.workspaceChanged,
|
|
42729
|
+
disposition: AGENT_DISPOSITIONS.includes(row.disposition) ? row.disposition : "pending",
|
|
42730
|
+
dispositionNote: row.dispositionNote,
|
|
42731
|
+
evaluatedAt: row.evaluatedAt?.toISOString() ?? null
|
|
42732
|
+
});
|
|
42733
|
+
var median = (values) => {
|
|
42734
|
+
if (values.length === 0) return null;
|
|
42735
|
+
values.sort((a, b) => a - b);
|
|
42736
|
+
const middle = Math.floor(values.length / 2);
|
|
42737
|
+
return values.length % 2 === 1 ? values[middle] : (values[middle - 1] + values[middle]) / 2;
|
|
42738
|
+
};
|
|
42739
|
+
var availableSum = (values) => {
|
|
42740
|
+
const known = values.filter((value) => value !== null);
|
|
42741
|
+
return known.length ? known.reduce((sum, value) => sum + value, 0) : null;
|
|
42742
|
+
};
|
|
42743
|
+
async function agentMetrics(query2) {
|
|
42744
|
+
const where = {
|
|
42745
|
+
...query2.projectId ? { projectId: query2.projectId } : {},
|
|
42746
|
+
...query2.from || query2.to ? { startedAt: {
|
|
42747
|
+
...query2.from ? { gte: query2.from } : {},
|
|
42748
|
+
...query2.to ? { lte: query2.to } : {}
|
|
42749
|
+
} } : {}
|
|
42750
|
+
};
|
|
42751
|
+
const rows = await prisma.agentInvocation.findMany({ where, orderBy: { startedAt: "desc" } });
|
|
42752
|
+
const groups = /* @__PURE__ */ new Map();
|
|
42753
|
+
for (const row of rows) groups.set(row.agentName, [...groups.get(row.agentName) ?? [], row]);
|
|
42754
|
+
const agents = [...groups.entries()].map(([agentName, invocations]) => {
|
|
42755
|
+
const dispositions = { pending: 0, accepted: 0, partial: 0, rejected: 0, falsePositive: 0 };
|
|
42756
|
+
for (const row of invocations) {
|
|
42757
|
+
if (row.disposition === "accepted") dispositions.accepted++;
|
|
42758
|
+
else if (row.disposition === "partial") dispositions.partial++;
|
|
42759
|
+
else if (row.disposition === "rejected") dispositions.rejected++;
|
|
42760
|
+
else if (row.disposition === "false-positive") dispositions.falsePositive++;
|
|
42761
|
+
else dispositions.pending++;
|
|
42762
|
+
}
|
|
42763
|
+
const evaluated = dispositions.accepted + dispositions.partial + dispositions.rejected + dispositions.falsePositive;
|
|
42764
|
+
return {
|
|
42765
|
+
agentName,
|
|
42766
|
+
invocationCount: invocations.length,
|
|
42767
|
+
medianDurationMs: median(invocations.flatMap((row) => row.durationMs === null ? [] : [row.durationMs])),
|
|
42768
|
+
inputTokens: availableSum(invocations.map((row) => row.inputTokens)),
|
|
42769
|
+
outputTokens: availableSum(invocations.map((row) => row.outputTokens)),
|
|
42770
|
+
cachedTokens: availableSum(invocations.map((row) => row.cachedTokens)),
|
|
42771
|
+
dispositions,
|
|
42772
|
+
operationalPrecision: evaluated ? (dispositions.accepted + dispositions.partial) / evaluated : null,
|
|
42773
|
+
workspaceChanged: invocations.some((row) => row.workspaceChanged)
|
|
42774
|
+
};
|
|
42775
|
+
}).sort((a, b) => a.agentName.localeCompare(b.agentName));
|
|
42776
|
+
return { agents, recent: rows.slice(0, 100).map(agentInvocationView) };
|
|
42777
|
+
}
|
|
42778
|
+
async function updateAgentInvocationDisposition(id, disposition, note) {
|
|
42779
|
+
const exists = await prisma.agentInvocation.findUnique({ where: { id } });
|
|
42780
|
+
if (!exists) return null;
|
|
42781
|
+
const row = await prisma.agentInvocation.update({
|
|
42782
|
+
where: { id },
|
|
42783
|
+
data: {
|
|
42784
|
+
disposition,
|
|
42785
|
+
dispositionNote: note?.trim() || null,
|
|
42786
|
+
evaluatedAt: /* @__PURE__ */ new Date()
|
|
42787
|
+
}
|
|
42788
|
+
});
|
|
42789
|
+
return agentInvocationView(row);
|
|
42790
|
+
}
|
|
42791
|
+
|
|
42792
|
+
// src/routes/session-events.ts
|
|
41477
42793
|
var bearer = (h) => /^Bearer (.+)$/.exec(h ?? "")?.[1] ?? "";
|
|
42794
|
+
var recordOf = (value) => value && typeof value === "object" ? value : null;
|
|
42795
|
+
var boundedString = (value, max) => typeof value === "string" && value.length > 0 && value.length <= max ? value : void 0;
|
|
41478
42796
|
async function session_events_default(app2) {
|
|
41479
42797
|
app2.post("/session-events", async (req, reply) => {
|
|
41480
42798
|
const sessionId2 = String(req.headers["x-hanoman-session"] ?? "");
|
|
@@ -41483,6 +42801,38 @@ async function session_events_default(app2) {
|
|
|
41483
42801
|
return reply.code(401).send({ error: "unauthorized" });
|
|
41484
42802
|
const s2 = await getSessionAsync(sessionId2);
|
|
41485
42803
|
if (!s2 || s2.exited) return reply.code(404).send({ error: "live session not found" });
|
|
42804
|
+
const body = recordOf(req.body);
|
|
42805
|
+
const lifecycle = body?.hook_event_name;
|
|
42806
|
+
if (body && (lifecycle === "SubagentStart" || lifecycle === "SubagentStop")) {
|
|
42807
|
+
const runtimeInvocationId = boundedString(
|
|
42808
|
+
body.agent_id ?? body.subagent_id ?? body.thread_id,
|
|
42809
|
+
500
|
|
42810
|
+
);
|
|
42811
|
+
const agentName = boundedString(body.agent_type ?? body.agent_name, 200);
|
|
42812
|
+
const meta = agentName ? (s2.agentRoster ?? []).find((agent) => agent.name === agentName) : void 0;
|
|
42813
|
+
if (!runtimeInvocationId || !meta) return reply.code(202).send({ ignored: true });
|
|
42814
|
+
const identity = {
|
|
42815
|
+
sessionId: sessionId2,
|
|
42816
|
+
projectId: s2.projectId,
|
|
42817
|
+
specId: s2.specId,
|
|
42818
|
+
runtime: s2.agent,
|
|
42819
|
+
runtimeInvocationId,
|
|
42820
|
+
customAgentId: meta.id,
|
|
42821
|
+
agentName: meta.name,
|
|
42822
|
+
model: meta.model,
|
|
42823
|
+
cwd: s2.cwd
|
|
42824
|
+
};
|
|
42825
|
+
const outcome = lifecycle === "SubagentStart" ? await startAgentInvocation(identity) : await stopAgentInvocation({
|
|
42826
|
+
...identity,
|
|
42827
|
+
status: body.status === "interrupted" ? "interrupted" : "completed",
|
|
42828
|
+
result: boundedString(body.last_assistant_message ?? body.result, 1e6),
|
|
42829
|
+
transcriptPath: boundedString(
|
|
42830
|
+
body.agent_transcript_path ?? body.transcript_path,
|
|
42831
|
+
4096
|
|
42832
|
+
)
|
|
42833
|
+
});
|
|
42834
|
+
return reply.code(202).send(outcome.duplicate ? { duplicate: true } : { accepted: true });
|
|
42835
|
+
}
|
|
41486
42836
|
const event = parseHookEvent(req.body);
|
|
41487
42837
|
if (!event) return reply.code(202).send({ ignored: true });
|
|
41488
42838
|
const r = await intakeAsk({
|
|
@@ -41576,9 +42926,9 @@ init_db();
|
|
|
41576
42926
|
init_db();
|
|
41577
42927
|
init_uploads();
|
|
41578
42928
|
init_src();
|
|
41579
|
-
import { createHash as
|
|
42929
|
+
import { createHash as createHash10, randomBytes as randomBytes6 } from "node:crypto";
|
|
41580
42930
|
function hashAccessKey(key) {
|
|
41581
|
-
return
|
|
42931
|
+
return createHash10("sha256").update(key).digest("hex");
|
|
41582
42932
|
}
|
|
41583
42933
|
function generateAccessKey() {
|
|
41584
42934
|
const key = "hnm_tkt_" + randomBytes6(24).toString("hex");
|
|
@@ -41588,13 +42938,13 @@ function generateShareToken() {
|
|
|
41588
42938
|
return "hnm_shr_" + randomBytes6(24).toString("hex");
|
|
41589
42939
|
}
|
|
41590
42940
|
async function createTicket(input) {
|
|
41591
|
-
const { key, hash:
|
|
42941
|
+
const { key, hash: hash3 } = generateAccessKey();
|
|
41592
42942
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
41593
42943
|
const max = await prisma.ticket.aggregate({ where: { projectId: input.projectId }, _max: { number: true } });
|
|
41594
42944
|
const number = (max._max.number ?? 0) + 1;
|
|
41595
42945
|
try {
|
|
41596
42946
|
const ticket = await prisma.ticket.create({
|
|
41597
|
-
data: { ...input, number, accessKeyHash:
|
|
42947
|
+
data: { ...input, number, accessKeyHash: hash3, shareToken: generateShareToken(), status: "new" }
|
|
41598
42948
|
});
|
|
41599
42949
|
return { ticket, key };
|
|
41600
42950
|
} catch (e) {
|
|
@@ -41621,6 +42971,7 @@ async function pruneOldTickets(now = Date.now()) {
|
|
|
41621
42971
|
// src/services/ticket-intake.ts
|
|
41622
42972
|
init_db();
|
|
41623
42973
|
init_notifications2();
|
|
42974
|
+
init_sync_notify();
|
|
41624
42975
|
init_uploads();
|
|
41625
42976
|
var TICKET_UPLOAD = { MAX_FILES: 3 };
|
|
41626
42977
|
async function parseTicketUpload(req) {
|
|
@@ -41784,10 +43135,12 @@ async function help_default(app2) {
|
|
|
41784
43135
|
|
|
41785
43136
|
// src/routes/tickets.ts
|
|
41786
43137
|
init_db();
|
|
43138
|
+
init_sync_notify();
|
|
41787
43139
|
init_uploads();
|
|
41788
43140
|
|
|
41789
43141
|
// src/services/ticket-accept.ts
|
|
41790
43142
|
init_db();
|
|
43143
|
+
init_sync_notify();
|
|
41791
43144
|
var attachmentData = (t, atts) => {
|
|
41792
43145
|
if (atts.length === 0) return "Tanpa lampiran.";
|
|
41793
43146
|
const list2 = atts.map(
|
|
@@ -42702,201 +44055,28 @@ async function changelog_default(app2) {
|
|
|
42702
44055
|
// src/routes/custom-agents.ts
|
|
42703
44056
|
init_src();
|
|
42704
44057
|
init_db();
|
|
42705
|
-
|
|
42706
|
-
|
|
42707
|
-
init_src();
|
|
42708
|
-
import { readFileSync as readFileSync16 } from "node:fs";
|
|
42709
|
-
import { homedir as homedir9 } from "node:os";
|
|
42710
|
-
import { join as join26 } from "node:path";
|
|
42711
|
-
var home = () => process.env.HOME || homedir9();
|
|
42712
|
-
var readJson2 = (path) => {
|
|
42713
|
-
try {
|
|
42714
|
-
return JSON.parse(readFileSync16(path, "utf8"));
|
|
42715
|
-
} catch {
|
|
42716
|
-
return null;
|
|
42717
|
-
}
|
|
42718
|
-
};
|
|
42719
|
-
var serversOf = (node) => {
|
|
42720
|
-
const ms = node?.mcpServers;
|
|
42721
|
-
if (!ms || typeof ms !== "object" || Array.isArray(ms)) return [];
|
|
42722
|
-
return Object.keys(ms);
|
|
42723
|
-
};
|
|
42724
|
-
var codexServers = () => {
|
|
42725
|
-
let text;
|
|
42726
|
-
try {
|
|
42727
|
-
text = readFileSync16(join26(home(), ".codex", "config.toml"), "utf8");
|
|
42728
|
-
} catch {
|
|
42729
|
-
return [];
|
|
42730
|
-
}
|
|
42731
|
-
const out4 = [];
|
|
42732
|
-
for (const m of text.matchAll(/^\s*\[mcp_servers\.(?:"([^"]+)"|([A-Za-z0-9_-]+))(?:\.[^\]]*)?\]/gm)) {
|
|
42733
|
-
const name2 = m[1] ?? m[2];
|
|
42734
|
-
if (name2) out4.push(name2);
|
|
42735
|
-
}
|
|
42736
|
-
return out4;
|
|
42737
|
-
};
|
|
42738
|
-
function mcpServerNames(repoDir) {
|
|
42739
|
-
const names = [];
|
|
42740
|
-
const claudeJson = readJson2(join26(home(), ".claude.json"));
|
|
42741
|
-
names.push(...serversOf(claudeJson));
|
|
42742
|
-
if (repoDir) {
|
|
42743
|
-
const projects = claudeJson?.projects;
|
|
42744
|
-
if (projects && typeof projects === "object") names.push(...serversOf(projects[repoDir]));
|
|
42745
|
-
names.push(...serversOf(readJson2(join26(repoDir, ".mcp.json"))));
|
|
42746
|
-
}
|
|
42747
|
-
names.push(...codexServers());
|
|
42748
|
-
return [...new Set(names.filter(Boolean))].sort((a, b) => a.localeCompare(b));
|
|
42749
|
-
}
|
|
42750
|
-
function agentToolCatalog(repoDir) {
|
|
42751
|
-
return [ALL_TOOLS_ENTRY, ...BUILTIN_AGENT_TOOLS, ...mcpServerNames(repoDir).map(mcpToolEntry)];
|
|
42752
|
-
}
|
|
42753
|
-
var agentToolIds = (repoDir) => agentToolCatalog(repoDir).map((t) => t.id);
|
|
42754
|
-
|
|
42755
|
-
// src/routes/custom-agents.ts
|
|
42756
|
-
init_settings3();
|
|
42757
|
-
|
|
42758
|
-
// src/services/builtin-agents.ts
|
|
42759
|
-
init_src();
|
|
42760
|
-
init_db();
|
|
44058
|
+
init_sync_notify();
|
|
44059
|
+
init_agent_tool_catalog();
|
|
42761
44060
|
init_settings3();
|
|
42762
|
-
|
|
42763
|
-
|
|
42764
|
-
var fingerprint = (name2, description, instructions, tools) => createHash9("sha256").update([name2, description, instructions, [...tools].join(",")].join(" ")).digest("hex").slice(0, 16);
|
|
42765
|
-
var builtinFingerprint = (a) => fingerprint(a.name, a.description, a.instructions, a.tools);
|
|
42766
|
-
var rowFingerprint = (r) => fingerprint(r.name, r.description, r.instructions, toolsOf(r.tools) ?? []);
|
|
42767
|
-
async function seedBuiltinAgents() {
|
|
42768
|
-
try {
|
|
42769
|
-
const setting = await getSetting();
|
|
42770
|
-
const stamps = { ...setting.builtinAgents };
|
|
42771
|
-
let changed = false;
|
|
42772
|
-
for (const a of BUILTIN_AGENTS) {
|
|
42773
|
-
const id = customAgentId(null, a.name);
|
|
42774
|
-
const fp = builtinFingerprint(a);
|
|
42775
|
-
const row = await prisma.customAgent.findUnique({ where: { id } });
|
|
42776
|
-
if (!row) {
|
|
42777
|
-
if (await findTombstone("customAgent", id)) continue;
|
|
42778
|
-
await prisma.customAgent.create({ data: {
|
|
42779
|
-
id,
|
|
42780
|
-
projectId: null,
|
|
42781
|
-
name: a.name,
|
|
42782
|
-
description: a.description,
|
|
42783
|
-
instructions: a.instructions,
|
|
42784
|
-
tools: [...a.tools],
|
|
42785
|
-
model: null,
|
|
42786
|
-
mentions: [],
|
|
42787
|
-
runtime: null,
|
|
42788
|
-
enabled: a.enabledByDefault
|
|
42789
|
-
} });
|
|
42790
|
-
await notifySynced("customAgent", id);
|
|
42791
|
-
stamps[a.name] = fp;
|
|
42792
|
-
changed = true;
|
|
42793
|
-
continue;
|
|
42794
|
-
}
|
|
42795
|
-
const stamped = stamps[a.name];
|
|
42796
|
-
if (!stamped || stamped === fp) continue;
|
|
42797
|
-
if (stamped !== rowFingerprint(row)) continue;
|
|
42798
|
-
await prisma.customAgent.update({ where: { id }, data: {
|
|
42799
|
-
description: a.description,
|
|
42800
|
-
instructions: a.instructions,
|
|
42801
|
-
tools: [...a.tools]
|
|
42802
|
-
// `enabled` TIDAK di sini. Sengaja.
|
|
42803
|
-
} });
|
|
42804
|
-
await notifySynced("customAgent", id);
|
|
42805
|
-
stamps[a.name] = fp;
|
|
42806
|
-
changed = true;
|
|
42807
|
-
}
|
|
42808
|
-
if (changed) {
|
|
42809
|
-
const data = { ...setting, builtinAgents: stamps };
|
|
42810
|
-
await prisma.setting.upsert({
|
|
42811
|
-
where: { id: 1 },
|
|
42812
|
-
update: { data },
|
|
42813
|
-
create: { id: 1, data }
|
|
42814
|
-
});
|
|
42815
|
-
}
|
|
42816
|
-
} catch {
|
|
42817
|
-
}
|
|
42818
|
-
}
|
|
42819
|
-
|
|
42820
|
-
// src/services/custom-agents.ts
|
|
42821
|
-
init_db();
|
|
42822
|
-
init_src();
|
|
42823
|
-
init_pty();
|
|
42824
|
-
var cache4 = [];
|
|
42825
|
-
var repoDirCache = /* @__PURE__ */ new Map();
|
|
42826
|
-
var asCustomAgent = (r) => ({
|
|
42827
|
-
id: r.id,
|
|
42828
|
-
projectId: r.projectId,
|
|
42829
|
-
name: r.name,
|
|
42830
|
-
description: r.description,
|
|
42831
|
-
instructions: r.instructions,
|
|
42832
|
-
tools: toolsOf(r.tools),
|
|
42833
|
-
model: r.model,
|
|
42834
|
-
mentions: mentionsOf(r.mentions),
|
|
42835
|
-
runtime: runtimeOf(r.runtime),
|
|
42836
|
-
enabled: r.enabled,
|
|
42837
|
-
createdAt: "",
|
|
42838
|
-
updatedAt: ""
|
|
42839
|
-
// tak dipakai lapis ini
|
|
42840
|
-
});
|
|
42841
|
-
async function loadCustomAgents() {
|
|
42842
|
-
try {
|
|
42843
|
-
cache4 = await prisma.customAgent.findMany();
|
|
42844
|
-
const projects = await prisma.project.findMany({ select: { id: true, repoDir: true } });
|
|
42845
|
-
const bindings = await prisma.localBinding.findMany({ select: { projectId: true, repoDir: true } });
|
|
42846
|
-
const next = /* @__PURE__ */ new Map();
|
|
42847
|
-
for (const p3 of projects) next.set(p3.id, p3.repoDir ?? null);
|
|
42848
|
-
for (const b of bindings) next.set(b.projectId, b.repoDir ?? null);
|
|
42849
|
-
repoDirCache = next;
|
|
42850
|
-
} catch {
|
|
42851
|
-
cache4 = [];
|
|
42852
|
-
repoDirCache = /* @__PURE__ */ new Map();
|
|
42853
|
-
}
|
|
42854
|
-
}
|
|
42855
|
-
function agentDefsFor(projectId, agent) {
|
|
42856
|
-
const globals = cache4.filter((r) => r.projectId === null).map(asCustomAgent);
|
|
42857
|
-
const project = cache4.filter((r) => r.projectId === projectId).map(asCustomAgent);
|
|
42858
|
-
const eff = effectiveAgents(globals, project).filter((a) => a.runtime === null || a.runtime === agent);
|
|
42859
|
-
const needsCatalog = eff.some((a) => (a.tools ?? []).includes(ALL_TOOLS));
|
|
42860
|
-
const catalogIds = needsCatalog ? agentToolIds(repoDirCache.get(projectId) ?? null) : [];
|
|
42861
|
-
return eff.map((a) => ({
|
|
42862
|
-
name: a.name,
|
|
42863
|
-
description: a.description,
|
|
42864
|
-
instructions: a.instructions,
|
|
42865
|
-
// Ekspansi terjadi DI SINI, sebelum `resolveTools` di runner: meneruskan `"*"` apa adanya
|
|
42866
|
-
// membuat claude membuangnya senyap (agen tanpa alat), sementara menerjemahkannya jadi `null`
|
|
42867
|
-
// membuat agen mewarisi SELURUH tool termasuk `Task` — lapis 2 anti-loop lenyap tanpa jejak.
|
|
42868
|
-
tools: expandTools(a.tools, catalogIds),
|
|
42869
|
-
model: a.model,
|
|
42870
|
-
mentions: a.mentions ?? []
|
|
42871
|
-
}));
|
|
42872
|
-
}
|
|
42873
|
-
function validateGraph(rows) {
|
|
42874
|
-
const projectScopes = [...new Set(rows.map((r) => r.projectId).filter((p3) => p3 !== null))];
|
|
42875
|
-
const globals = rows.filter((r) => r.projectId === null).map(asCustomAgent);
|
|
42876
|
-
for (const scope of [null, ...projectScopes]) {
|
|
42877
|
-
const project = scope === null ? [] : rows.filter((r) => r.projectId === scope).map(asCustomAgent);
|
|
42878
|
-
const nodes = effectiveAgents(globals, project).map((a) => ({ name: a.name, mentions: a.mentions ?? [] }));
|
|
42879
|
-
const cycle = detectCycle(nodes);
|
|
42880
|
-
if (cycle) return { scope: scope ?? GLOBAL_SCOPE, cycle };
|
|
42881
|
-
}
|
|
42882
|
-
return null;
|
|
42883
|
-
}
|
|
42884
|
-
function unknownMentions(row, all) {
|
|
42885
|
-
const visible = new Set(
|
|
42886
|
-
all.filter((r) => r.projectId === null || row.projectId !== null && r.projectId === row.projectId).map((r) => r.name)
|
|
42887
|
-
);
|
|
42888
|
-
return mentionsOf(row.mentions).filter((m) => !visible.has(m));
|
|
42889
|
-
}
|
|
42890
|
-
async function installCustomAgents() {
|
|
42891
|
-
await seedBuiltinAgents();
|
|
42892
|
-
await loadCustomAgents();
|
|
42893
|
-
registerCustomAgentSource((projectId, agent) => agentDefsFor(projectId, agent));
|
|
42894
|
-
}
|
|
42895
|
-
|
|
42896
|
-
// src/routes/custom-agents.ts
|
|
44061
|
+
init_builtin_agents2();
|
|
44062
|
+
init_custom_agents2();
|
|
42897
44063
|
var rowsOf = async () => await prisma.customAgent.findMany();
|
|
42898
44064
|
var stampsOf = async () => (await getSetting()).builtinAgents;
|
|
42899
|
-
var
|
|
44065
|
+
var availabilityOf = (r, requestedRuntime) => {
|
|
44066
|
+
const configuredRuntime = runtimeOf(r.runtime);
|
|
44067
|
+
if (requestedRuntime && configuredRuntime && configuredRuntime !== requestedRuntime) {
|
|
44068
|
+
return { available: false, availabilityReason: `hanya tersedia untuk runtime ${configuredRuntime}` };
|
|
44069
|
+
}
|
|
44070
|
+
const effectiveRuntime = requestedRuntime ?? configuredRuntime;
|
|
44071
|
+
if (workspacePolicyOf(r.workspacePolicy) === "isolated-worktree" && effectiveRuntime === "codex") {
|
|
44072
|
+
return {
|
|
44073
|
+
available: false,
|
|
44074
|
+
availabilityReason: "isolated-worktree belum tersedia untuk subagent Codex"
|
|
44075
|
+
};
|
|
44076
|
+
}
|
|
44077
|
+
return { available: true };
|
|
44078
|
+
};
|
|
44079
|
+
var view8 = (r, projectId, stamps = {}, requestedRuntime) => {
|
|
42900
44080
|
const builtin = r.projectId === null && BUILTIN_AGENT_NAMES.includes(r.name);
|
|
42901
44081
|
return {
|
|
42902
44082
|
id: r.id,
|
|
@@ -42908,12 +44088,18 @@ var view8 = (r, projectId, stamps = {}) => {
|
|
|
42908
44088
|
model: r.model,
|
|
42909
44089
|
mentions: mentionsOf(r.mentions),
|
|
42910
44090
|
runtime: runtimeOf(r.runtime),
|
|
44091
|
+
activation: activationOf(r.activation),
|
|
44092
|
+
effort: effortOf(r.effort),
|
|
44093
|
+
workspacePolicy: workspacePolicyOf(r.workspacePolicy),
|
|
44094
|
+
maxTurns: maxTurnsOf(r.maxTurns),
|
|
44095
|
+
timeoutSeconds: timeoutSecondsOf(r.timeoutSeconds),
|
|
42911
44096
|
enabled: r.enabled,
|
|
42912
44097
|
builtin,
|
|
42913
44098
|
// Sidik jari yang tak tercatat (baris menyeberang sync dari mesin lain, seed di sini belum
|
|
42914
44099
|
// pernah menyentuhnya) dibaca sebagai "disunting" — lebih baik menandai berlebih daripada
|
|
42915
44100
|
// menjanjikan "asli bawaan" untuk isi yang tak bisa kita buktikan.
|
|
42916
44101
|
builtinEdited: builtin ? stamps[r.name] !== rowFingerprint(r) : false,
|
|
44102
|
+
...availabilityOf(r, requestedRuntime),
|
|
42917
44103
|
...projectId ? { inherited: r.projectId === null } : {}
|
|
42918
44104
|
};
|
|
42919
44105
|
};
|
|
@@ -42931,6 +44117,11 @@ function modelProblem(model, runtime) {
|
|
|
42931
44117
|
const ok2 = modelsForRuntime(runtime).some((m) => m.id === model);
|
|
42932
44118
|
return ok2 ? null : { error: "model tak dikenal untuk runtime ini", model, runtime };
|
|
42933
44119
|
}
|
|
44120
|
+
function effortProblem(effort, runtime, model) {
|
|
44121
|
+
if (!effort) return null;
|
|
44122
|
+
const ok2 = effortsForRuntimeModel(runtime, model).includes(effort);
|
|
44123
|
+
return ok2 ? null : { error: "effort tak didukung runtime/model ini", effort, runtime, model };
|
|
44124
|
+
}
|
|
42934
44125
|
async function custom_agents_default(app2) {
|
|
42935
44126
|
app2.get("/custom-agents/catalog", async (req) => {
|
|
42936
44127
|
const projectId = req.query.projectId;
|
|
@@ -42940,8 +44131,13 @@ async function custom_agents_default(app2) {
|
|
|
42940
44131
|
runtimes: AGENT_RUNTIMES.map((id) => ({ id, label: AGENT_RUNTIME_LABELS[id] }))
|
|
42941
44132
|
};
|
|
42942
44133
|
});
|
|
42943
|
-
app2.get("/custom-agents", async (req) => {
|
|
42944
|
-
const
|
|
44134
|
+
app2.get("/custom-agents", async (req, reply) => {
|
|
44135
|
+
const query2 = req.query;
|
|
44136
|
+
const projectId = query2.projectId;
|
|
44137
|
+
const requestedRuntime = query2.runtime;
|
|
44138
|
+
if (requestedRuntime && !AGENT_RUNTIMES.includes(requestedRuntime)) {
|
|
44139
|
+
return reply.code(400).send({ error: "runtime harus claude atau codex" });
|
|
44140
|
+
}
|
|
42945
44141
|
const rows = await prisma.customAgent.findMany({
|
|
42946
44142
|
where: projectId ? { OR: [{ projectId: null }, { projectId }] } : { projectId: null },
|
|
42947
44143
|
orderBy: { name: "asc" }
|
|
@@ -42950,7 +44146,7 @@ async function custom_agents_default(app2) {
|
|
|
42950
44146
|
for (const r of rows) if (r.projectId === null) byName.set(r.name, r);
|
|
42951
44147
|
for (const r of rows) if (r.projectId !== null) byName.set(r.name, r);
|
|
42952
44148
|
const stamps = await stampsOf();
|
|
42953
|
-
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name)).map((r) => view8(r, projectId, stamps));
|
|
44149
|
+
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name)).map((r) => view8(r, projectId, stamps, requestedRuntime));
|
|
42954
44150
|
});
|
|
42955
44151
|
app2.post("/custom-agents", async (req, reply) => {
|
|
42956
44152
|
const parsed = zCreateCustomAgent.safeParse(req.body);
|
|
@@ -42963,6 +44159,8 @@ async function custom_agents_default(app2) {
|
|
|
42963
44159
|
if (tp) return reply.code(400).send(tp);
|
|
42964
44160
|
const mp = modelProblem(p3.model ?? null, p3.runtime ?? null);
|
|
42965
44161
|
if (mp) return reply.code(400).send(mp);
|
|
44162
|
+
const ep = effortProblem(p3.effort ?? null, p3.runtime ?? null, p3.model ?? null);
|
|
44163
|
+
if (ep) return reply.code(400).send(ep);
|
|
42966
44164
|
const id = customAgentId(projectId, p3.name);
|
|
42967
44165
|
if (await prisma.customAgent.findUnique({ where: { id } }))
|
|
42968
44166
|
return reply.code(409).send({ error: "nama sudah dipakai di scope ini", id });
|
|
@@ -42976,6 +44174,11 @@ async function custom_agents_default(app2) {
|
|
|
42976
44174
|
model: p3.model ?? null,
|
|
42977
44175
|
mentions: p3.mentions ?? [],
|
|
42978
44176
|
runtime: p3.runtime ?? null,
|
|
44177
|
+
activation: p3.activation ?? "always",
|
|
44178
|
+
effort: p3.effort ?? null,
|
|
44179
|
+
workspacePolicy: p3.workspacePolicy ?? "inherit",
|
|
44180
|
+
maxTurns: p3.maxTurns ?? null,
|
|
44181
|
+
timeoutSeconds: p3.timeoutSeconds ?? null,
|
|
42979
44182
|
enabled: p3.enabled ?? true
|
|
42980
44183
|
};
|
|
42981
44184
|
const all = [...await rowsOf(), candidate];
|
|
@@ -42993,6 +44196,11 @@ async function custom_agents_default(app2) {
|
|
|
42993
44196
|
model: candidate.model,
|
|
42994
44197
|
mentions: candidate.mentions,
|
|
42995
44198
|
runtime: candidate.runtime,
|
|
44199
|
+
activation: candidate.activation,
|
|
44200
|
+
effort: candidate.effort,
|
|
44201
|
+
workspacePolicy: candidate.workspacePolicy,
|
|
44202
|
+
maxTurns: candidate.maxTurns,
|
|
44203
|
+
timeoutSeconds: candidate.timeoutSeconds,
|
|
42996
44204
|
enabled: candidate.enabled
|
|
42997
44205
|
} });
|
|
42998
44206
|
await loadCustomAgents();
|
|
@@ -43010,6 +44218,12 @@ async function custom_agents_default(app2) {
|
|
|
43010
44218
|
if (!existing) return reply.code(404).send({ error: "not found" });
|
|
43011
44219
|
const before2 = existing;
|
|
43012
44220
|
const effRuntime = "runtime" in parsed.data ? parsed.data.runtime ?? null : runtimeOf(before2.runtime);
|
|
44221
|
+
const effWorkspacePolicy = "workspacePolicy" in parsed.data ? parsed.data.workspacePolicy ?? "inherit" : workspacePolicyOf(before2.workspacePolicy);
|
|
44222
|
+
if (("runtime" in parsed.data || "workspacePolicy" in parsed.data) && effWorkspacePolicy === "isolated-worktree" && effRuntime !== "claude") {
|
|
44223
|
+
return reply.code(400).send({
|
|
44224
|
+
error: "isolated-worktree hanya tersedia untuk agen ber-runtime Claude Code"
|
|
44225
|
+
});
|
|
44226
|
+
}
|
|
43013
44227
|
if (parsed.data.tools !== void 0) {
|
|
43014
44228
|
const tp = toolsProblem(parsed.data.tools, agentToolIds(await repoDirOf2(before2.projectId)));
|
|
43015
44229
|
if (tp) return reply.code(400).send(tp);
|
|
@@ -43021,12 +44235,25 @@ async function custom_agents_default(app2) {
|
|
|
43021
44235
|
);
|
|
43022
44236
|
if (mp) return reply.code(400).send(mp);
|
|
43023
44237
|
}
|
|
44238
|
+
if (parsed.data.effort !== void 0 || parsed.data.model !== void 0 || "runtime" in parsed.data) {
|
|
44239
|
+
const ep = effortProblem(
|
|
44240
|
+
parsed.data.effort !== void 0 ? parsed.data.effort : effortOf(before2.effort),
|
|
44241
|
+
effRuntime,
|
|
44242
|
+
parsed.data.model !== void 0 ? parsed.data.model : before2.model
|
|
44243
|
+
);
|
|
44244
|
+
if (ep) return reply.code(400).send(ep);
|
|
44245
|
+
}
|
|
43024
44246
|
const candidate = {
|
|
43025
44247
|
...before2,
|
|
43026
44248
|
...parsed.data,
|
|
43027
44249
|
mentions: parsed.data.mentions ?? mentionsOf(before2.mentions),
|
|
43028
44250
|
tools: parsed.data.tools !== void 0 ? parsed.data.tools : toolsOf(before2.tools),
|
|
43029
|
-
runtime: effRuntime
|
|
44251
|
+
runtime: effRuntime,
|
|
44252
|
+
activation: parsed.data.activation ?? activationOf(before2.activation),
|
|
44253
|
+
effort: parsed.data.effort !== void 0 ? parsed.data.effort : effortOf(before2.effort),
|
|
44254
|
+
workspacePolicy: effWorkspacePolicy,
|
|
44255
|
+
maxTurns: parsed.data.maxTurns !== void 0 ? parsed.data.maxTurns : maxTurnsOf(before2.maxTurns),
|
|
44256
|
+
timeoutSeconds: parsed.data.timeoutSeconds !== void 0 ? parsed.data.timeoutSeconds : timeoutSecondsOf(before2.timeoutSeconds)
|
|
43030
44257
|
};
|
|
43031
44258
|
const all = (await rowsOf()).map((r) => r.id === id ? candidate : r);
|
|
43032
44259
|
const unknown = unknownMentions(candidate, all);
|
|
@@ -43040,6 +44267,11 @@ async function custom_agents_default(app2) {
|
|
|
43040
44267
|
model: candidate.model,
|
|
43041
44268
|
mentions: candidate.mentions,
|
|
43042
44269
|
runtime: candidate.runtime,
|
|
44270
|
+
activation: candidate.activation,
|
|
44271
|
+
effort: candidate.effort,
|
|
44272
|
+
workspacePolicy: candidate.workspacePolicy,
|
|
44273
|
+
maxTurns: candidate.maxTurns,
|
|
44274
|
+
timeoutSeconds: candidate.timeoutSeconds,
|
|
43043
44275
|
enabled: candidate.enabled
|
|
43044
44276
|
} });
|
|
43045
44277
|
await loadCustomAgents();
|
|
@@ -43066,9 +44298,39 @@ async function custom_agents_default(app2) {
|
|
|
43066
44298
|
});
|
|
43067
44299
|
}
|
|
43068
44300
|
|
|
44301
|
+
// src/routes/custom-agent-metrics.ts
|
|
44302
|
+
init_zod();
|
|
44303
|
+
var zQuery = external_exports.object({
|
|
44304
|
+
projectId: external_exports.string().min(1).optional(),
|
|
44305
|
+
from: external_exports.string().datetime({ offset: true }).optional(),
|
|
44306
|
+
to: external_exports.string().datetime({ offset: true }).optional()
|
|
44307
|
+
});
|
|
44308
|
+
var zPatch = external_exports.object({
|
|
44309
|
+
disposition: external_exports.enum(["accepted", "partial", "rejected", "false-positive"]),
|
|
44310
|
+
note: external_exports.string().max(500).nullable().optional()
|
|
44311
|
+
}).strict();
|
|
44312
|
+
async function custom_agent_metrics_default(app2) {
|
|
44313
|
+
app2.get("/custom-agents/metrics", async (req, reply) => {
|
|
44314
|
+
const parsed = zQuery.safeParse(req.query);
|
|
44315
|
+
if (!parsed.success) return reply.code(400).send({ error: parsed.error.issues[0]?.message });
|
|
44316
|
+
const from = parsed.data.from ? new Date(parsed.data.from) : void 0;
|
|
44317
|
+
const to = parsed.data.to ? new Date(parsed.data.to) : void 0;
|
|
44318
|
+
if (from && to && from > to) return reply.code(400).send({ error: "from harus sebelum to" });
|
|
44319
|
+
return agentMetrics({ projectId: parsed.data.projectId, from, to });
|
|
44320
|
+
});
|
|
44321
|
+
app2.patch("/custom-agents/invocations/:id", async (req, reply) => {
|
|
44322
|
+
const body = zPatch.safeParse(req.body);
|
|
44323
|
+
if (!body.success) return reply.code(400).send({ error: body.error.issues[0]?.message });
|
|
44324
|
+
const id = String(req.params.id ?? "");
|
|
44325
|
+
const view13 = await updateAgentInvocationDisposition(id, body.data.disposition, body.data.note);
|
|
44326
|
+
return view13 ?? reply.code(404).send({ error: "invocation tidak ditemukan" });
|
|
44327
|
+
});
|
|
44328
|
+
}
|
|
44329
|
+
|
|
43069
44330
|
// src/routes/members.ts
|
|
43070
44331
|
init_src();
|
|
43071
44332
|
init_db();
|
|
44333
|
+
init_sync_notify();
|
|
43072
44334
|
var view9 = (m) => ({
|
|
43073
44335
|
id: m.id,
|
|
43074
44336
|
name: m.name,
|
|
@@ -43125,10 +44387,12 @@ async function members_default(app2) {
|
|
|
43125
44387
|
// src/routes/tasks.ts
|
|
43126
44388
|
init_src();
|
|
43127
44389
|
init_db();
|
|
44390
|
+
init_sync_notify();
|
|
43128
44391
|
|
|
43129
44392
|
// src/services/task-escalate.ts
|
|
43130
44393
|
init_src();
|
|
43131
44394
|
init_db();
|
|
44395
|
+
init_sync_notify();
|
|
43132
44396
|
var day = (d) => d ? d.toISOString().slice(0, 10) : null;
|
|
43133
44397
|
function contextOf(task, member, backlink) {
|
|
43134
44398
|
const lines2 = [
|
|
@@ -43409,7 +44673,7 @@ function issuesFromRest(raw) {
|
|
|
43409
44673
|
}
|
|
43410
44674
|
var GH_FIELDS = "number,title,body,author,labels,url,state,createdAt,updatedAt";
|
|
43411
44675
|
var API2 = "https://api.github.com";
|
|
43412
|
-
var defaultRunGh = (args, env) => new Promise((
|
|
44676
|
+
var defaultRunGh = (args, env) => new Promise((resolve22, reject2) => {
|
|
43413
44677
|
execFile16(
|
|
43414
44678
|
args[0],
|
|
43415
44679
|
args.slice(1),
|
|
@@ -43417,7 +44681,7 @@ var defaultRunGh = (args, env) => new Promise((resolve21, reject2) => {
|
|
|
43417
44681
|
(err, stdout, stderr) => {
|
|
43418
44682
|
const e = err;
|
|
43419
44683
|
if (e && (e.code === "ENOENT" || e.code === "EACCES")) return reject2(e);
|
|
43420
|
-
|
|
44684
|
+
resolve22({ code: err ? Number(err.code ?? 1) : 0, stdout, stderr });
|
|
43421
44685
|
}
|
|
43422
44686
|
);
|
|
43423
44687
|
});
|
|
@@ -43512,6 +44776,7 @@ async function fetchIssues(repo, opts, deps = {}) {
|
|
|
43512
44776
|
}
|
|
43513
44777
|
|
|
43514
44778
|
// src/services/github-issues.ts
|
|
44779
|
+
init_sync_notify();
|
|
43515
44780
|
var issueRowId = (projectId, slug, number) => `${projectId}:${slug}#${number}`;
|
|
43516
44781
|
async function pullIssues(projectId, opts = {}, deps = {}) {
|
|
43517
44782
|
const resolved = await resolveGithubRepo(projectId);
|
|
@@ -43560,6 +44825,7 @@ async function pullIssues(projectId, opts = {}, deps = {}) {
|
|
|
43560
44825
|
// src/services/github-accept.ts
|
|
43561
44826
|
init_src();
|
|
43562
44827
|
init_db();
|
|
44828
|
+
init_sync_notify();
|
|
43563
44829
|
var backlinkOf = (i) => `Dari GitHub issue ${i.repoSlug}#${i.number} (${i.url}).`;
|
|
43564
44830
|
async function acceptGithubIssue(issue2, opts) {
|
|
43565
44831
|
if (issue2.specId) {
|
|
@@ -43616,6 +44882,7 @@ ${backlink}`;
|
|
|
43616
44882
|
}
|
|
43617
44883
|
|
|
43618
44884
|
// src/routes/github-issues.ts
|
|
44885
|
+
init_sync_notify();
|
|
43619
44886
|
var zPull = external_exports.object({
|
|
43620
44887
|
state: external_exports.enum(["open", "all"]).optional(),
|
|
43621
44888
|
limit: external_exports.number().int().min(1).max(1e3).optional()
|
|
@@ -44452,10 +45719,10 @@ function sanitizeClientText(text) {
|
|
|
44452
45719
|
return text.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, "").slice(0, MAX_PESAN);
|
|
44453
45720
|
}
|
|
44454
45721
|
function wrapClientMessage(text, nonce) {
|
|
44455
|
-
const
|
|
45722
|
+
const open5 = `<pesan-klien-${nonce}>`;
|
|
44456
45723
|
const close = `</pesan-klien-${nonce}>`;
|
|
44457
45724
|
const jinak = sanitizeClientText(text).replaceAll("</pesan-klien", "<\u200B/pesan-klien").replaceAll("<pesan-klien", "<\u200Bpesan-klien");
|
|
44458
|
-
return `${
|
|
45725
|
+
return `${open5}
|
|
44459
45726
|
${jinak}
|
|
44460
45727
|
${close}`;
|
|
44461
45728
|
}
|
|
@@ -44554,9 +45821,9 @@ ${baru}`;
|
|
|
44554
45821
|
// src/services/portal-chat/workspace.ts
|
|
44555
45822
|
init_src();
|
|
44556
45823
|
init_db();
|
|
44557
|
-
import { mkdirSync as mkdirSync12, mkdtempSync as mkdtempSync2, rmSync as
|
|
44558
|
-
import { tmpdir as
|
|
44559
|
-
import { join as
|
|
45824
|
+
import { mkdirSync as mkdirSync12, mkdtempSync as mkdtempSync2, rmSync as rmSync8, writeFileSync as writeFileSync9 } from "node:fs";
|
|
45825
|
+
import { tmpdir as tmpdir6 } from "node:os";
|
|
45826
|
+
import { join as join30 } from "node:path";
|
|
44560
45827
|
var STAGE_LABEL = {
|
|
44561
45828
|
brainstorming: "Dirumuskan",
|
|
44562
45829
|
objective: "Dirumuskan",
|
|
@@ -44611,7 +45878,7 @@ ${c.body}
|
|
|
44611
45878
|
${baris.join("\n")}`;
|
|
44612
45879
|
}
|
|
44613
45880
|
async function buildChatWorkspace(projectId) {
|
|
44614
|
-
const dir2 = mkdtempSync2(
|
|
45881
|
+
const dir2 = mkdtempSync2(join30(tmpdir6(), "hanoman-portal-chat-"));
|
|
44615
45882
|
try {
|
|
44616
45883
|
const project = await prisma.project.findUnique({
|
|
44617
45884
|
where: { id: projectId },
|
|
@@ -44632,7 +45899,7 @@ async function buildChatWorkspace(projectId) {
|
|
|
44632
45899
|
});
|
|
44633
45900
|
const files = [];
|
|
44634
45901
|
const tulis = (rel, isi) => {
|
|
44635
|
-
|
|
45902
|
+
writeFileSync9(join30(dir2, rel), isi, { mode: 384 });
|
|
44636
45903
|
files.push(rel);
|
|
44637
45904
|
};
|
|
44638
45905
|
tulis("project.md", renderProjectDoc(project));
|
|
@@ -44641,16 +45908,16 @@ async function buildChatWorkspace(projectId) {
|
|
|
44641
45908
|
tulis("catatan-rilis.md", renderChangelogDoc(changelogs));
|
|
44642
45909
|
const prds = await listPrds(projectId);
|
|
44643
45910
|
if (prds.length) {
|
|
44644
|
-
mkdirSync12(
|
|
45911
|
+
mkdirSync12(join30(dir2, "dokumen"), { mode: 448 });
|
|
44645
45912
|
for (const prd of prds) {
|
|
44646
45913
|
const isi = await readPrd(projectId, prd.path);
|
|
44647
45914
|
if (!isi) continue;
|
|
44648
|
-
tulis(
|
|
45915
|
+
tulis(join30("dokumen", `${prd.slug.replaceAll("/", "-")}.md`), isi);
|
|
44649
45916
|
}
|
|
44650
45917
|
}
|
|
44651
|
-
return { dir: dir2, files, cleanup: () =>
|
|
45918
|
+
return { dir: dir2, files, cleanup: () => rmSync8(dir2, { recursive: true, force: true }) };
|
|
44652
45919
|
} catch (error) {
|
|
44653
|
-
|
|
45920
|
+
rmSync8(dir2, { recursive: true, force: true });
|
|
44654
45921
|
throw error;
|
|
44655
45922
|
}
|
|
44656
45923
|
}
|
|
@@ -44684,7 +45951,7 @@ function portalChatArgv(o) {
|
|
|
44684
45951
|
o.prompt
|
|
44685
45952
|
];
|
|
44686
45953
|
}
|
|
44687
|
-
var
|
|
45954
|
+
var shellQuote4 = (v) => `'${v.replace(/'/g, `'"'"'`)}'`;
|
|
44688
45955
|
function portalChatProcess(o, env = process.env) {
|
|
44689
45956
|
const file = effectiveStr("HANOMAN_CLAUDE_BIN") ?? "claude";
|
|
44690
45957
|
const args = portalChatArgv(o);
|
|
@@ -44694,7 +45961,7 @@ function portalChatProcess(o, env = process.env) {
|
|
|
44694
45961
|
throw new Error("chat portal menolak jalan: sandbox sesi wajib saat hardening menyala");
|
|
44695
45962
|
return { file, args, cwd: o.workspace };
|
|
44696
45963
|
}
|
|
44697
|
-
const command = [file, ...args].map(
|
|
45964
|
+
const command = [file, ...args].map(shellQuote4).join(" ");
|
|
44698
45965
|
const sandbox = sandboxArgvFromEnv({
|
|
44699
45966
|
command,
|
|
44700
45967
|
worktree: o.workspace,
|
|
@@ -44728,7 +45995,7 @@ function chatFailureReason(err, stdout, stderr, timeoutMs) {
|
|
|
44728
45995
|
return `chat portal gagal (${bagaimana}): ${detail}`;
|
|
44729
45996
|
}
|
|
44730
45997
|
function runProcess(p3, timeoutMs) {
|
|
44731
|
-
return new Promise((
|
|
45998
|
+
return new Promise((resolve22, reject2) => {
|
|
44732
45999
|
const child = execFile17(p3.file, p3.args, {
|
|
44733
46000
|
cwd: p3.cwd,
|
|
44734
46001
|
timeout: timeoutMs,
|
|
@@ -44740,7 +46007,7 @@ function runProcess(p3, timeoutMs) {
|
|
|
44740
46007
|
stdout,
|
|
44741
46008
|
stderr,
|
|
44742
46009
|
timeoutMs
|
|
44743
|
-
))) :
|
|
46010
|
+
))) : resolve22(stdout));
|
|
44744
46011
|
child.stdin?.end();
|
|
44745
46012
|
});
|
|
44746
46013
|
}
|
|
@@ -44964,7 +46231,7 @@ init_zod();
|
|
|
44964
46231
|
init_db();
|
|
44965
46232
|
init_settings3();
|
|
44966
46233
|
var zPrd = external_exports.object({ slug: external_exports.string().regex(/^[a-z0-9]([a-z0-9-]{0,60}[a-z0-9])?$/) });
|
|
44967
|
-
var
|
|
46234
|
+
var zQuery2 = external_exports.object({ project: external_exports.string().min(1) });
|
|
44968
46235
|
var sessionRow = (s2) => ({
|
|
44969
46236
|
id: s2.id,
|
|
44970
46237
|
projectId: s2.projectId,
|
|
@@ -44992,7 +46259,7 @@ var messageRow = (m) => ({
|
|
|
44992
46259
|
});
|
|
44993
46260
|
async function portal_chat_admin_default(app2) {
|
|
44994
46261
|
app2.get("/portal-chat/sessions", async (req, reply) => {
|
|
44995
|
-
const parsed =
|
|
46262
|
+
const parsed = zQuery2.safeParse(req.query);
|
|
44996
46263
|
if (!parsed.success) return reply.code(400).send({ error: "project wajib" });
|
|
44997
46264
|
const { page, limit } = req.query;
|
|
44998
46265
|
const rows = await prisma.portalChatSession.findMany({
|
|
@@ -45036,7 +46303,7 @@ async function portal_chat_admin_default(app2) {
|
|
|
45036
46303
|
return reply.code(201).send({ path });
|
|
45037
46304
|
});
|
|
45038
46305
|
app2.get("/portal-chat/export", async (req, reply) => {
|
|
45039
|
-
const parsed =
|
|
46306
|
+
const parsed = zQuery2.safeParse(req.query);
|
|
45040
46307
|
if (!parsed.success) return reply.code(400).send({ error: "project wajib" });
|
|
45041
46308
|
const { from, to } = req.query;
|
|
45042
46309
|
const rows = await prisma.portalChatSession.findMany({
|
|
@@ -45156,15 +46423,15 @@ init_src();
|
|
|
45156
46423
|
init_db();
|
|
45157
46424
|
|
|
45158
46425
|
// src/services/bootstrap.ts
|
|
45159
|
-
import { createHash as
|
|
46426
|
+
import { createHash as createHash11, randomBytes as randomBytes10, timingSafeEqual as timingSafeEqual4 } from "node:crypto";
|
|
45160
46427
|
import { chmod, lstat as lstat3, mkdir as mkdir6, readFile as readFile5, unlink as unlink5, writeFile as writeFile5 } from "node:fs/promises";
|
|
45161
|
-
import { join as
|
|
46428
|
+
import { join as join31 } from "node:path";
|
|
45162
46429
|
var TTL_MS5 = 15 * 6e4;
|
|
45163
46430
|
var SETUP_TOKEN_FILE = "setup.token";
|
|
45164
46431
|
var BootstrapError = class extends Error {
|
|
45165
46432
|
code = "BOOTSTRAP_PROOF";
|
|
45166
46433
|
};
|
|
45167
|
-
var tokenPath = (home3) =>
|
|
46434
|
+
var tokenPath = (home3) => join31(home3, SETUP_TOKEN_FILE);
|
|
45168
46435
|
async function readStored(home3) {
|
|
45169
46436
|
try {
|
|
45170
46437
|
const info = await lstat3(tokenPath(home3));
|
|
@@ -45209,8 +46476,8 @@ ${new Date(expiresAt).toISOString()}
|
|
|
45209
46476
|
}
|
|
45210
46477
|
async function verifySetupToken(candidate, home3, now = Date.now()) {
|
|
45211
46478
|
const stored = await readStored(home3);
|
|
45212
|
-
const got =
|
|
45213
|
-
const want =
|
|
46479
|
+
const got = createHash11("sha256").update(candidate).digest();
|
|
46480
|
+
const want = createHash11("sha256").update(stored.token).digest();
|
|
45214
46481
|
if (stored.expiresAt <= now || !timingSafeEqual4(got, want)) throw new BootstrapError("invalid setup proof");
|
|
45215
46482
|
}
|
|
45216
46483
|
async function consumeSetupToken(home3) {
|
|
@@ -45489,7 +46756,10 @@ function capabilityForRoute(method, path) {
|
|
|
45489
46756
|
return rw("settings");
|
|
45490
46757
|
}
|
|
45491
46758
|
if (top === "lead") return rw("lead");
|
|
45492
|
-
if (top === "custom-agents")
|
|
46759
|
+
if (top === "custom-agents") {
|
|
46760
|
+
if (seg[1] === "metrics" || seg[1] === "invocations") return "COOKIE_ONLY";
|
|
46761
|
+
return rw("agents");
|
|
46762
|
+
}
|
|
45493
46763
|
if (top === "telegram") {
|
|
45494
46764
|
const sub = seg[1] ?? "";
|
|
45495
46765
|
if (sub === "settings" || sub === "test" || sub === "credentials") return "COOKIE_ONLY";
|
|
@@ -45776,6 +47046,7 @@ function buildApp({ requireAuth = true, agentDocFile, env = process.env } = {})
|
|
|
45776
47046
|
await api.register(methods);
|
|
45777
47047
|
await api.register(lead_default);
|
|
45778
47048
|
await api.register(custom_agents_default);
|
|
47049
|
+
await api.register(custom_agent_metrics_default);
|
|
45779
47050
|
await api.register(githubIssues);
|
|
45780
47051
|
await api.register(telegramRoutes);
|
|
45781
47052
|
await api.register(webhooks_default);
|
|
@@ -45976,6 +47247,7 @@ init_db();
|
|
|
45976
47247
|
init_src2();
|
|
45977
47248
|
init_notifications2();
|
|
45978
47249
|
init_stage_machine();
|
|
47250
|
+
init_sync_notify();
|
|
45979
47251
|
init_pty();
|
|
45980
47252
|
init_session_phases();
|
|
45981
47253
|
var FAIL_REASON = "sesi berakhir sebelum mencapai done (gagal/limit)";
|
|
@@ -46142,6 +47414,7 @@ function registerTriaseSource() {
|
|
|
46142
47414
|
}
|
|
46143
47415
|
|
|
46144
47416
|
// src/server.ts
|
|
47417
|
+
init_custom_agents2();
|
|
46145
47418
|
init_pty();
|
|
46146
47419
|
init_bootstrap();
|
|
46147
47420
|
|
|
@@ -46151,7 +47424,7 @@ init_tap();
|
|
|
46151
47424
|
// src/services/webhooks/emit.ts
|
|
46152
47425
|
init_src();
|
|
46153
47426
|
init_db();
|
|
46154
|
-
import { randomUUID as
|
|
47427
|
+
import { randomUUID as randomUUID11 } from "node:crypto";
|
|
46155
47428
|
function skipped(def2, row) {
|
|
46156
47429
|
if (!def2.skipWhen || !row) return false;
|
|
46157
47430
|
return row[def2.skipWhen.field] === def2.skipWhen.equals;
|
|
@@ -46217,7 +47490,7 @@ async function emitWebhook(i) {
|
|
|
46217
47490
|
const targets = matchingEndpoints(type, projectId);
|
|
46218
47491
|
if (!targets.length) return;
|
|
46219
47492
|
const name2 = projectId ? (await prisma.project.findUnique({ where: { id: projectId }, select: { name: true } }))?.name ?? null : null;
|
|
46220
|
-
const env = buildEnvelope(i, name2, (/* @__PURE__ */ new Date()).toISOString(), `evt_${
|
|
47493
|
+
const env = buildEnvelope(i, name2, (/* @__PURE__ */ new Date()).toISOString(), `evt_${randomUUID11().replace(/-/g, "")}`);
|
|
46221
47494
|
if (!env) return;
|
|
46222
47495
|
await enqueueEnvelope(env, targets);
|
|
46223
47496
|
} catch (e) {
|
|
@@ -46530,13 +47803,13 @@ init_src2();
|
|
|
46530
47803
|
|
|
46531
47804
|
// src/services/secure-home.ts
|
|
46532
47805
|
import { chmod as chmod2, lstat as lstat4, mkdir as mkdir7 } from "node:fs/promises";
|
|
46533
|
-
import { isAbsolute as
|
|
47806
|
+
import { isAbsolute as isAbsolute8, resolve as resolve21 } from "node:path";
|
|
46534
47807
|
var HomePermissionError = class extends Error {
|
|
46535
47808
|
code = "HOME_SYMLINK";
|
|
46536
47809
|
};
|
|
46537
47810
|
async function assertNoSymlink(path, allowMissing) {
|
|
46538
47811
|
try {
|
|
46539
|
-
const info = await lstat4(
|
|
47812
|
+
const info = await lstat4(resolve21(path));
|
|
46540
47813
|
if (info.isSymbolicLink()) throw new HomePermissionError(`symlink ditolak: ${path}`);
|
|
46541
47814
|
} catch (error) {
|
|
46542
47815
|
if (error.code === "ENOENT" && allowMissing) return;
|
|
@@ -46544,7 +47817,7 @@ async function assertNoSymlink(path, allowMissing) {
|
|
|
46544
47817
|
}
|
|
46545
47818
|
}
|
|
46546
47819
|
async function secureHanomanHome(opts) {
|
|
46547
|
-
if (!
|
|
47820
|
+
if (!isAbsolute8(opts.home)) throw new HomePermissionError("HANOMAN_HOME harus absolut");
|
|
46548
47821
|
await assertNoSymlink(opts.home, true);
|
|
46549
47822
|
await mkdir7(opts.home, { recursive: true, mode: 448 });
|
|
46550
47823
|
await assertNoSymlink(opts.home, false);
|
|
@@ -46734,10 +48007,106 @@ function startRetentionSweep() {
|
|
|
46734
48007
|
|
|
46735
48008
|
// src/server.ts
|
|
46736
48009
|
init_uploads();
|
|
48010
|
+
|
|
48011
|
+
// src/services/session-event-relay.ts
|
|
48012
|
+
init_session_event_token();
|
|
48013
|
+
init_session_event_spool();
|
|
48014
|
+
import { constants as constants3 } from "node:fs";
|
|
48015
|
+
import { mkdir as mkdir8, open as open4, readdir as readdir7, rm as rm6 } from "node:fs/promises";
|
|
48016
|
+
import { join as join32 } from "node:path";
|
|
48017
|
+
var MAX_EVENT_BYTES = 1e6;
|
|
48018
|
+
var MAX_FILES_PER_DRAIN = 1e3;
|
|
48019
|
+
var SESSION_ID_RE = /^[a-z0-9_-]+$/;
|
|
48020
|
+
async function drainSessionEventSpool(app2, root = sessionEventSpoolRoot()) {
|
|
48021
|
+
await mkdir8(root, { recursive: true, mode: 448 });
|
|
48022
|
+
let delivered = 0;
|
|
48023
|
+
let examined = 0;
|
|
48024
|
+
let readBuffer;
|
|
48025
|
+
for (const session of await readdir7(root, { withFileTypes: true })) {
|
|
48026
|
+
if (!session.isDirectory() || !SESSION_ID_RE.test(session.name)) continue;
|
|
48027
|
+
const dir2 = join32(root, session.name);
|
|
48028
|
+
let entries3;
|
|
48029
|
+
try {
|
|
48030
|
+
entries3 = await readdir7(dir2, { withFileTypes: true });
|
|
48031
|
+
} catch {
|
|
48032
|
+
continue;
|
|
48033
|
+
}
|
|
48034
|
+
for (const entry of entries3) {
|
|
48035
|
+
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
48036
|
+
if (++examined > MAX_FILES_PER_DRAIN) return delivered;
|
|
48037
|
+
const path = join32(dir2, entry.name);
|
|
48038
|
+
let payload;
|
|
48039
|
+
let handle;
|
|
48040
|
+
try {
|
|
48041
|
+
handle = await open4(path, constants3.O_RDONLY | constants3.O_NOFOLLOW);
|
|
48042
|
+
const stat4 = await handle.stat();
|
|
48043
|
+
if (!stat4.isFile() || stat4.size > MAX_EVENT_BYTES) throw new Error("payload terlalu besar");
|
|
48044
|
+
readBuffer ??= Buffer.allocUnsafe(MAX_EVENT_BYTES + 1);
|
|
48045
|
+
const { bytesRead } = await handle.read(readBuffer, 0, readBuffer.length, 0);
|
|
48046
|
+
if (bytesRead > MAX_EVENT_BYTES) throw new Error("payload terlalu besar");
|
|
48047
|
+
const parsed = JSON.parse(readBuffer.subarray(0, bytesRead).toString("utf8"));
|
|
48048
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
48049
|
+
throw new Error("payload event bukan object");
|
|
48050
|
+
}
|
|
48051
|
+
payload = parsed;
|
|
48052
|
+
} catch {
|
|
48053
|
+
await rm6(path, { force: true }).catch(() => {
|
|
48054
|
+
});
|
|
48055
|
+
continue;
|
|
48056
|
+
} finally {
|
|
48057
|
+
await handle?.close().catch(() => {
|
|
48058
|
+
});
|
|
48059
|
+
}
|
|
48060
|
+
try {
|
|
48061
|
+
const response = await app2.inject({
|
|
48062
|
+
method: "POST",
|
|
48063
|
+
url: "/api/session-events",
|
|
48064
|
+
headers: {
|
|
48065
|
+
authorization: `Bearer ${sessionEventToken(session.name)}`,
|
|
48066
|
+
"x-hanoman-session": session.name
|
|
48067
|
+
},
|
|
48068
|
+
payload
|
|
48069
|
+
});
|
|
48070
|
+
if (response.statusCode === 429 || response.statusCode >= 500) continue;
|
|
48071
|
+
await rm6(path, { force: true }).catch(() => {
|
|
48072
|
+
});
|
|
48073
|
+
if (response.statusCode >= 200 && response.statusCode < 300) delivered++;
|
|
48074
|
+
} catch {
|
|
48075
|
+
}
|
|
48076
|
+
}
|
|
48077
|
+
}
|
|
48078
|
+
return delivered;
|
|
48079
|
+
}
|
|
48080
|
+
function startSessionEventRelay(app2, options2 = {}) {
|
|
48081
|
+
const root = options2.root ?? sessionEventSpoolRoot();
|
|
48082
|
+
let running = false;
|
|
48083
|
+
const tick6 = async () => {
|
|
48084
|
+
if (running) return;
|
|
48085
|
+
running = true;
|
|
48086
|
+
try {
|
|
48087
|
+
await drainSessionEventSpool({ inject: async (request) => app2.inject(request) }, root);
|
|
48088
|
+
} catch (error) {
|
|
48089
|
+
console.error("session event relay gagal:", error);
|
|
48090
|
+
} finally {
|
|
48091
|
+
running = false;
|
|
48092
|
+
}
|
|
48093
|
+
};
|
|
48094
|
+
const timer9 = setInterval(() => {
|
|
48095
|
+
void tick6();
|
|
48096
|
+
}, options2.intervalMs ?? 250);
|
|
48097
|
+
timer9.unref();
|
|
48098
|
+
app2.addHook("onClose", async () => {
|
|
48099
|
+
clearInterval(timer9);
|
|
48100
|
+
});
|
|
48101
|
+
void tick6();
|
|
48102
|
+
}
|
|
48103
|
+
|
|
48104
|
+
// src/server.ts
|
|
46737
48105
|
var app = buildApp();
|
|
46738
48106
|
var port = Number(process.env.PORT ?? 8787);
|
|
46739
48107
|
var host = process.env.HOST ?? "127.0.0.1";
|
|
46740
48108
|
assertRuntimeBoundary(process.env, { uid: process.getuid?.(), host });
|
|
48109
|
+
startSessionEventRelay(app);
|
|
46741
48110
|
process.on("unhandledRejection", (err) => console.error("unhandledRejection:", err));
|
|
46742
48111
|
process.on("uncaughtException", (err) => console.error("uncaughtException:", err));
|
|
46743
48112
|
async function shutdown(sig) {
|
|
@@ -46766,9 +48135,19 @@ var bootstrapReady = secureHanomanHome({
|
|
|
46766
48135
|
const proof = await ensureSetupToken(resolveHome());
|
|
46767
48136
|
console.log(`setup admin memerlukan token di ${proof.path}; kedaluwarsa ${new Date(proof.expiresAt).toISOString()}`);
|
|
46768
48137
|
});
|
|
46769
|
-
bootstrapReady.then(
|
|
46770
|
-
|
|
48138
|
+
bootstrapReady.then(async () => {
|
|
48139
|
+
try {
|
|
48140
|
+
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config2(), config_exports));
|
|
48141
|
+
const { applyConfigOnBoot: applyConfigOnBoot2 } = await Promise.resolve().then(() => (init_config_apply(), config_apply_exports));
|
|
48142
|
+
await loadConfig2();
|
|
48143
|
+
await applyConfigOnBoot2();
|
|
48144
|
+
} catch (e) {
|
|
48145
|
+
console.error("config runtime gagal dimuat \u2014 memakai env/default:", e);
|
|
48146
|
+
}
|
|
46771
48147
|
await installCustomAgents();
|
|
48148
|
+
return app.listen({ port, host });
|
|
48149
|
+
}).then(async () => {
|
|
48150
|
+
console.log(`hanoman api ${host}:${port}`);
|
|
46772
48151
|
await installWebhooks();
|
|
46773
48152
|
installSessionHistory();
|
|
46774
48153
|
try {
|
|
@@ -46776,17 +48155,12 @@ bootstrapReady.then(() => app.listen({ port, host })).then(async () => {
|
|
|
46776
48155
|
void reconcileHistory(liveIds).then((n2) => {
|
|
46777
48156
|
if (n2) console.log(`riwayat sesi: ${n2} baris berjalan direkonsiliasi`);
|
|
46778
48157
|
}).catch((e) => console.error("rekonsiliasi riwayat sesi:", e));
|
|
48158
|
+
void reconcileAgentInvocations(liveIds).then((n2) => {
|
|
48159
|
+
if (n2) console.log(`custom agent: ${n2} invocation ditandai abandoned`);
|
|
48160
|
+
}).catch((e) => console.error("rekonsiliasi invocation custom agent:", e));
|
|
46779
48161
|
} catch (e) {
|
|
46780
48162
|
console.error("rekonsiliasi riwayat sesi dilewati \u2014 tmux tak terbaca:", e);
|
|
46781
48163
|
}
|
|
46782
|
-
try {
|
|
46783
|
-
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config2(), config_exports));
|
|
46784
|
-
const { applyConfigOnBoot: applyConfigOnBoot2 } = await Promise.resolve().then(() => (init_config_apply(), config_apply_exports));
|
|
46785
|
-
await loadConfig2();
|
|
46786
|
-
await applyConfigOnBoot2();
|
|
46787
|
-
} catch (e) {
|
|
46788
|
-
console.error("config runtime gagal dimuat \u2014 memakai env/default:", e);
|
|
46789
|
-
}
|
|
46790
48164
|
const boundPort = app.server.address().port;
|
|
46791
48165
|
await installTelegramGateway(app, { apiBase: `http://127.0.0.1:${boundPort}` });
|
|
46792
48166
|
startVpsMonitor();
|