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.
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.2.3",
3
- "sha": "7f99c250",
4
- "builtAt": "2026-08-29T10:43:44.678Z"
2
+ "version": "0.2.5",
3
+ "sha": "70297e6b",
4
+ "builtAt": "2026-09-03T08:49:25.228Z"
5
5
  }
package/dist/cli.js CHANGED
@@ -4413,6 +4413,15 @@ var init_auto_merge = __esm({
4413
4413
  });
4414
4414
 
4415
4415
  // ../shared/src/entities.ts
4416
+ function cmpVersion(a, b) {
4417
+ const pa = a.split(".").map(Number);
4418
+ const pb = b.split(".").map(Number);
4419
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
4420
+ const d = (pa[i] ?? 0) - (pb[i] ?? 0);
4421
+ if (d !== 0) return d;
4422
+ }
4423
+ return 0;
4424
+ }
4416
4425
  var zProject, zBriefPayload, zQaPayload, zGoalPayload, zSpecBlocker, zSourceChange, zManualDone, zSpec, NOTIFY_SOUNDS, zCodex, CODEX_DEFAULTS, zSourceCommon, zScheduler, SCHEDULER_DEFAULTS, zGoal, GOAL_DEFAULTS, zConflict, CONFLICT_DEFAULTS, CHANGELOG_ENGINE_DEFAULTS, zLeadEngine, zLead, LEAD_DEFAULTS, zPortalChat, PORTAL_CHAT_DEFAULTS, zSetting, zNotification, zDocFile, zDeviceTokenView;
4417
4426
  var init_entities = __esm({
4418
4427
  "../shared/src/entities.ts"() {
@@ -4666,7 +4675,10 @@ var init_entities = __esm({
4666
4675
  //
4667
4676
  // LOKAL per mesin: `setting` TIDAK ada di FIELDS sync (server/src/services/sync.ts), jadi dua
4668
4677
  // mesin dengan versi hanoman berbeda tak bisa saling menimpa definisi bolak-balik.
4669
- builtinAgents: external_exports.record(external_exports.string(), external_exports.string()).default({})
4678
+ builtinAgents: external_exports.record(external_exports.string(), external_exports.string()).default({}),
4679
+ // SPEC-950 · marker safety policy lokal. Terpisah dari fingerprint konten agar policy sekali
4680
+ // jalan tidak membuat seed memperlakukan konfigurasi operator sebagai bawaan.
4681
+ builtinAgentPolicies: external_exports.record(external_exports.string(), external_exports.string()).default({})
4670
4682
  });
4671
4683
  zNotification = external_exports.object({
4672
4684
  id: external_exports.string(),
@@ -4789,13 +4801,28 @@ var init_agent = __esm({
4789
4801
  });
4790
4802
 
4791
4803
  // ../shared/src/custom-agent.ts
4792
- var AGENT_RUNTIMES, zAgentRuntime, AGENT_NAME_RE, DEFAULT_AGENT_TOOLS, zCustomAgent, zCreateCustomAgent, zUpdateCustomAgent;
4804
+ function validateCreateWorkspacePolicy(value, ctx) {
4805
+ if (value.workspacePolicy === "isolated-worktree" && value.runtime !== "claude") {
4806
+ ctx.addIssue({
4807
+ code: external_exports.ZodIssueCode.custom,
4808
+ path: ["workspacePolicy"],
4809
+ message: "isolated-worktree hanya tersedia untuk Claude Code"
4810
+ });
4811
+ }
4812
+ }
4813
+ var AGENT_RUNTIMES, zAgentRuntime, AGENT_ACTIVATIONS, zAgentActivation, AGENT_EFFORTS, zAgentEffort, AGENT_WORKSPACE_POLICIES, zAgentWorkspacePolicy, AGENT_NAME_RE, DEFAULT_AGENT_TOOLS, zCustomAgent, zCreateCustomAgentFields, zCreateCustomAgent, zUpdateCustomAgent;
4793
4814
  var init_custom_agent = __esm({
4794
4815
  "../shared/src/custom-agent.ts"() {
4795
4816
  "use strict";
4796
4817
  init_zod();
4797
4818
  AGENT_RUNTIMES = ["claude", "codex"];
4798
4819
  zAgentRuntime = external_exports.enum(AGENT_RUNTIMES);
4820
+ AGENT_ACTIVATIONS = ["always", "smart"];
4821
+ zAgentActivation = external_exports.enum(AGENT_ACTIVATIONS);
4822
+ AGENT_EFFORTS = ["ultra", "max", "xhigh", "high", "medium", "low", "ultracode"];
4823
+ zAgentEffort = external_exports.enum(AGENT_EFFORTS);
4824
+ AGENT_WORKSPACE_POLICIES = ["inherit", "read-only", "isolated-worktree"];
4825
+ zAgentWorkspacePolicy = external_exports.enum(AGENT_WORKSPACE_POLICIES);
4799
4826
  AGENT_NAME_RE = /^[a-z][a-z0-9-]{1,39}$/;
4800
4827
  DEFAULT_AGENT_TOOLS = [
4801
4828
  "Read",
@@ -4817,11 +4844,16 @@ var init_custom_agent = __esm({
4817
4844
  model: external_exports.string().nullable(),
4818
4845
  mentions: external_exports.array(external_exports.string()).nullable(),
4819
4846
  runtime: external_exports.enum(AGENT_RUNTIMES).nullable(),
4847
+ activation: zAgentActivation.default("always"),
4848
+ effort: zAgentEffort.nullable().default(null),
4849
+ workspacePolicy: zAgentWorkspacePolicy.default("inherit"),
4850
+ maxTurns: external_exports.number().int().min(1).max(200).nullable().default(null),
4851
+ timeoutSeconds: external_exports.number().int().min(30).max(3600).nullable().default(null),
4820
4852
  enabled: external_exports.boolean(),
4821
4853
  createdAt: external_exports.string(),
4822
4854
  updatedAt: external_exports.string()
4823
4855
  });
4824
- zCreateCustomAgent = external_exports.object({
4856
+ zCreateCustomAgentFields = external_exports.object({
4825
4857
  projectId: external_exports.string().nullable().optional(),
4826
4858
  name: external_exports.string().regex(AGENT_NAME_RE),
4827
4859
  description: external_exports.string().trim().min(1).max(500),
@@ -4831,9 +4863,23 @@ var init_custom_agent = __esm({
4831
4863
  mentions: external_exports.array(external_exports.string()).nullable().optional(),
4832
4864
  // SPEC-484 · ADR-0101 · PENYARING mesin sesi, bukan pemilih proses. null/absen = ikut sesi induk.
4833
4865
  runtime: external_exports.enum(AGENT_RUNTIMES).nullable().optional(),
4866
+ activation: zAgentActivation.optional(),
4867
+ effort: zAgentEffort.nullable().optional(),
4868
+ workspacePolicy: zAgentWorkspacePolicy.optional(),
4869
+ maxTurns: external_exports.number().int().min(1).max(200).nullable().optional(),
4870
+ timeoutSeconds: external_exports.number().int().min(30).max(3600).nullable().optional(),
4834
4871
  enabled: external_exports.boolean().optional()
4835
4872
  });
4836
- zUpdateCustomAgent = zCreateCustomAgent.omit({ name: true, projectId: true }).partial();
4873
+ zCreateCustomAgent = zCreateCustomAgentFields.superRefine(validateCreateWorkspacePolicy);
4874
+ zUpdateCustomAgent = zCreateCustomAgentFields.omit({ name: true, projectId: true }).partial().superRefine((value, ctx) => {
4875
+ if (value.workspacePolicy === "isolated-worktree" && value.runtime !== void 0 && value.runtime !== "claude") {
4876
+ ctx.addIssue({
4877
+ code: external_exports.ZodIssueCode.custom,
4878
+ path: ["workspacePolicy"],
4879
+ message: "isolated-worktree hanya tersedia untuk Claude Code"
4880
+ });
4881
+ }
4882
+ });
4837
4883
  }
4838
4884
  });
4839
4885
 
@@ -4848,6 +4894,12 @@ var init_builtin_agents = __esm({
4848
4894
  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.",
4849
4895
  tools: ["Read", "Glob", "Grep"],
4850
4896
  enabledByDefault: true,
4897
+ activation: "smart",
4898
+ effort: "low",
4899
+ workspacePolicy: "read-only",
4900
+ maxTurns: null,
4901
+ timeoutSeconds: null,
4902
+ models: { claude: "haiku", codex: "gpt-5.6-terra" },
4851
4903
  instructions: [
4852
4904
  "Kamu navigator basis kode. Tugasmu MENJAWAB, bukan menyalin.",
4853
4905
  "",
@@ -4874,6 +4926,12 @@ var init_builtin_agents = __esm({
4874
4926
  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.",
4875
4927
  tools: ["Read", "Glob", "Grep", "Bash"],
4876
4928
  enabledByDefault: false,
4929
+ activation: "smart",
4930
+ effort: "high",
4931
+ workspacePolicy: "read-only",
4932
+ maxTurns: null,
4933
+ timeoutSeconds: null,
4934
+ models: { claude: "sonnet", codex: "gpt-5.6" },
4877
4935
  instructions: [
4878
4936
  "Kamu diagnostikus. Kamu TIDAK memperbaiki kode \u2014 kamu membuktikan sebabnya.",
4879
4937
  "",
@@ -4903,7 +4961,13 @@ var init_builtin_agents = __esm({
4903
4961
  name: "qa-verifier",
4904
4962
  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.",
4905
4963
  tools: ["Read", "Glob", "Grep", "Bash"],
4906
- enabledByDefault: true,
4964
+ enabledByDefault: false,
4965
+ activation: "smart",
4966
+ effort: "medium",
4967
+ workspacePolicy: "isolated-worktree",
4968
+ maxTurns: 40,
4969
+ timeoutSeconds: 900,
4970
+ models: { claude: "sonnet", codex: "gpt-5.6-terra" },
4907
4971
  instructions: [
4908
4972
  "Kamu gerbang terakhir sebelum sesuatu diumumkan hijau. Tugasmu MERAGUKAN kehijauan itu.",
4909
4973
  "",
@@ -4915,18 +4979,18 @@ var init_builtin_agents = __esm({
4915
4979
  " paralelisme antar-berkas test, sisa proses/soket/port dari run sebelumnya, variabel",
4916
4980
  " lingkungan yang bocor dari shell, dan test yang memang sudah merah SEBELUM perubahan.",
4917
4981
  " Cara memutuskannya: jalankan ulang test itu SENDIRIAN, dengan state yang bersih.",
4918
- "4. UJI RELEVANSI \u2014 langkah yang hampir tak pernah dilakukan siapa pun, dan tanpa ini 'hijau'",
4919
- " tak berarti apa-apa: siapkan pohon kerja terpisah di commit SEBELUM perubahan",
4920
- " (`git worktree add --detach <dir> <base-sha>`), pasang test barunya di sana, jalankan,",
4921
- " dan tuntut test itu MERAH. Test yang tetap hijau tanpa perubahan tidak membuktikan apa",
4922
- " pun tentang perubahan itu.",
4923
- "5. Bersihkan pohon kerja sementara (`git worktree remove`).",
4982
+ "4. UJI RELEVANSI hanya di worktree sementara dari `baseSha`, tidak pernah di worktree",
4983
+ " parent: buat worktree terpisah, pasang patch test di sana, jalankan, dan tuntut test",
4984
+ " itu MERAH. Bila `baseSha` atau patch test tidak tersedia, laporkan `belum terbukti`;",
4985
+ " jangan mencoba eksperimen kontrol di source parent.",
4986
+ "5. Bersihkan worktree sementara milikmu. Laporkan secara eksplisit bila cleanup gagal.",
4924
4987
  "",
4925
4988
  "Larangan keras:",
4926
4989
  "- JANGAN `git stash` untuk apa pun. Tumpukan stash milik REPO, bukan pohon kerja \u2014 sesi lain",
4927
4990
  " bisa mem-pop stash milikmu, dan kamu bisa mem-pop milik mereka. Isolasi memakai",
4928
4991
  " `git worktree add`, titik.",
4929
4992
  "- JANGAN mengubah test agar lulus. Bila test-nya yang salah, itu temuan, bukan pekerjaan.",
4993
+ "- JANGAN mengubah satu byte pun di worktree parent, termasuk berkas probe sementara.",
4930
4994
  "",
4931
4995
  "Gerbang bukti: setiap klaim membawa perintah DAN potongan keluarannya. Tanpa keluaran, tanpa",
4932
4996
  "klaim. 'Semua test lulus' tanpa keluaran adalah kegagalanmu, bukan laporan.",
@@ -4941,6 +5005,12 @@ var init_builtin_agents = __esm({
4941
5005
  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.",
4942
5006
  tools: ["Read", "Glob", "Grep", "Bash", "Write", "Edit"],
4943
5007
  enabledByDefault: false,
5008
+ activation: "smart",
5009
+ effort: "high",
5010
+ workspacePolicy: "isolated-worktree",
5011
+ maxTurns: null,
5012
+ timeoutSeconds: null,
5013
+ models: { claude: "sonnet", codex: "gpt-5.6" },
4944
5014
  instructions: [
4945
5015
  "Kamu penambal jalur bahagia. Cakupan yang terlihat baik bukan urusanmu \u2014 kontrak yang tak",
4946
5016
  "pernah diuji itu urusanmu.",
@@ -4973,6 +5043,12 @@ var init_builtin_agents = __esm({
4973
5043
  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.",
4974
5044
  tools: ["Read", "Glob", "Grep", "Bash"],
4975
5045
  enabledByDefault: true,
5046
+ activation: "smart",
5047
+ effort: "medium",
5048
+ workspacePolicy: "read-only",
5049
+ maxTurns: null,
5050
+ timeoutSeconds: null,
5051
+ models: { claude: "sonnet", codex: "gpt-5.6-terra" },
4976
5052
  instructions: [
4977
5053
  "Kamu pencari cermin yang hanyut. Kelas bug yang kamu buru punya satu ciri: TIDAK ADA yang",
4978
5054
  "error. Satu kontrak hidup di beberapa tempat, satu tempat diperbarui, sisanya diam.",
@@ -5005,6 +5081,12 @@ var init_builtin_agents = __esm({
5005
5081
  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.",
5006
5082
  tools: ["Read", "Glob", "Grep", "Bash"],
5007
5083
  enabledByDefault: false,
5084
+ activation: "smart",
5085
+ effort: "high",
5086
+ workspacePolicy: "read-only",
5087
+ maxTurns: null,
5088
+ timeoutSeconds: null,
5089
+ models: { claude: "sonnet", codex: "gpt-5.6-terra" },
5008
5090
  instructions: [
5009
5091
  "Kamu pengadu janji. Kamu tak menilai bagus atau tidaknya kode \u2014 kamu menilai apakah yang",
5010
5092
  "diminta benar-benar ada.",
@@ -5033,6 +5115,12 @@ var init_builtin_agents = __esm({
5033
5115
  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.",
5034
5116
  tools: ["Read", "Glob", "Grep", "Bash"],
5035
5117
  enabledByDefault: true,
5118
+ activation: "smart",
5119
+ effort: "high",
5120
+ workspacePolicy: "read-only",
5121
+ maxTurns: null,
5122
+ timeoutSeconds: null,
5123
+ models: { claude: "sonnet", codex: "gpt-5.6" },
5036
5124
  instructions: [
5037
5125
  "Kamu penelusur sumber-ke-sink. Daftar kekhawatiran umum tak mengubah apa pun; yang mengubah",
5038
5126
  "adalah satu jalur konkret dari input yang tak dipercaya sampai ke tempat ia melukai.",
@@ -5067,6 +5155,12 @@ var init_builtin_agents = __esm({
5067
5155
  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.",
5068
5156
  tools: ["Read", "Glob", "Grep", "Bash", "WebSearch", "WebFetch"],
5069
5157
  enabledByDefault: false,
5158
+ activation: "smart",
5159
+ effort: "medium",
5160
+ workspacePolicy: "read-only",
5161
+ maxTurns: null,
5162
+ timeoutSeconds: null,
5163
+ models: { claude: "haiku", codex: "gpt-5.6-terra" },
5070
5164
  instructions: [
5071
5165
  "Kamu gerbang rantai pasok. Satu dependensi masuk lewat satu baris diff dan tak pernah",
5072
5166
  "diperiksa lagi seumur hidup proyek \u2014 pemeriksaan itu terjadi sekarang atau tidak sama sekali.",
@@ -6217,6 +6311,8 @@ var init_api = __esm({
6217
6311
  leadFlowCancel: (id) => `${API}/lead/flows/${encodeURIComponent(id)}/cancel`,
6218
6312
  // SPEC-450 · ADR-0094 · katalog custom agent. `?projectId=` → himpunan EFEKTIF (global+project).
6219
6313
  customAgents: `${API}/custom-agents`,
6314
+ customAgentMetrics: `${API}/custom-agents/metrics`,
6315
+ customAgentInvocation: (id) => `${API}/custom-agents/invocations/${encodeURIComponent(id)}`,
6220
6316
  // SPEC-484 · ADR-0101 · sumber daftar tools/model/runtime untuk form (mention dari `customAgents`).
6221
6317
  customAgentCatalog: `${API}/custom-agents/catalog`,
6222
6318
  // SPEC-481 · ADR-0100 · webhook keluar (cookie-only)
@@ -10969,6 +11065,13 @@ var init_presence = __esm({
10969
11065
  }
10970
11066
  });
10971
11067
 
11068
+ // ../shared/src/pending.ts
11069
+ var init_pending = __esm({
11070
+ "../shared/src/pending.ts"() {
11071
+ "use strict";
11072
+ }
11073
+ });
11074
+
10972
11075
  // ../shared/src/index.ts
10973
11076
  var init_src = __esm({
10974
11077
  "../shared/src/index.ts"() {
@@ -11009,6 +11112,7 @@ var init_src = __esm({
11009
11112
  init_session_dialog();
11010
11113
  init_session_ask();
11011
11114
  init_presence();
11115
+ init_pending();
11012
11116
  init_team();
11013
11117
  }
11014
11118
  });
@@ -11142,17 +11246,27 @@ var init_git = __esm({
11142
11246
  });
11143
11247
 
11144
11248
  // ../runner/src/settings.ts
11145
- var EVENT_HOOK_COMMAND;
11249
+ var EVENT_SPOOL_SCRIPT, EVENT_HOOK_COMMAND;
11146
11250
  var init_settings2 = __esm({
11147
11251
  "../runner/src/settings.ts"() {
11148
11252
  "use strict";
11253
+ EVENT_SPOOL_SCRIPT = [
11254
+ 'const fs=require("node:fs"),p=require("node:path"),c=require("node:crypto")',
11255
+ 'let d=""',
11256
+ 'process.stdin.setEncoding("utf8")',
11257
+ 'process.stdin.on("data",x=>d+=x)',
11258
+ '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{}}})'
11259
+ ].join(";");
11149
11260
  EVENT_HOOK_COMMAND = [
11261
+ 'if [ -n "${HANOMAN_EVENT_DIR:-}" ]; then',
11262
+ `node -e '${EVENT_SPOOL_SCRIPT}' >/dev/null 2>&1;`,
11263
+ "else",
11150
11264
  'curl -sS -m 2 -X POST "$HANOMAN_EVENT_URL"',
11151
11265
  "-H 'content-type: application/json'",
11152
11266
  '-H "authorization: Bearer $HANOMAN_EVENT_TOKEN"',
11153
11267
  '-H "x-hanoman-session: $HANOMAN_SESSION_ID"',
11154
11268
  '${HANOMAN_EVENT_HOST:+-H "host: $HANOMAN_EVENT_HOST"}',
11155
- "--data-binary @- >/dev/null 2>&1; exit 0"
11269
+ "--data-binary @- >/dev/null 2>&1; fi; exit 0"
11156
11270
  ].join(" ");
11157
11271
  }
11158
11272
  });
@@ -11195,6 +11309,75 @@ var init_custom_agents = __esm({
11195
11309
  }
11196
11310
  });
11197
11311
 
11312
+ // ../runner/src/agent-readonly.ts
11313
+ var init_agent_readonly = __esm({
11314
+ "../runner/src/agent-readonly.ts"() {
11315
+ "use strict";
11316
+ }
11317
+ });
11318
+
11319
+ // ../runner/src/runtime-profile.ts
11320
+ function resolveHardening(env) {
11321
+ if (env.HANOMAN_HARDENING === "1") return true;
11322
+ return env.HANOMAN_SESSION_SANDBOX === "podman" || filled(env.HANOMAN_PUBLIC_ORIGINS) || filled(env.HANOMAN_TRUST_PROXY);
11323
+ }
11324
+ var filled;
11325
+ var init_runtime_profile = __esm({
11326
+ "../runner/src/runtime-profile.ts"() {
11327
+ "use strict";
11328
+ filled = (v) => !!v && v.trim() !== "";
11329
+ }
11330
+ });
11331
+
11332
+ // ../runner/src/codex-agent-config.ts
11333
+ function codexNativeAgentsSupported(version3) {
11334
+ const parsed = version3 ? /(\d+)\.(\d+)\.(\d+)/.exec(version3)?.[0] : null;
11335
+ return parsed ? cmpVersion(parsed, CODEX_NATIVE_AGENTS_MIN_CLIENT) >= 0 : false;
11336
+ }
11337
+ function codexNativeVersionProbe(env, codexBin = env.HANOMAN_CODEX_BIN ?? "codex") {
11338
+ const sandbox = env.HANOMAN_SESSION_SANDBOX ?? (resolveHardening(env) ? "required" : "off");
11339
+ if (sandbox !== "podman") return { bin: codexBin, args: ["--version"] };
11340
+ return {
11341
+ bin: env.HANOMAN_PODMAN_BIN ?? "podman",
11342
+ args: [
11343
+ "run",
11344
+ "--rm",
11345
+ "--read-only",
11346
+ "--cap-drop=ALL",
11347
+ "--userns=keep-id",
11348
+ "--network",
11349
+ "none",
11350
+ env.HANOMAN_SESSION_IMAGE ?? "hanoman-agent:latest",
11351
+ "/bin/sh",
11352
+ "-lc",
11353
+ `${shellQuote(codexBin)} --version`
11354
+ ]
11355
+ };
11356
+ }
11357
+ var CODEX_NATIVE_AGENTS_MIN_CLIENT, shellQuote;
11358
+ var init_codex_agent_config = __esm({
11359
+ "../runner/src/codex-agent-config.ts"() {
11360
+ "use strict";
11361
+ init_src();
11362
+ init_custom_agents();
11363
+ init_runtime_profile();
11364
+ CODEX_NATIVE_AGENTS_MIN_CLIENT = "0.151.0";
11365
+ shellQuote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
11366
+ }
11367
+ });
11368
+
11369
+ // ../runner/src/custom-agent-eval.ts
11370
+ var init_custom_agent_eval = __esm({
11371
+ "../runner/src/custom-agent-eval.ts"() {
11372
+ "use strict";
11373
+ init_src();
11374
+ init_codex_agent_config();
11375
+ init_codex_settings();
11376
+ init_custom_agents();
11377
+ init_settings2();
11378
+ }
11379
+ });
11380
+
11198
11381
  // ../runner/src/paths.ts
11199
11382
  import { homedir } from "node:os";
11200
11383
  import { dirname, isAbsolute, join, resolve } from "node:path";
@@ -11529,19 +11712,6 @@ var init_config_env = __esm({
11529
11712
  }
11530
11713
  });
11531
11714
 
11532
- // ../runner/src/runtime-profile.ts
11533
- function resolveHardening(env) {
11534
- if (env.HANOMAN_HARDENING === "1") return true;
11535
- return env.HANOMAN_SESSION_SANDBOX === "podman" || filled(env.HANOMAN_PUBLIC_ORIGINS) || filled(env.HANOMAN_TRUST_PROXY);
11536
- }
11537
- var filled;
11538
- var init_runtime_profile = __esm({
11539
- "../runner/src/runtime-profile.ts"() {
11540
- "use strict";
11541
- filled = (v) => !!v && v.trim() !== "";
11542
- }
11543
- });
11544
-
11545
11715
  // ../runner/src/sandbox-probe.ts
11546
11716
  import { execFileSync } from "node:child_process";
11547
11717
  import { accessSync, constants } from "node:fs";
@@ -11654,6 +11824,9 @@ var init_src2 = __esm({
11654
11824
  init_codex_settings();
11655
11825
  init_agent_cli();
11656
11826
  init_custom_agents();
11827
+ init_agent_readonly();
11828
+ init_codex_agent_config();
11829
+ init_custom_agent_eval();
11657
11830
  init_verify_scope();
11658
11831
  init_code_style();
11659
11832
  init_paths();
@@ -12039,7 +12212,8 @@ var init_start = __esm({
12039
12212
  var doctor_exports = {};
12040
12213
  __export(doctor_exports, {
12041
12214
  default: () => doctor,
12042
- doctorReport: () => doctorReport
12215
+ doctorReport: () => doctorReport,
12216
+ resolveDoctorCodexBin: () => resolveDoctorCodexBin
12043
12217
  });
12044
12218
  import { execFileSync as execFileSync4 } from "node:child_process";
12045
12219
  import { accessSync as accessSync2, constants as constants2, existsSync as existsSync4 } from "node:fs";
@@ -12051,19 +12225,22 @@ function doctorReport(p3) {
12051
12225
  { mark: p3.git ? "\u2713" : "\u2717", text: p3.git ?? "git \u2014 TAK ADA (wajib: worktree per sesi)", fatal: !p3.git },
12052
12226
  { mark: p3.tmux ? "\u2713" : "\u2717", text: p3.tmux ?? "tmux \u2014 TAK ADA (wajib: sesi agen hidup di tmux)", fatal: !p3.tmux },
12053
12227
  { mark: p3.claude ? "\u2713" : "\xB7", text: p3.claude ? `claude ${p3.claude}` : "claude \u2014 tak ada", fatal: false },
12054
- { mark: p3.codex ? "\u2713" : "\xB7", text: p3.codex ? `codex ${p3.codex}` : "codex \u2014 tak ada", fatal: false },
12228
+ {
12229
+ mark: p3.codex ? codexNativeAgentsSupported(p3.codex) ? "\u2713" : "!" : "\xB7",
12230
+ text: p3.codex ? `codex ${p3.codex} \u2014 custom agent native ${codexNativeAgentsSupported(p3.codex) ? "siap" : `butuh >= ${CODEX_NATIVE_AGENTS_MIN_CLIENT}; sesi parent tetap bisa berjalan`}` : "codex \u2014 tak ada",
12231
+ fatal: false
12232
+ },
12055
12233
  // SPEC-471 · ADR-0095 · `gh` opsional: tanpa dia tarik issue jatuh ke HTTPS + GITHUB_TOKEN.
12056
12234
  {
12057
12235
  mark: p3.gh ? "\u2713" : "\xB7",
12058
12236
  text: p3.gh ? `gh ${p3.gh}` : "gh \u2014 tak ada (tarik issue akan lewat HTTP + GITHUB_TOKEN)",
12059
12237
  fatal: false
12060
12238
  },
12061
- // SPEC-909 · ADR-0146 · NON-FATAL dan itu disengaja: tanpa `curl` hook tetap `exit 0` — ia tak
12062
- // pernah memblokir agen tapi hanoman-lead tak akan menerima SATU PUN pertanyaan sesi, dan tak
12063
- // ada error di mana pun yang akan mengatakannya. Kegagalan senyap justru yang layak dilaporkan.
12239
+ // SPEC-950 · jalur normal memakai spool file bahkan di sandbox. curl hanya fallback untuk hook
12240
+ // lama/tanpa HANOMAN_EVENT_DIR, jadi absennya tidak lagi berarti telemetry utama mati.
12064
12241
  {
12065
12242
  mark: p3.curl ? "\u2713" : "!",
12066
- text: p3.curl ? `curl ${p3.curl}` : "curl \u2014 TAK ADA: hanoman-lead tak akan menerima pertanyaan sesi (SPEC-909)",
12243
+ text: p3.curl ? `curl ${p3.curl}` : "curl \u2014 tak ada (fallback event tanpa spool tidak tersedia)",
12067
12244
  fatal: false
12068
12245
  },
12069
12246
  // Direktori turunan lahir saat dipakai, jadi izin tulis yang belum ada di situ adalah
@@ -12117,12 +12294,22 @@ function version(bin, args) {
12117
12294
  return null;
12118
12295
  }
12119
12296
  }
12297
+ async function resolveDoctorCodexBin(env, dbUrl, read = readRuntimeConfig) {
12298
+ try {
12299
+ const configured = (await read(dbUrl, "HANOMAN_CODEX_BIN"))?.trim();
12300
+ if (configured) return configured;
12301
+ } catch {
12302
+ }
12303
+ return env.HANOMAN_CODEX_BIN?.trim() || "codex";
12304
+ }
12120
12305
  async function doctor(_argv, ctx) {
12121
12306
  let layout;
12122
12307
  let db;
12308
+ let dbUrl;
12123
12309
  try {
12124
12310
  layout = resolveLayout(distDir(), existsSync4);
12125
- db = dbFilePath(resolveDbUrl(ctx.env, dirname5(layout.schema)));
12311
+ dbUrl = resolveDbUrl(ctx.env, dirname5(layout.schema));
12312
+ db = dbFilePath(dbUrl);
12126
12313
  } catch (e) {
12127
12314
  ctx.stderr(`${e.message}
12128
12315
  `);
@@ -12136,7 +12323,8 @@ async function doctor(_argv, ctx) {
12136
12323
  { label: "key SSH", path: d.sshKeys, writable: writable(d.sshKeys), fatal: false }
12137
12324
  ];
12138
12325
  const claude = version(ctx.env.HANOMAN_CLAUDE_BIN ?? "claude", ["--version"]);
12139
- const codex = version(ctx.env.HANOMAN_CODEX_BIN ?? "codex", ["--version"]);
12326
+ const codexProbe = codexNativeVersionProbe(ctx.env, await resolveDoctorCodexBin(ctx.env, dbUrl));
12327
+ const codex = version(codexProbe.bin, codexProbe.args);
12140
12328
  const method = METHODS[DEFAULT_METHOD];
12141
12329
  const methods = zAgent.options.filter((a) => (a === "codex" ? codex : claude) !== null).map((a) => {
12142
12330
  const s2 = scanAgentSkills(a, ctx.env);
@@ -12172,6 +12360,7 @@ ${notice}
12172
12360
  if (!r.ok) ctx.stderr("\nada prasyarat yang belum terpenuhi \u2014 hanoman tak akan bisa menjalankan sesi\n");
12173
12361
  return r.ok ? 0 : 1;
12174
12362
  }
12363
+ var readRuntimeConfig;
12175
12364
  var init_doctor = __esm({
12176
12365
  "src/commands/doctor.ts"() {
12177
12366
  "use strict";
@@ -12179,6 +12368,15 @@ var init_doctor = __esm({
12179
12368
  init_src();
12180
12369
  init_layout();
12181
12370
  init_start();
12371
+ readRuntimeConfig = async (dbUrl, key) => {
12372
+ const { PrismaClient } = await import("@prisma/client");
12373
+ const client = new PrismaClient({ datasourceUrl: dbUrl });
12374
+ try {
12375
+ return (await client.runtimeConfig.findUnique({ where: { key }, select: { value: true } }))?.value ?? null;
12376
+ } finally {
12377
+ await client.$disconnect();
12378
+ }
12379
+ };
12182
12380
  }
12183
12381
  });
12184
12382