hanoman 0.9.2 → 0.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.js CHANGED
@@ -4393,7 +4393,7 @@ var init_agent_engine = __esm({
4393
4393
  zAgentEngine = external_exports.object({
4394
4394
  enabled: external_exports.boolean().default(false),
4395
4395
  agent: zAgent.default("claude"),
4396
- model: external_exports.string().default("claude-opus-5"),
4396
+ model: external_exports.string().default("opus"),
4397
4397
  effort: external_exports.string().default("xhigh")
4398
4398
  });
4399
4399
  }
@@ -4832,7 +4832,7 @@ var init_runtime_defaults = __esm({
4832
4832
  "use strict";
4833
4833
  init_orchestration();
4834
4834
  init_zod();
4835
- RUNTIME_DEFAULTS_VERSION = "2026-09-17-v1";
4835
+ RUNTIME_DEFAULTS_VERSION = "2026-09-23-v1";
4836
4836
  zRuntimeDefaultState = external_exports.enum(["seeded", "user"]);
4837
4837
  zBuiltinRuntimeDefaults = external_exports.object({
4838
4838
  version: external_exports.string().default(RUNTIME_DEFAULTS_VERSION),
@@ -4847,7 +4847,7 @@ var init_runtime_defaults = __esm({
4847
4847
  orchestration: external_exports.record(external_exports.string(), zRuntimeDefaultState).default({})
4848
4848
  });
4849
4849
  BUILTIN_CLAUDE_RUNTIME_DEFAULTS = {
4850
- model: "claude-sonnet-5",
4850
+ model: "sonnet",
4851
4851
  effort: "medium"
4852
4852
  };
4853
4853
  BUILTIN_CODEX_RUNTIME_DEFAULTS = {
@@ -4872,11 +4872,11 @@ var init_runtime_defaults = __esm({
4872
4872
  BUILTIN_ORCHESTRATION_DEFAULTS = {
4873
4873
  feature: flow(
4874
4874
  {
4875
- Brainstorm: cell("claude-sonnet-5", "medium"),
4876
- Objective: cell("claude-sonnet-5", "medium"),
4877
- Spec: cell("claude-opus-5", "medium"),
4878
- Plan: cell("claude-sonnet-5", "medium"),
4879
- Execute: cell("claude-sonnet-5", "medium")
4875
+ Brainstorm: cell("sonnet", "medium"),
4876
+ Objective: cell("sonnet", "medium"),
4877
+ Spec: cell("opus", "medium"),
4878
+ Plan: cell("sonnet", "medium"),
4879
+ Execute: cell("sonnet", "medium")
4880
4880
  },
4881
4881
  {
4882
4882
  Brainstorm: cell("gpt-5.6-terra", "medium"),
@@ -4888,10 +4888,10 @@ var init_runtime_defaults = __esm({
4888
4888
  ),
4889
4889
  qa: flow(
4890
4890
  {
4891
- Audit: cell("claude-sonnet-5", "medium"),
4892
- Spec: cell("claude-opus-5", "medium"),
4893
- Plan: cell("claude-sonnet-5", "medium"),
4894
- Execute: cell("claude-sonnet-5", "medium")
4891
+ Audit: cell("sonnet", "medium"),
4892
+ Spec: cell("opus", "medium"),
4893
+ Plan: cell("sonnet", "medium"),
4894
+ Execute: cell("sonnet", "medium")
4895
4895
  },
4896
4896
  {
4897
4897
  Audit: cell("gpt-5.6-terra", "medium"),
@@ -4902,9 +4902,9 @@ var init_runtime_defaults = __esm({
4902
4902
  ),
4903
4903
  scaffold: flow(
4904
4904
  {
4905
- Brainstorm: cell("claude-sonnet-5", "medium"),
4906
- Objective: cell("claude-sonnet-5", "medium"),
4907
- "Doc index": cell("claude-opus-5", "medium")
4905
+ Brainstorm: cell("sonnet", "medium"),
4906
+ Objective: cell("sonnet", "medium"),
4907
+ "Doc index": cell("opus", "medium")
4908
4908
  },
4909
4909
  {
4910
4910
  Brainstorm: cell("gpt-5.6-terra", "medium"),
@@ -4914,11 +4914,11 @@ var init_runtime_defaults = __esm({
4914
4914
  ),
4915
4915
  reverse: flow(
4916
4916
  {
4917
- Scan: cell("claude-sonnet-5", "medium"),
4918
- "Docs teknis": cell("claude-sonnet-5", "medium"),
4919
- Wawancara: cell("claude-sonnet-5", "medium"),
4920
- "Konvensi & index": cell("claude-sonnet-5", "medium"),
4921
- "Serah terima": cell("claude-sonnet-5", "medium")
4917
+ Scan: cell("sonnet", "medium"),
4918
+ "Docs teknis": cell("sonnet", "medium"),
4919
+ Wawancara: cell("sonnet", "medium"),
4920
+ "Konvensi & index": cell("sonnet", "medium"),
4921
+ "Serah terima": cell("sonnet", "medium")
4922
4922
  },
4923
4923
  {
4924
4924
  Scan: cell("gpt-5.6-terra", "medium"),
@@ -4930,8 +4930,8 @@ var init_runtime_defaults = __esm({
4930
4930
  ),
4931
4931
  prd: flow(
4932
4932
  {
4933
- Brainstorm: cell("claude-sonnet-5", "medium"),
4934
- PRD: cell("claude-opus-5", "medium")
4933
+ Brainstorm: cell("sonnet", "medium"),
4934
+ PRD: cell("opus", "medium")
4935
4935
  },
4936
4936
  {
4937
4937
  Brainstorm: cell("gpt-5.6-terra", "medium"),
@@ -4940,8 +4940,8 @@ var init_runtime_defaults = __esm({
4940
4940
  ),
4941
4941
  audit: flow(
4942
4942
  {
4943
- Audit: cell("claude-sonnet-5", "medium"),
4944
- Laporan: cell("claude-sonnet-5", "medium")
4943
+ Audit: cell("sonnet", "medium"),
4944
+ Laporan: cell("sonnet", "medium")
4945
4945
  },
4946
4946
  {
4947
4947
  Audit: cell("gpt-5.6-terra", "medium"),
@@ -4950,8 +4950,8 @@ var init_runtime_defaults = __esm({
4950
4950
  ),
4951
4951
  breakdown: flow(
4952
4952
  {
4953
- Analisis: cell("claude-opus-5", "medium"),
4954
- Breakdown: cell("claude-opus-5", "medium")
4953
+ Analisis: cell("opus", "medium"),
4954
+ Breakdown: cell("opus", "medium")
4955
4955
  },
4956
4956
  {
4957
4957
  Analisis: cell("gpt-5.6-sol", "medium"),
@@ -4960,8 +4960,8 @@ var init_runtime_defaults = __esm({
4960
4960
  ),
4961
4961
  goal: flow(
4962
4962
  {
4963
- Goal: cell("claude-sonnet-5", "medium"),
4964
- Verifikasi: cell("claude-sonnet-5", "medium")
4963
+ Goal: cell("sonnet", "medium"),
4964
+ Verifikasi: cell("sonnet", "medium")
4965
4965
  },
4966
4966
  {
4967
4967
  Goal: cell("gpt-5.6-terra", "medium"),
@@ -4969,7 +4969,7 @@ var init_runtime_defaults = __esm({
4969
4969
  }
4970
4970
  ),
4971
4971
  no_effort: flow(
4972
- { Kerjakan: cell("claude-haiku-4-5", "low") },
4972
+ { Kerjakan: cell("haiku", "low") },
4973
4973
  { Kerjakan: cell("gpt-5.6-terra", "low") },
4974
4974
  false
4975
4975
  )
@@ -4995,11 +4995,17 @@ function replaceModelCatalog(claude, codex2) {
4995
4995
  MODELS = claude;
4996
4996
  CODEX_MODELS = codex2;
4997
4997
  }
4998
+ function claudeModel(id2) {
4999
+ return MODELS.find((m) => m.id === id2) ?? MODELS.find((m) => m.resolved === id2);
5000
+ }
5001
+ function subagentClaudeModels() {
5002
+ return MODELS.filter((m) => m.id !== CLAUDE_DEFAULT_ALIAS);
5003
+ }
4998
5004
  function claudeEfforts(modelId) {
4999
- return MODELS.find((m) => m.id === modelId)?.efforts ?? EFFORTS;
5005
+ return claudeModel(modelId)?.efforts ?? EFFORTS;
5000
5006
  }
5001
5007
  function coerceClaudeEffort(modelId, effort2) {
5002
- const levels = MODELS.find((m) => m.id === modelId)?.efforts;
5008
+ const levels = claudeModel(modelId)?.efforts;
5003
5009
  if (!levels?.length || levels.includes(effort2)) return effort2;
5004
5010
  return levels.includes("xhigh") ? "xhigh" : levels[0];
5005
5011
  }
@@ -5023,7 +5029,7 @@ function cmpVersion(a, b) {
5023
5029
  }
5024
5030
  return 0;
5025
5031
  }
5026
- var zProject, zBriefPayload, zQaPayload, zGoalPayload, zSpecBlocker, zSourceChange, zManualDone, zSpec, zSpecListItem, zSpecSlim, NOTIFY_SOUNDS, MODELS, EFFORTS, E_5_6, E_LUNA, E_BASE, CODEX_MODELS, RETIRED_CODEX_MODELS, zCodex, CODEX_DEFAULTS, zSourceCommon, zScheduler, SCHEDULER_DEFAULTS, zGoal, GOAL_DEFAULTS, zConflict, CONFLICT_DEFAULTS, CHANGELOG_ENGINE_DEFAULTS, zLeadEngine, zLead, LEAD_DEFAULTS, zPortalChat, PORTAL_CHAT_DEFAULTS, zPhaseCell, zPhaseOverride, zPhaseOverrides, zFlowOrchestration, zOrchestration, ORCHESTRATION_DEFAULTS, zSetting, zNotification, zDocFile, zDeviceTokenView;
5032
+ var zProject, zBriefPayload, zQaPayload, zGoalPayload, zSpecBlocker, zSourceChange, zManualDone, zSpec, zSpecListItem, zSpecSlim, NOTIFY_SOUNDS, MODELS, CLAUDE_DEFAULT_ALIAS, EFFORTS, E_5_6, E_LUNA, E_BASE, CODEX_MODELS, RETIRED_CODEX_MODELS, zCodex, CODEX_DEFAULTS, zSourceCommon, zScheduler, SCHEDULER_DEFAULTS, zGoal, GOAL_DEFAULTS, zConflict, CONFLICT_DEFAULTS, CHANGELOG_ENGINE_DEFAULTS, zLeadEngine, zLead, LEAD_DEFAULTS, zPortalChat, PORTAL_CHAT_DEFAULTS, zPhaseCell, PHASE_OVERRIDE_LIMITS, zPhaseOverride, zPhaseOverrides, zFlowOrchestration, zOrchestration, ORCHESTRATION_DEFAULTS, zSetting, zNotification, zDocFile, zDeviceTokenView;
5027
5033
  var init_entities = __esm({
5028
5034
  "../shared/src/entities.ts"() {
5029
5035
  "use strict";
@@ -5151,12 +5157,13 @@ var init_entities = __esm({
5151
5157
  "fanfare"
5152
5158
  ];
5153
5159
  MODELS = [
5154
- { id: "claude-opus-5", label: "Opus 5" },
5155
- { id: "claude-sonnet-5", label: "Sonnet 5" },
5156
- { id: "claude-haiku-4-5", label: "Haiku 4.5" },
5157
- { id: "claude-fable-5", label: "Fable 5" },
5158
- { id: "claude-fable-5-1", label: "Fable 5.1", efforts: ["max", "xhigh", "high", "medium", "low"] }
5160
+ { id: "default", label: "Default (rekomendasi CLI)" },
5161
+ { id: "opus", label: "Opus" },
5162
+ { id: "sonnet", label: "Sonnet" },
5163
+ { id: "haiku", label: "Haiku" },
5164
+ { id: "fable", label: "Fable" }
5159
5165
  ];
5166
+ CLAUDE_DEFAULT_ALIAS = "default";
5160
5167
  EFFORTS = ["xhigh", "high", "medium", "low", "max", "ultracode"];
5161
5168
  E_5_6 = ["ultra", "max", "xhigh", "high", "medium", "low"];
5162
5169
  E_LUNA = ["max", "xhigh", "high", "medium", "low"];
@@ -5207,7 +5214,7 @@ var init_entities = __esm({
5207
5214
  enabled: external_exports.boolean().default(false),
5208
5215
  agent: zAgent.default("claude"),
5209
5216
  // Lenient z.string() seperti `model`/`effort` di akar: katalog ditegakkan UI, bukan server.
5210
- model: external_exports.string().default("claude-opus-5"),
5217
+ model: external_exports.string().default("opus"),
5211
5218
  effort: external_exports.string().default("xhigh")
5212
5219
  });
5213
5220
  CONFLICT_DEFAULTS = zConflict.parse({});
@@ -5257,7 +5264,7 @@ var init_entities = __esm({
5257
5264
  enabled: external_exports.boolean().default(false),
5258
5265
  brainstormPerMonth: external_exports.number().int().min(0).max(1e3).default(2),
5259
5266
  askPerMonth: external_exports.number().int().min(0).max(1e4).default(30),
5260
- model: external_exports.string().default("claude-opus-5"),
5267
+ model: external_exports.string().default("opus"),
5261
5268
  effort: external_exports.string().default("high"),
5262
5269
  timeoutSec: external_exports.number().int().min(10).max(900).default(180)
5263
5270
  });
@@ -5266,11 +5273,15 @@ var init_entities = __esm({
5266
5273
  model: external_exports.string().nullable().default(null),
5267
5274
  effort: external_exports.string().nullable().default(null)
5268
5275
  });
5276
+ PHASE_OVERRIDE_LIMITS = { model: 200, effort: 64, phase: 64, phases: 32 };
5269
5277
  zPhaseOverride = external_exports.object({
5270
- model: external_exports.string().optional(),
5271
- effort: external_exports.string().optional()
5278
+ model: external_exports.string().min(1).max(PHASE_OVERRIDE_LIMITS.model).optional(),
5279
+ effort: external_exports.string().min(1).max(PHASE_OVERRIDE_LIMITS.effort).optional()
5272
5280
  });
5273
- zPhaseOverrides = external_exports.record(external_exports.string(), zPhaseOverride);
5281
+ zPhaseOverrides = external_exports.record(external_exports.string().min(1).max(PHASE_OVERRIDE_LIMITS.phase), zPhaseOverride).refine(
5282
+ (o) => Object.keys(o).length <= PHASE_OVERRIDE_LIMITS.phases,
5283
+ { message: `phaseOverrides maksimal ${PHASE_OVERRIDE_LIMITS.phases} fase` }
5284
+ );
5274
5285
  zFlowOrchestration = external_exports.object({
5275
5286
  enabled: external_exports.boolean().default(true),
5276
5287
  claude: external_exports.record(external_exports.string(), zPhaseCell).default({}),
@@ -5289,7 +5300,7 @@ var init_entities = __esm({
5289
5300
  });
5290
5301
  ORCHESTRATION_DEFAULTS = zOrchestration.parse(BUILTIN_ORCHESTRATION_DEFAULTS);
5291
5302
  zSetting = external_exports.object({
5292
- model: external_exports.string().default("claude-sonnet-5"),
5303
+ model: external_exports.string().default("sonnet"),
5293
5304
  effort: external_exports.string().default("medium"),
5294
5305
  autoDefault: external_exports.boolean(),
5295
5306
  autoScaffold: external_exports.boolean(),
@@ -5394,7 +5405,7 @@ function codexNativeAgentsSupported(version) {
5394
5405
  return parsed ? cmpVersion(parsed, CODEX_NATIVE_AGENTS_MIN_CLIENT) >= 0 : false;
5395
5406
  }
5396
5407
  function resolvePhasePlan(input) {
5397
- const cfg = input.orchestration?.[input.flow];
5408
+ const cfg = (input.orchestration ?? ORCHESTRATION_DEFAULTS)[input.flow];
5398
5409
  if (!input.nativeAgents || cfg?.enabled === false) return null;
5399
5410
  const cells = cfg?.[input.runtime] ?? {};
5400
5411
  const coerce3 = input.runtime === "codex" ? coerceCodexEffort : coerceClaudeEffort;
@@ -5404,7 +5415,8 @@ function resolvePhasePlan(input) {
5404
5415
  phases: FLOW_PHASES[input.flow].map((phase) => {
5405
5416
  const cell2 = cells[phase];
5406
5417
  const override = input.phaseOverrides?.[phase];
5407
- const model = override?.model ?? cell2?.model ?? input.orchestrator.model;
5418
+ const picked = override?.model ?? cell2?.model ?? input.orchestrator.model;
5419
+ const model = input.runtime === "claude" && picked === CLAUDE_DEFAULT_ALIAS ? CLAUDE_SUBAGENT_INHERIT : picked;
5408
5420
  return {
5409
5421
  phase,
5410
5422
  agentName: phaseAgentName(phase),
@@ -5414,12 +5426,13 @@ function resolvePhasePlan(input) {
5414
5426
  })
5415
5427
  };
5416
5428
  }
5417
- var CODEX_NATIVE_AGENTS_MIN_CLIENT;
5429
+ var CLAUDE_SUBAGENT_INHERIT, CODEX_NATIVE_AGENTS_MIN_CLIENT;
5418
5430
  var init_orchestration_plan = __esm({
5419
5431
  "../shared/src/orchestration-plan.ts"() {
5420
5432
  "use strict";
5421
5433
  init_entities();
5422
5434
  init_orchestration();
5435
+ CLAUDE_SUBAGENT_INHERIT = "inherit";
5423
5436
  CODEX_NATIVE_AGENTS_MIN_CLIENT = "0.151.0";
5424
5437
  }
5425
5438
  });
@@ -6318,14 +6331,18 @@ var init_builtin_agents = __esm({
6318
6331
 
6319
6332
  // ../shared/src/agent-catalog.ts
6320
6333
  function modelsForRuntime(rt) {
6321
- const claude = MODELS.map((m) => ({ id: m.id, label: m.label, runtime: "claude" }));
6334
+ const claude = subagentClaudeModels().map((m) => ({ id: m.id, label: m.label, runtime: "claude" }));
6322
6335
  const codex2 = CODEX_MODELS.map((m) => ({ id: m.id, label: m.label, runtime: "codex" }));
6323
6336
  if (rt === "claude") return claude;
6324
6337
  if (rt === "codex") return codex2;
6325
6338
  return [...claude, ...codex2];
6326
6339
  }
6340
+ function modelKnownForRuntime(rt, model) {
6341
+ if (modelsForRuntime(rt).some((m) => m.id === model)) return true;
6342
+ return rt !== "codex" && model !== "default" && claudeModel(model)?.resolved === model;
6343
+ }
6327
6344
  function effortsForRuntimeModel(runtime, model) {
6328
- if (runtime === "claude" || !runtime && model && MODELS.some((entry) => entry.id === model)) {
6345
+ if (runtime === "claude" || !runtime && model && claudeModel(model)) {
6329
6346
  return [...claudeEfforts(model ?? "")];
6330
6347
  }
6331
6348
  const codexModel2 = model ? CODEX_MODELS.find((entry) => entry.id === model) : void 0;
@@ -10465,7 +10482,7 @@ var init_settings = __esm({
10465
10482
  {
10466
10483
  name: "hanoman_settings_set",
10467
10484
  title: "Simpan setelan instance",
10468
- description: "Menyimpan setelan instance. MENGGANTI blok penuh \u2014 kirim hasil hanoman_settings_get yang sudah diubah, bukan potongan. Setelan ini berlaku untuk SETIAP sesi baru di seluruh workspace. Menyentuh blok Telegram akan memuat ulang gateway.",
10485
+ description: "Menyimpan setelan instance. MENGGANTI blok penuh \u2014 kirim hasil hanoman_settings_get yang sudah diubah, bukan potongan. Panggil hanoman_settings_get TEPAT sebelum tool ini (jangan memakai hasil lama) dan ubah HANYA field yang dimaksud: setiap model/effort/sel orkestrasi yang berbeda dari nilai tersimpan ditandai milik operator dan tak lagi diperbarui default bawaan saat update. Setelan ini berlaku untuk SETIAP sesi baru di seluruh workspace. Menyentuh blok Telegram akan memuat ulang gateway.",
10469
10486
  inputSchema: obj({ properties: { settings: WHOLE_BLOCK("setelan") }, required: ["settings"] }),
10470
10487
  mode: "write",
10471
10488
  capability: "settings:write",
@@ -13048,11 +13065,14 @@ function phaseSkillsFor(flow2, phase, method) {
13048
13065
  }
13049
13066
  function orchestratorClause(plan, o = {}) {
13050
13067
  const codex2 = plan.runtime === "codex";
13051
- const call = codex2 ? "spawn_agent" : "tool Agent";
13052
- const resume = codex2 ? "send_input ke agent id yang sama" : "SendMessage ke agent ID yang kamu terima";
13053
- const describeRule = codex2 ? "Deskripsi/label pemanggilan diisi persis `Fase <Nama Fase>` \u2014 tanpa nomor urut, dan BUKAN baris pertama blok serah-terima di atas." : "Deskripsi pemanggilan (parameter `description`) diisi persis `Fase <Nama Fase>` \u2014 tanpa nomor urut, dan BUKAN baris pertama blok serah-terima di atas.";
13068
+ const call = codex2 ? "spawn_agent" : `tool Agent dengan parameter \`subagent_type\` berisi persis nama agen fase di daftar (mis. \`${plan.phases[0]?.agentName ?? "hanoman-fase-<fase>"}\`)`;
13069
+ const resume = codex2 ? "send_input ke agent id yang sama" : "SendMessage ke agent ID yang kamu catat";
13070
+ const describeRule = codex2 ? "Deskripsi/label pemanggilan diisi persis `Fase <Nama Fase>` \u2014 tanpa nomor urut, dan BUKAN baris pertama blok serah-terima di atas." : "Deskripsi pemanggilan (parameter `description`) diisi persis `Fase <Nama Fase>` \u2014 tanpa nomor urut, dan BUKAN baris pertama blok serah-terima di atas.\nJANGAN pernah memanggil tool Agent tanpa `subagent_type` atau dengan `general-purpose`: fase itu jatuh ke agen umum ber-model orchestrator dan model/effort fasenya hilang. Sebelum memanggil, periksa ulang bahwa `subagent_type` = nama agen fase itu; bila tool menolaknya, itu galat langkah 3 \u2014 bukan alasan beralih ke agen umum. Catat agent ID yang dikembalikan hasil pemanggilan: itulah satu-satunya pegangan untuk melanjutkan agen yang SAMA. Melanjutkan agen itu (relay jawaban langkah 4) HANYA lewat SendMessage \u2014 tool Agent tak punya parameter `to`, dan memanggilnya lagi melahirkan agen BARU tanpa konteks (duplikat).";
13071
+ const retry = codex2 ? "" : " (pemanggilan tool Agent BARU dengan `subagent_type` yang sama)";
13054
13072
  const askEscalation = codex2 ? "tanyakan di terminal ini lalu tunggu jawaban" : "tanyakan lewat AskUserQuestion";
13055
- const askDecisions = codex2 ? "di terminal ini sekaligus dalam satu pesan, tiap pertanyaan bernomor dan diakhiri `?` beserta opsinya, lalu tunggu jawaban" : "lewat AskUserQuestion \u2014 tool itu memuat paling banyak 4 pertanyaan per panggilan, jadi pecah sisanya ke panggilan berikutnya sampai semuanya terjawab (di fase yang memang bergiliran dengan manusia \u2014 Wawancara, Brainstorm prd/scaffold \u2014 tanyakan di terminal ini)";
13073
+ const askDecisions = codex2 ? "di terminal ini sekaligus dalam satu pesan, tiap pertanyaan bernomor dan diakhiri `?` beserta opsinya, lalu tunggu jawaban" : "lewat AskUserQuestion \u2014 tool itu memuat paling banyak 4 pertanyaan per panggilan, jadi pecah sisanya ke panggilan berikutnya sampai semuanya terjawab";
13074
+ const turnTaking = plan.flow === "reverse" || plan.flow === "scaffold" || plan.flow === "prd" ? " Fase yang bergiliran dengan manusia (Wawancara, Brainstorm prd/scaffold) melapor satu topik per putaran \u2014 relay tiap putaran dengan cara yang sama, bawa konteks & opsi dari laporannya, dan biarkan jawaban bebas." : "";
13075
+ const handoff = plan.phases.some((p3) => p3.phase === "Serah terima") ? "Sesudah fase Serah terima tercatat, tampilkan bagian `Ringkasan serah terima:` dari laporannya APA ADANYA ke manusia di terminal ini (teks biasa \u2014 informasi, bukan pertanyaan) sebelum menutup pekerjaan." : "";
13056
13076
  const list2 = plan.phases.map((p3, i) => `${i + 1}. ${p3.phase} \u2192 \`${p3.agentName}\` \xB7 ${p3.model} \xB7 ${p3.effort}`).join("\n");
13057
13077
  return [
13058
13078
  "Sesi ini ORCHESTRATOR. Setiap fase dikerjakan subagent fase miliknya dengan model & effort yang sudah terkunci di definisinya \u2014 kamu TIDAK mengerjakan isi fase sendiri.",
@@ -13070,9 +13090,10 @@ Percobaan: <k>/2
13070
13090
 
13071
13091
  ${describeRule}`,
13072
13092
  '2. Baca laporannya. `Status: selesai` DENGAN bukti \u2192 SEBELUM hal lain (memanggil fase berikutnya, commit, atau push): jalankan persis `echo "<Nama Fase> done" >> "$HANOMAN_PHASE_FILE"`, lalu verifikasi dengan `tail -1 "$HANOMAN_PHASE_FILE"` bahwa barisnya benar tertulis. Kamu satu-satunya penulis berkas itu.',
13073
- `3. \`Status: sebagian\`/\`terhalang\`, galat, atau laporan tanpa bukti \u2192 delegasikan ULANG SEKALI ke subagent fase yang sama dengan laporan gagalnya disertakan (\`Percobaan: 2/2\`). Gagal lagi \u2192 BERHENTI dan ${askEscalation} apa yang harus dilakukan. Aturan ini berlaku walau klausa otonomi di prompt ini menyuruhmu tak bertanya.`,
13074
- `4. \`Status: menunggu-keputusan\` atau \`Keputusan terbuka:\` yang bukan \`-\` \u2192 SEBELUM menulis marker fase atau memanggil fase berikutnya, ajukan SEMUA pertanyaannya ${askDecisions}, lalu LANJUTKAN subagent yang SAMA lewat ${resume} dengan seluruh jawabannya. Ulangi selama subagent masih melaporkan keputusan terbuka \u2014 tak ada batas jumlah pertanyaan maupun putaran, dan giliran relay ini bukan percobaan ulang. JANGAN pernah menjawab sendiri, termasuk di sesi scheduler: yang menjawab ${DECIDER} \u2014 hanoman yang merutekannya, kamu cukup bertanya.`,
13093
+ `3. \`Status: sebagian\`/\`terhalang\`, galat, atau laporan tanpa bukti \u2192 delegasikan ULANG SEKALI ke subagent fase yang sama${retry} dengan laporan gagalnya disertakan (\`Percobaan: 2/2\`). Gagal lagi \u2192 BERHENTI dan ${askEscalation} apa yang harus dilakukan. Aturan ini berlaku walau klausa otonomi di prompt ini menyuruhmu tak bertanya.`,
13094
+ `4. \`Status: menunggu-keputusan\` atau \`Keputusan terbuka:\` yang bukan \`-\` \u2192 SEBELUM menulis marker fase atau memanggil fase berikutnya, ajukan SEMUA pertanyaannya ${askDecisions}, lalu LANJUTKAN subagent yang SAMA lewat ${resume} dengan seluruh jawabannya. Ulangi selama subagent masih melaporkan keputusan terbuka \u2014 tak ada batas jumlah pertanyaan maupun putaran, dan giliran relay ini bukan percobaan ulang. JANGAN pernah menjawab sendiri, termasuk di sesi scheduler: yang menjawab ${DECIDER} \u2014 hanoman yang merutekannya, kamu cukup bertanya.${turnTaking}`,
13075
13095
  o.fastPath ? '5. `Rekomendasi fase: jalur-cepat` sesudah Audit \u2192 SEBELUM lanjut ke Execute: jalankan `echo "Spec skipped" >> "$HANOMAN_PHASE_FILE"` lalu `echo "Plan skipped" >> "$HANOMAN_PHASE_FILE"` (gerbang yang sama seperti langkah 2), lalu lanjut ke Execute. `penuh` \u2192 Spec \u2192 Plan \u2192 Execute.' : "",
13096
+ handoff,
13076
13097
  "DILARANG mengerjakan isi fase sendiri \u2014 termasuk saat subagent gagal. Menulis `skipped` untuk fase yang dilewati bukan mengerjakannya.",
13077
13098
  'Pekerjaan ini BELUM selesai sampai SEMUA fase di daftar di atas punya baris `done` atau `skipped` di $HANOMAN_PHASE_FILE \u2014 JANGAN commit/push final atau menyatakan tuntas sebelum itu. Periksa dengan `cat "$HANOMAN_PHASE_FILE"` sebelum commit terakhir.'
13078
13099
  ].filter(Boolean).join("\n\n");
@@ -13116,6 +13137,9 @@ function continuePrompt(flow2, spec, branchTo, autonomy, verifyScope, method, at
13116
13137
  return [
13117
13138
  head,
13118
13139
  `JANGAN mengulang fase awal \u2014 spec & plan sudah ada di ${m.planDir}/**. Lanjutkan HANYA fase Execute lewat subagent fasenya.`,
13140
+ // S3 · berkas fase tak pernah dikosongkan: baris `Execute done` run lama masih ada dan akan
13141
+ // memenuhi gerbang penutup sebelum agen Execute sesi ini bekerja sama sekali.
13142
+ "$HANOMAN_PHASE_FILE masih memuat baris `Execute done` LAMA dari sesi sebelumnya \u2014 baris itu TIDAK berlaku untuk sesi ini dan tidak memenuhi gerbang penutup. Sesudah agen fase Execute SESI INI melapor `Status: selesai`, tulis baris `Execute done` BARU (verifikasi `tail -1`); commit/push final baru sah sesudah baris baru itu.",
13119
13143
  orchestratorClause(plan),
13120
13144
  autonomyClause(autonomy),
13121
13145
  attachmentClause(attachments),
@@ -13607,7 +13631,7 @@ var init_git = __esm({
13607
13631
  });
13608
13632
 
13609
13633
  // ../runner/src/settings.ts
13610
- var EVENT_SPOOL_SCRIPT, EVENT_HOOK_COMMAND, guardSettings;
13634
+ var EVENT_SPOOL_SCRIPT, EVENT_HOOK_COMMAND, BUSY_SNAPSHOT_SCRIPT, busySnapshotCommand, guardSettings;
13611
13635
  var init_settings2 = __esm({
13612
13636
  "../runner/src/settings.ts"() {
13613
13637
  "use strict";
@@ -13629,6 +13653,11 @@ var init_settings2 = __esm({
13629
13653
  '${HANOMAN_EVENT_HOST:+-H "host: $HANOMAN_EVENT_HOST"}',
13630
13654
  "--data-binary @- >/dev/null 2>&1; fi; exit 0"
13631
13655
  ].join(" ");
13656
+ BUSY_SNAPSHOT_SCRIPT = [
13657
+ 'let d=""',
13658
+ 'process.stdin.on("data",c=>d+=c).on("end",()=>{let n=0;try{n=(JSON.parse(d).background_tasks||[]).filter(t=>t&&t.type==="subagent"&&t.status==="running").length}catch{}require("fs").writeFileSync(process.argv[1],"1\\n".repeat(n))})'
13659
+ ].join(";");
13660
+ busySnapshotCommand = (quotedSubFile) => `node -e '${BUSY_SNAPSHOT_SCRIPT}' ${quotedSubFile} >/dev/null 2>&1`;
13632
13661
  guardSettings = (decisionFile, goal, eventHook, subagentStatusLine) => {
13633
13662
  const hooks2 = {};
13634
13663
  if (decisionFile) {
@@ -13649,11 +13678,7 @@ var init_settings2 = __esm({
13649
13678
  if (decisionFile) {
13650
13679
  const sub = `'${decisionFile.split("'").join("'\\''")}.sub'`;
13651
13680
  const f = `'${decisionFile.split("'").join("'\\''")}'`;
13652
- hooks2.SubagentStart.push({ hooks: [{ type: "command", command: `echo 1 >> ${sub}; exit 0` }] });
13653
- hooks2.SubagentStop.push({ hooks: [{
13654
- type: "command",
13655
- command: `[ -s ${sub} ] && { sed '$d' ${sub} > ${sub}.t; mv ${sub}.t ${sub}; }; exit 0`
13656
- }] });
13681
+ (hooks2.Stop ??= []).push({ hooks: [{ type: "command", command: `${busySnapshotCommand(sub)}; exit 0` }] });
13657
13682
  hooks2.Notification = [{ hooks: [{
13658
13683
  type: "command",
13659
13684
  command: `m=$(cat); if [ -s ${sub} ]; then echo "$m" | grep -qiE 'permission|needs.?input'; else echo "$m" | grep -qiE 'idle|permission|waiting for|needs.?input'; fi && { [ -s ${f} ] || date +%s > ${f}; } || true`
@@ -13661,7 +13686,7 @@ var init_settings2 = __esm({
13661
13686
  hooks2.UserPromptSubmit = [{ hooks: [{ type: "command", command: `: > ${f}; : > ${sub}` }] }];
13662
13687
  }
13663
13688
  }
13664
- if (goal) hooks2.Stop = [{ hooks: [{ type: "prompt", prompt: goal }] }];
13689
+ if (goal) (hooks2.Stop ??= []).push({ hooks: [{ type: "prompt", prompt: goal }] });
13665
13690
  return {
13666
13691
  hooks: hooks2,
13667
13692
  ...subagentStatusLine ? { subagentStatusLine: { type: "command", command: subagentStatusLine } } : {}
@@ -13840,6 +13865,14 @@ var init_agent_cli = __esm({
13840
13865
  });
13841
13866
 
13842
13867
  // ../runner/src/custom-agents.ts
13868
+ function phasePromptOf(def2, file) {
13869
+ if (def2.context === void 0) return def2.instructions;
13870
+ const body = file && file.context === def2.context ? `Konteks sesi ini (backlog/project/brief/PRD) ada di berkas \`${file.path}\`. Baca berkas itu UTUH dengan tool Read SEBELUM mengerjakan apa pun \u2014 isinya bagian dari instruksimu, bukan lampiran opsional. Berkas itu hanya-baca dan berada di luar worktree; jangan menyalin atau meng-commit-nya.` : def2.context;
13871
+ return `${def2.instructions}
13872
+
13873
+ ${PHASE_CONTEXT_HEADER}
13874
+ ${body}`;
13875
+ }
13843
13876
  function agentPromptOf(def2, roster, runtime = "claude") {
13844
13877
  const can = liveMentions(def2, roster);
13845
13878
  const contract = [
@@ -13880,7 +13913,7 @@ function renderAgentsJson(defs, options2 = {}) {
13880
13913
  if (d.kind === "phase") {
13881
13914
  out4[d.name] = {
13882
13915
  description: d.description,
13883
- prompt: d.instructions,
13916
+ prompt: phasePromptOf(d, options2.phaseContextFile),
13884
13917
  ...d.model ? { model: d.model } : {},
13885
13918
  ...d.effort ? { effort: d.effort } : {}
13886
13919
  };
@@ -13923,13 +13956,14 @@ function agentDelegationClause(defs, runtime = "claude") {
13923
13956
  ""
13924
13957
  ].join("\n");
13925
13958
  }
13926
- var liveMentions, policyClause, workLimitClause, handoffClause, READ_ONLY_TOOLS;
13959
+ var PHASE_CONTEXT_HEADER, liveMentions, policyClause, workLimitClause, handoffClause, READ_ONLY_TOOLS;
13927
13960
  var init_custom_agents = __esm({
13928
13961
  "../runner/src/custom-agents.ts"() {
13929
13962
  "use strict";
13930
13963
  init_src();
13931
13964
  init_code_style();
13932
13965
  init_prompt();
13966
+ PHASE_CONTEXT_HEADER = "=== KONTEKS ===";
13933
13967
  liveMentions = (def2, roster) => {
13934
13968
  if (def2.workspacePolicy === "read-only") return [];
13935
13969
  const names = new Set(roster.map((r) => r.name));
@@ -14034,11 +14068,36 @@ function codexNativeVersionProbe(env, codexBin3 = env.HANOMAN_CODEX_BIN ?? "code
14034
14068
  ]
14035
14069
  };
14036
14070
  }
14071
+ function tomlBasicString(value) {
14072
+ let out4 = '"';
14073
+ for (let i = 0; i < value.length; i += 1) {
14074
+ const code = value.charCodeAt(i);
14075
+ if (code >= 55296 && code <= 56319) {
14076
+ const next = value.charCodeAt(i + 1);
14077
+ if (next >= 56320 && next <= 57343) {
14078
+ out4 += value[i] + value[i + 1];
14079
+ i += 1;
14080
+ } else {
14081
+ out4 += "\uFFFD";
14082
+ }
14083
+ continue;
14084
+ }
14085
+ if (code >= 56320 && code <= 57343) {
14086
+ out4 += "\uFFFD";
14087
+ continue;
14088
+ }
14089
+ const short = TOML_SHORT_ESCAPES[code];
14090
+ if (short) out4 += short;
14091
+ else if (code < 32 || code === 127) out4 += `\\u${code.toString(16).toUpperCase().padStart(4, "0")}`;
14092
+ else out4 += value[i];
14093
+ }
14094
+ return `${out4}"`;
14095
+ }
14037
14096
  function renderCodexAgentToml(def2, roster, options2 = {}) {
14038
14097
  const lines2 = [
14039
14098
  `name = ${tomlString(def2.name)}`,
14040
14099
  `description = ${tomlString(def2.description)}`,
14041
- `developer_instructions = ${tomlString(def2.kind === "phase" ? def2.instructions : agentPromptOf(def2, roster, "codex") + (options2.promptSuffix ?? ""))}`,
14100
+ `developer_instructions = ${tomlString(def2.kind === "phase" ? phasePromptOf(def2) : agentPromptOf(def2, roster, "codex") + (options2.promptSuffix ?? ""))}`,
14042
14101
  ...def2.model ? [`model = ${tomlString(def2.model)}`] : [],
14043
14102
  ...def2.effort ? [`model_reasoning_effort = ${tomlString(def2.effort)}`] : [],
14044
14103
  ...def2.workspacePolicy === "read-only" ? ['sandbox_mode = "read-only"'] : []
@@ -14122,7 +14181,7 @@ function materializeCodexAgents(defs, tempDir, options2 = {}) {
14122
14181
  liveDefs
14123
14182
  };
14124
14183
  }
14125
- var shellQuote, tomlString, tomlKey, safeFilename;
14184
+ var shellQuote, TOML_SHORT_ESCAPES, tomlString, tomlKey, safeFilename;
14126
14185
  var init_codex_agent_config = __esm({
14127
14186
  "../runner/src/codex-agent-config.ts"() {
14128
14187
  "use strict";
@@ -14130,8 +14189,17 @@ var init_codex_agent_config = __esm({
14130
14189
  init_custom_agents();
14131
14190
  init_runtime_profile();
14132
14191
  shellQuote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
14133
- tomlString = (value) => JSON.stringify(value);
14134
- tomlKey = (value) => JSON.stringify(value);
14192
+ TOML_SHORT_ESCAPES = {
14193
+ 8: "\\b",
14194
+ 9: "\\t",
14195
+ 10: "\\n",
14196
+ 12: "\\f",
14197
+ 13: "\\r",
14198
+ 34: '\\"',
14199
+ 92: "\\\\"
14200
+ };
14201
+ tomlString = tomlBasicString;
14202
+ tomlKey = tomlBasicString;
14135
14203
  safeFilename = (name2) => name2.replace(/[^a-z0-9-]/gi, "-");
14136
14204
  }
14137
14205
  });
@@ -14313,6 +14381,14 @@ var init_agent_definition = __esm({
14313
14381
  });
14314
14382
 
14315
14383
  // ../runner/src/phase-agents.ts
14384
+ function resumeNoteFor(r) {
14385
+ return "=== MELANJUTKAN ===\n" + [
14386
+ "Sesi ini MELANJUTKAN pekerjaan sesi sebelumnya untuk tugas yang sama \u2014 bukan memulai dari nol.",
14387
+ r.worktreeKept ? "Worktree ini adalah worktree sesi sebelumnya apa adanya \u2014 termasuk perubahan yang belum di-commit." : "Worktree ini DIBANGUN ULANG dari tip branch sesi: commit sesi sebelumnya ada, tetapi perubahan yang belum sempat di-commit TIDAK ada.",
14388
+ r.recorded.length ? `Fase yang SUDAH tercatat di $HANOMAN_PHASE_FILE: ${r.recorded.join(" \xB7 ")}.` : "",
14389
+ "Sebelum menulis apa pun: baca `git log --oneline` dan `git status`, lalu artefak fasemu yang mungkin sudah ada (dokumen spec/plan/docs untuk tugas ini). Lanjutkan & perbarui berkas itu \u2014 JANGAN membuat dokumen baru kedua dan JANGAN menulis ulang yang sudah ada."
14390
+ ].filter(Boolean).join(" ");
14391
+ }
14316
14392
  function fromAuditOf(payload) {
14317
14393
  if (!payload || typeof payload !== "object" || Array.isArray(payload)) return void 0;
14318
14394
  const value = payload.fromAudit;
@@ -14346,11 +14422,11 @@ function backlogGuide(flow2, phase, ctx) {
14346
14422
  }
14347
14423
  }
14348
14424
  function projectGuide(flow2, phase, ctx) {
14349
- if (flow2 === "reverse") return guideLine(REVERSE_PHASE_GUIDE, phase);
14350
- if (flow2 === "scaffold") return guideLine(SCAFFOLD_PHASE_GUIDE, phase);
14425
+ if (flow2 === "reverse") return forPhaseAgent(guideLine(REVERSE_PHASE_GUIDE, phase));
14426
+ if (flow2 === "scaffold") return forPhaseAgent(guideLine(SCAFFOLD_PHASE_GUIDE, phase));
14351
14427
  const slug = ctx.prd?.slug ?? flow2;
14352
14428
  const lines2 = flow2 === "prd" ? prdPhaseLines(slug) : breakdownPhaseLines(slug, ctx.prd?.title ?? slug);
14353
- return lines2[phase] ?? "";
14429
+ return forPhaseAgent(lines2[phase] ?? "");
14354
14430
  }
14355
14431
  function phaseAgentInstructions(entry, index, plan, ctx) {
14356
14432
  const { flow: flow2, method } = ctx;
@@ -14371,8 +14447,9 @@ ${REVERSE_STANDARD}` : "",
14371
14447
  PHASE_AGENT_AUTONOMY,
14372
14448
  PHASE_AGENT_RULES,
14373
14449
  PHASE_AGENT_REPORT,
14374
- `=== KONTEKS ===
14375
- ${ctx.context}`
14450
+ // S2 · catatan resume kecil & khas sesi → tetap di instruksi; konteks bersama yang besar dirakit
14451
+ // terpisah oleh `phasePromptOf` (T2) supaya tak disalin ke tiap agen.
14452
+ ctx.resume ? resumeNoteFor(ctx.resume) : ""
14376
14453
  ].filter(Boolean).join("\n\n");
14377
14454
  }
14378
14455
  function buildPhaseAgents(plan, ctx) {
@@ -14382,13 +14459,16 @@ function buildPhaseAgents(plan, ctx) {
14382
14459
  name: entry.agentName,
14383
14460
  description: `Fase ${entry.phase} flow ${plan.flow} hanoman \u2014 hanya dipanggil orchestrator sesi ini.`,
14384
14461
  instructions: phaseAgentInstructions(entry, index, plan, ctx),
14462
+ // T2 · konteks bersama dibawa TERPISAH (lihat `phasePromptOf`): claude merujuknya lewat satu
14463
+ // berkas di temp dir sesi, codex tetap inline.
14464
+ context: ctx.context,
14385
14465
  tools: null,
14386
14466
  model: entry.model,
14387
14467
  effort: entry.effort,
14388
14468
  mentions: []
14389
14469
  }));
14390
14470
  }
14391
- var PROJECT_FLOWS, DOC_WRITING_PHASES, PHASE_AGENT_AUTONOMY, PHASE_AGENT_RULES, PHASE_AGENT_REPORT, ATTACHMENT_NOTE;
14471
+ var PROJECT_FLOWS, DOC_WRITING_PHASES, PHASE_AGENT_AUTONOMY, PHASE_AGENT_RULES, PHASE_AGENT_REPORT, ATTACHMENT_NOTE, TERMINAL_ASK, PHASE_AGENT_ASK, TERMINAL_HANDOFF, PHASE_AGENT_HANDOFF, forPhaseAgent;
14392
14472
  var init_phase_agents = __esm({
14393
14473
  "../runner/src/phase-agents.ts"() {
14394
14474
  "use strict";
@@ -14403,7 +14483,11 @@ var init_phase_agents = __esm({
14403
14483
  "- JANGAN menulis `$HANOMAN_PHASE_FILE` \u2014 orchestrator satu-satunya penulis marker fase.",
14404
14484
  "- JANGAN `git push` \u2014 push milik orchestrator. Commit artefak fasemu sendiri sebelum melapor.",
14405
14485
  `- JANGAN memanggil agen berawalan \`${PHASE_AGENT_PREFIX}\`. Custom agent lain boleh dipanggil bila relevan.`,
14406
- "- Kerjakan HANYA fase ini; fase lain dikerjakan agen fasenya sendiri."
14486
+ "- Kerjakan HANYA fase ini; fase lain dikerjakan agen fasenya sendiri.",
14487
+ // T1 · ADR-0164 amandemen 2026-09-23 · 22 laporan tanpa `Status:` di audit: agen fase mengakhiri
14488
+ // gilirannya sambil menunggu proses latarnya sendiri, dan teks "menunggu" itu dibaca orchestrator
14489
+ // sebagai laporan final. Giliranmu yang berakhir = laporanmu, jadi menunggu harus terjadi DI DALAM giliran.
14490
+ '- JANGAN mengakhiri giliran atau melapor selama masih menunggu proses/tugas latar milikmu sendiri (test/build/server yang kamu jalankan di latar, subagent yang kamu panggil): tunggu sampai selesai dan baca hasilnya, lalu tulis laporan lengkap berawalan `Status:`. Akhir giliranmu dibaca orchestrator sebagai laporan final \u2014 teks "menunggu proses latar" bukan laporan.'
14407
14491
  ].join("\n");
14408
14492
  PHASE_AGENT_REPORT = [
14409
14493
  "Kontrak laporan (wajib, di akhir):",
@@ -14415,6 +14499,11 @@ var init_phase_agents = __esm({
14415
14499
  "Klaim tanpa bukti bukan bukti. Maksimal 1200 kata."
14416
14500
  ].join("\n");
14417
14501
  ATTACHMENT_NOTE = "Bila serah-terima menyebut manifest lampiran (`INDEX.md`), baca manifest itu dan lampiran yang relevan di awal fase.";
14502
+ TERMINAL_ASK = /([Aa])jukan SATU pertanyaan per giliran ke manusia di terminal ini, tunggu jawabannya(?=[,.])/;
14503
+ PHASE_AGENT_ASK = "jukan pertanyaannya sebagai butir `Keputusan terbuka:` di laporanmu \u2014 satu topik per putaran, beserta konteks & opsinya, karena kamu tak berhadapan langsung dengan manusia \u2014 lalu tulis `Status: menunggu-keputusan` dan berhenti; jawabannya datang sebagai pesan susulan ke agen yang sama";
14504
+ TERMINAL_HANDOFF = "tulis ringkasan hasil + daftar pertanyaan yang belum terjawab ke terminal.";
14505
+ PHASE_AGENT_HANDOFF = "tulis bagian `Ringkasan serah terima:` di laporanmu berisi ringkasan hasil + daftar pertanyaan yang belum terjawab (informasi untuk manusia, BUKAN `Keputusan terbuka:`) \u2014 orchestrator yang menampilkannya ke manusia.";
14506
+ forPhaseAgent = (line) => line.replace(TERMINAL_ASK, (_m, a) => a + PHASE_AGENT_ASK).replace(TERMINAL_HANDOFF, PHASE_AGENT_HANDOFF);
14418
14507
  }
14419
14508
  });
14420
14509
 
@@ -15656,7 +15745,8 @@ function writeSubagentStatusline(dir2) {
15656
15745
  const script2 = join9(dir2, "subagent-statusline.cjs");
15657
15746
  const labels = join9(dir2, "subagent-models.json");
15658
15747
  writeFileSync6(script2, SUBAGENT_STATUSLINE_SCRIPT, { mode: 384 });
15659
- writeFileSync6(labels, JSON.stringify(Object.fromEntries(MODELS.map((m) => [m.id, m.label]))), { mode: 384 });
15748
+ const entries3 = MODELS.flatMap((m) => [[m.id, m.label], ...m.resolved ? [[m.resolved, m.label]] : []]);
15749
+ writeFileSync6(labels, JSON.stringify(Object.fromEntries(entries3)), { mode: 384 });
15660
15750
  return `node ${JSON.stringify(script2)} ${JSON.stringify(labels)}`;
15661
15751
  }
15662
15752
  var SUBAGENT_STATUSLINE_SCRIPT;
@@ -15707,6 +15797,7 @@ __export(src_exports, {
15707
15797
  GOAL_MAX: () => GOAL_MAX,
15708
15798
  GOAL_MAX_BLOCKS: () => GOAL_MAX_BLOCKS,
15709
15799
  GOAL_TUI_PASTE_LIMIT: () => GOAL_TUI_PASTE_LIMIT,
15800
+ PHASE_CONTEXT_HEADER: () => PHASE_CONTEXT_HEADER,
15710
15801
  PIPELINES: () => PIPELINES,
15711
15802
  PROJECT_METHOD: () => PROJECT_METHOD,
15712
15803
  RESUMED_WORKTREE_NOTE: () => RESUMED_WORKTREE_NOTE,
@@ -15757,6 +15848,7 @@ __export(src_exports, {
15757
15848
  orchestratorClause: () => orchestratorClause,
15758
15849
  parseConfigEnv: () => parseConfigEnv,
15759
15850
  phaseAgentInstructions: () => phaseAgentInstructions,
15851
+ phasePromptOf: () => phasePromptOf,
15760
15852
  phaseSkillsFor: () => phaseSkillsFor,
15761
15853
  prdAuditBlock: () => prdAuditBlock,
15762
15854
  prdBriefBlock: () => prdBriefBlock,
@@ -15794,6 +15886,7 @@ __export(src_exports, {
15794
15886
  startProjectPrompt: () => startProjectPrompt,
15795
15887
  startPrompt: () => startPrompt,
15796
15888
  startScaffoldPrompt: () => startScaffoldPrompt,
15889
+ tomlBasicString: () => tomlBasicString,
15797
15890
  validateAgentEvalManifest: () => validateAgentEvalManifest,
15798
15891
  verifyScopeClause: () => verifyScopeClause,
15799
15892
  writeConfigEnv: () => writeConfigEnv,
@@ -16451,9 +16544,10 @@ var init_ssrf = __esm({
16451
16544
  });
16452
16545
 
16453
16546
  // src/services/safe-outbound-request.ts
16454
- import { lookup as dnsLookup2 } from "node:dns/promises";
16547
+ import { resolve4 as resolve42, resolve6 as resolve62 } from "node:dns/promises";
16455
16548
  import { request as httpRequest } from "node:http";
16456
16549
  import { request as httpsRequest } from "node:https";
16550
+ import { isIP } from "node:net";
16457
16551
  import { createGunzip } from "node:zlib";
16458
16552
  async function pinnedRequest(input) {
16459
16553
  return new Promise((resolve24, reject2) => {
@@ -16520,10 +16614,26 @@ async function pinnedRequest(input) {
16520
16614
  req.end();
16521
16615
  });
16522
16616
  }
16617
+ function withLookupTimeout(p3, ms) {
16618
+ return new Promise((resolve24, reject2) => {
16619
+ const timer10 = setTimeout(() => reject2(Object.assign(new Error("outbound timeout"), { name: "AbortError" })), ms);
16620
+ timer10.unref?.();
16621
+ p3.then((v) => {
16622
+ clearTimeout(timer10);
16623
+ resolve24(v);
16624
+ }, (e) => {
16625
+ clearTimeout(timer10);
16626
+ reject2(e);
16627
+ });
16628
+ });
16629
+ }
16523
16630
  async function safeRequest(options2, deps = {}) {
16524
16631
  if (options2.url.protocol !== "http:" && options2.url.protocol !== "https:") throw new Error("outbound scheme ditolak");
16525
16632
  if (options2.url.username || options2.url.password) throw new Error("outbound credential URL ditolak");
16526
- const addresses = await (deps.lookupAll ?? defaultLookup2)(options2.url.hostname);
16633
+ const addresses = await withLookupTimeout(
16634
+ (deps.lookupAll ?? defaultLookup2)(options2.url.hostname),
16635
+ options2.connectMs
16636
+ );
16527
16637
  if (!addresses.length) throw new Error("DNS tak mengembalikan alamat");
16528
16638
  if (!options2.allowPrivate) {
16529
16639
  const blocked = addresses.find((row) => isBlockedAddress(row.address));
@@ -16537,7 +16647,15 @@ var init_safe_outbound_request = __esm({
16537
16647
  "src/services/safe-outbound-request.ts"() {
16538
16648
  "use strict";
16539
16649
  init_ssrf();
16540
- defaultLookup2 = async (host2) => (await dnsLookup2(host2, { all: true })).map((row) => ({ address: row.address, family: row.family }));
16650
+ defaultLookup2 = async (host2) => {
16651
+ const literal = isIP(host2);
16652
+ if (literal) return [{ address: host2, family: literal }];
16653
+ const [v4, v6] = await Promise.allSettled([resolve42(host2), resolve62(host2)]);
16654
+ const out4 = [];
16655
+ if (v4.status === "fulfilled") out4.push(...v4.value.map((address) => ({ address, family: 4 })));
16656
+ if (v6.status === "fulfilled") out4.push(...v6.value.map((address) => ({ address, family: 6 })));
16657
+ return out4;
16658
+ };
16541
16659
  }
16542
16660
  });
16543
16661
 
@@ -17055,19 +17173,45 @@ import { chmodSync as chmodSync6, mkdirSync as mkdirSync7, readFileSync as readF
17055
17173
  import { stat as stat2 } from "node:fs/promises";
17056
17174
  import { dirname as dirname8 } from "node:path";
17057
17175
  import { tmpdir as tmpdir3 } from "node:os";
17058
- function phaseView(p3, a, phases2) {
17059
- const now = Date.now();
17060
- trackDoneSeen(phases2, a.doneSeenAt, now);
17176
+ function doneSeenOf(id2) {
17177
+ const known2 = phaseDoneSeen.get(id2);
17178
+ if (known2) return known2;
17179
+ const seen2 = /* @__PURE__ */ new Map();
17180
+ try {
17181
+ const raw = tmux("show-options", "-v", "-q", "-t", name(id2), PHASE_DONE_SEEN_OPT).trim();
17182
+ const parsed = raw ? JSON.parse(raw) : {};
17183
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
17184
+ for (const [phase, at] of Object.entries(parsed))
17185
+ if (typeof at === "number" && Number.isFinite(at)) seen2.set(phase, at);
17186
+ }
17187
+ } catch {
17188
+ }
17189
+ phaseDoneSeen.set(id2, seen2);
17190
+ return seen2;
17191
+ }
17192
+ function trackPhaseDoneSeen(id2, phases2, now) {
17193
+ const seen2 = doneSeenOf(id2);
17194
+ const before2 = JSON.stringify([...seen2]);
17195
+ trackDoneSeen(phases2, seen2, now);
17196
+ if (JSON.stringify([...seen2]) !== before2) {
17197
+ void tmuxAsync("set-option", "-t", name(id2), PHASE_DONE_SEEN_OPT, JSON.stringify(Object.fromEntries(seen2))).catch(() => {
17198
+ });
17199
+ }
17200
+ return seen2;
17201
+ }
17202
+ function phaseView(p3, phases2) {
17061
17203
  const roster = (p3.agentRoster ?? []).flatMap((r) => r.phase ? [{ name: r.name, phase: r.phase, model: r.model, effort: r.effort }] : []);
17062
- return roster.length ? enrichPhases(
17204
+ if (!roster.length) return phases2;
17205
+ const now = Date.now();
17206
+ return enrichPhases(
17063
17207
  phases2,
17064
17208
  roster,
17065
17209
  phaseInvocations.get(p3.id) ?? [],
17066
- a.doneSeenAt,
17210
+ trackPhaseDoneSeen(p3.id, phases2, now),
17067
17211
  now,
17068
17212
  p3.startedAt * 1e3,
17069
17213
  new Set(p3.doneAtBirth ?? [])
17070
- ) : phases2;
17214
+ );
17071
17215
  }
17072
17216
  function noTtyPromptEnv() {
17073
17217
  const path = askpassDenyPath();
@@ -17289,6 +17433,7 @@ function createSession(projectId, cwd, opts = {}) {
17289
17433
  const existing = getSession(id2);
17290
17434
  if (existing && !existing.exited) return existing;
17291
17435
  if (existing) killSession(id2);
17436
+ phaseDoneSeen.delete(id2);
17292
17437
  const eventDir = opts.command ? void 0 : sessionEventDir(id2);
17293
17438
  if (eventDir) {
17294
17439
  mkdirSync7(eventDir, { recursive: true, mode: 448 });
@@ -17296,26 +17441,30 @@ function createSession(projectId, cwd, opts = {}) {
17296
17441
  }
17297
17442
  const agentForDefs = opts.agent ?? "claude";
17298
17443
  const sessionEffort = agentForDefs === "codex" && opts.model && opts.effort ? coerceCodexEffort(opts.model, opts.effort) : opts.effort;
17444
+ const requestedPhaseDefs = opts.command ? [] : opts.phaseAgents ?? [];
17445
+ const bornPrompt = (orch) => orch ? opts.prompt : opts.legacyPrompt ?? opts.prompt;
17299
17446
  const selectionContext = {
17300
17447
  projectId,
17301
17448
  runtime: agentForDefs,
17302
17449
  flow: opts.flow,
17303
17450
  cwd,
17304
17451
  baseSha: opts.env?.HANOMAN_BASE_SHA,
17305
- prompt: opts.prompt,
17452
+ prompt: bornPrompt(requestedPhaseDefs.length > 0),
17306
17453
  changedFiles: opts.command ? [] : collectChangedFiles(cwd, opts.env?.HANOMAN_BASE_SHA)
17307
17454
  };
17308
- const rawCustomDefs = opts.command ? [] : customAgentsFor(selectionContext);
17309
- const customDefs = rawCustomDefs.filter((def2) => !isPhaseAgentName(def2.name));
17310
- for (const def2 of rawCustomDefs) {
17311
- if (isPhaseAgentName(def2.name)) {
17312
- process.stderr.write(
17313
- `hanoman: custom agent ${def2.name} diabaikan \u2014 awalan hanoman-fase- dicadangkan (ADR-0164)
17455
+ const selectCustomDefs = (context, warn) => {
17456
+ const raw = opts.command ? [] : customAgentsFor(context);
17457
+ for (const def2 of raw) {
17458
+ if (warn && isPhaseAgentName(def2.name)) {
17459
+ process.stderr.write(
17460
+ `hanoman: custom agent ${def2.name} diabaikan \u2014 awalan hanoman-fase- dicadangkan (ADR-0164)
17314
17461
  `
17315
- );
17462
+ );
17463
+ }
17316
17464
  }
17317
- }
17318
- const requestedPhaseDefs = opts.command ? [] : opts.phaseAgents ?? [];
17465
+ return raw.filter((def2) => !isPhaseAgentName(def2.name));
17466
+ };
17467
+ let customDefs = selectCustomDefs(selectionContext, true);
17319
17468
  let rosterBlock = "";
17320
17469
  let codexAgentArgs = [];
17321
17470
  let agentsFile;
@@ -17328,14 +17477,32 @@ function createSession(projectId, cwd, opts = {}) {
17328
17477
  const tempDir = agentTempDir(id2);
17329
17478
  agentConfigDir = tempDir;
17330
17479
  mkdirSync7(tempDir, { recursive: true, mode: 448 });
17331
- const readOnlyHook = customDefs.some((def2) => def2.workspacePolicy === "read-only") ? writeReadOnlyHook(tempDir) : void 0;
17480
+ let hook;
17481
+ const readOnlyHookFor = () => customDefs.some((def2) => def2.workspacePolicy === "read-only") ? hook ??= writeReadOnlyHook(tempDir) : void 0;
17332
17482
  const attempt = (phaseDefs) => {
17333
17483
  const defs = [...phaseDefs, ...customDefs];
17334
17484
  if (defs.length === 0) return [];
17485
+ const readOnlyHook = readOnlyHookFor();
17335
17486
  if (agentForDefs === "claude") {
17336
17487
  const file = agentsFilePath(id2);
17337
17488
  try {
17338
- writeFileSync8(file, renderAgentsJson(defs, { readOnlyHookCommand: readOnlyHook?.command }), { mode: 384 });
17489
+ const shared = phaseDefs.find((d) => d.context !== void 0)?.context;
17490
+ let phaseContextFile;
17491
+ if (shared !== void 0) {
17492
+ const path = phaseContextFilePath(id2);
17493
+ writeFileSync8(path, shared, { mode: 384 });
17494
+ chmodSync6(path, 384);
17495
+ phaseContextFile = { context: shared, path };
17496
+ }
17497
+ const json = renderAgentsJson(defs, {
17498
+ readOnlyHookCommand: readOnlyHook?.command,
17499
+ ...phaseContextFile ? { phaseContextFile } : {}
17500
+ });
17501
+ const bytes = Buffer.byteLength(json);
17502
+ if (phaseDefs.length > 0 && bytes > AGENTS_ARG_SAFE_BYTES) {
17503
+ return [`argumen --agents ${bytes} B melewati ambang aman ${AGENTS_ARG_SAFE_BYTES} B (batas satu argumen exec Linux 128 KiB)`];
17504
+ }
17505
+ writeFileSync8(file, json, { mode: 384 });
17339
17506
  } catch (error) {
17340
17507
  if (phaseDefs.length === 0) throw error;
17341
17508
  return [error instanceof Error ? error.message : String(error)];
@@ -17371,11 +17538,13 @@ function createSession(projectId, cwd, opts = {}) {
17371
17538
  orchestrated = phaseFailReasons.length === 0;
17372
17539
  }
17373
17540
  if (!orchestrated) {
17374
- if (requestedPhaseDefs.length > 0)
17541
+ if (requestedPhaseDefs.length > 0) {
17375
17542
  process.stderr.write(
17376
17543
  `hanoman: agen fase sesi ${id2} gagal dimaterialisasi \u2014 sesi lahir mode tunggal: ${phaseFailReasons.join("; ")}
17377
17544
  `
17378
17545
  );
17546
+ customDefs = selectCustomDefs({ ...selectionContext, prompt: bornPrompt(false) }, false);
17547
+ }
17379
17548
  attempt([]);
17380
17549
  }
17381
17550
  if (orchestrated && agentForDefs === "claude") {
@@ -17391,7 +17560,7 @@ function createSession(projectId, cwd, opts = {}) {
17391
17560
  }
17392
17561
  let promptArg = "";
17393
17562
  let promptFile;
17394
- const sessionPrompt = orchestrated ? opts.prompt : opts.legacyPrompt ?? opts.prompt;
17563
+ const sessionPrompt = bornPrompt(orchestrated);
17395
17564
  if (!opts.command && sessionPrompt) {
17396
17565
  promptFile = promptFilePath(id2);
17397
17566
  mkdirSync7(dirname8(promptFile), { recursive: true, mode: 448 });
@@ -17561,7 +17730,8 @@ function createSession(projectId, cwd, opts = {}) {
17561
17730
  }
17562
17731
  if (opts.phaseFile) tmux("set-option", "-t", name(id2), "@hanoman_phase_file", opts.phaseFile);
17563
17732
  if (opts.phaseFile && opts.flow) {
17564
- const doneAtBirth = readPhases(opts.phaseFile, opts.flow).filter((p3) => p3.state === "done" || p3.state === "skipped").map((p3) => p3.name);
17733
+ const rerun = new Set(opts.rerunPhases ?? []);
17734
+ const doneAtBirth = readPhases(opts.phaseFile, opts.flow).filter((p3) => (p3.state === "done" || p3.state === "skipped") && !rerun.has(p3.name)).map((p3) => p3.name);
17565
17735
  if (doneAtBirth.length > 0)
17566
17736
  tmux("set-option", "-t", name(id2), "@hanoman_done_at_birth", doneAtBirth.join(","));
17567
17737
  }
@@ -17700,8 +17870,7 @@ function open(id2) {
17700
17870
  scrollback: "",
17701
17871
  clients: /* @__PURE__ */ new Set(),
17702
17872
  lastPhases: "",
17703
- pending: "",
17704
- doneSeenAt: /* @__PURE__ */ new Map()
17873
+ pending: ""
17705
17874
  };
17706
17875
  pty.onData((d) => {
17707
17876
  a.pending += d;
@@ -17731,13 +17900,14 @@ function end(id2, code) {
17731
17900
  flushOutput(a);
17732
17901
  broadcast(a, { t: "exit", code });
17733
17902
  drop(id2);
17903
+ phaseDoneSeen.delete(id2);
17734
17904
  }
17735
17905
  async function pollPhases(p3, a) {
17736
17906
  if (!p3.flow || !p3.phaseFile) return;
17737
17907
  const raw = await readPhasesAsync(p3.phaseFile, p3.flow);
17738
17908
  const complete = await sessionCompleteAsync(raw, p3.cwd, p3.specId);
17739
17909
  if (attached.get(p3.id) !== a) return;
17740
- const phases2 = phaseView(p3, a, raw);
17910
+ const phases2 = phaseView(p3, raw);
17741
17911
  const json = phaseKey(phases2, complete);
17742
17912
  if (json === a.lastPhases) return;
17743
17913
  a.lastPhases = json;
@@ -17814,7 +17984,7 @@ function attachLive(id2, c, p3) {
17814
17984
  c.send(frame({ t: "alt", on: p3.altScreen }));
17815
17985
  if (p3.flow && p3.phaseFile) {
17816
17986
  const raw = readPhases(p3.phaseFile, p3.flow);
17817
- const phases2 = phaseView(p3, a, raw);
17987
+ const phases2 = phaseView(p3, raw);
17818
17988
  const complete = sessionComplete(raw, p3.cwd, p3.specId);
17819
17989
  a.lastPhases = phaseKey(phases2, complete);
17820
17990
  c.send(frame({ t: "phase", phases: phases2, complete }));
@@ -17831,6 +18001,7 @@ function resize(id2, cols, rows) {
17831
18001
  }
17832
18002
  function killSession(id2) {
17833
18003
  phaseInvocations.delete(id2);
18004
+ phaseDoneSeen.delete(id2);
17834
18005
  const p3 = getSession(id2);
17835
18006
  if (!p3) return false;
17836
18007
  const transcript = captureTranscript(id2);
@@ -17869,7 +18040,7 @@ function spawnPty(...args) {
17869
18040
  );
17870
18041
  }
17871
18042
  }
17872
- var socket, PREFIX, MAX_SCROLLBACK, SCROLLBACK_SLACK, POLL_MS, markerFilled, markerFilledAsync, clearMarker, PANE_QUIET_MS, paneQuiet, markerOnset, decisionOnset, attached, phaseInvocations, claudeBin, shellBin, codexBin, agentBin, rootBypassEnv, frame, name, promptFilePath, goalGatePath, goalStatePath, agentTempDir, agentsFilePath, askpassDenyPath, ASKPASS_DENY, NO_SERVER, TmuxError, sq, idFor, FMT, toSessionInfo, listSessions, listSessionsAsync, decisionsOf, getSession, getSessionAsync, hooks, emitBirth, emitDeath, customAgentSource, codexNativeAgentSupport, nativeAgentsAvailable, customAgentsFor, runGitDiff, sleep, paneText, paneIO, DIALOG_CAPTURE_LINES, GOAL_ARMED_MARKERS, goalArmed, COALESCE_MS, COALESCE_MAX_BYTES, trimScrollback, phaseKey, sessionFinishedAsync, poll, polling, TERMINAL_QUERY, stripTerminalQueries, CAPTURE_ARGS, detach;
18043
+ var socket, PREFIX, MAX_SCROLLBACK, SCROLLBACK_SLACK, POLL_MS, markerFilled, markerFilledAsync, clearMarker, PANE_QUIET_MS, paneQuiet, markerOnset, decisionOnset, attached, phaseInvocations, PHASE_DONE_SEEN_OPT, phaseDoneSeen, claudeBin, shellBin, codexBin, agentBin, rootBypassEnv, frame, name, promptFilePath, goalGatePath, goalStatePath, agentTempDir, agentsFilePath, phaseContextFilePath, AGENTS_ARG_SAFE_BYTES, askpassDenyPath, ASKPASS_DENY, NO_SERVER, TmuxError, sq, idFor, FMT, toSessionInfo, listSessions, listSessionsAsync, decisionsOf, getSession, getSessionAsync, hooks, emitBirth, emitDeath, customAgentSource, codexNativeAgentSupport, nativeAgentsAvailable, customAgentsFor, runGitDiff, sleep, paneText, paneIO, DIALOG_CAPTURE_LINES, GOAL_ARMED_MARKERS, goalArmed, COALESCE_MS, COALESCE_MAX_BYTES, trimScrollback, phaseKey, sessionFinishedAsync, poll, polling, TERMINAL_QUERY, stripTerminalQueries, CAPTURE_ARGS, detach;
17873
18044
  var init_pty = __esm({
17874
18045
  "src/services/pty.ts"() {
17875
18046
  "use strict";
@@ -17929,6 +18100,8 @@ var init_pty = __esm({
17929
18100
  };
17930
18101
  attached = /* @__PURE__ */ new Map();
17931
18102
  phaseInvocations = /* @__PURE__ */ new Map();
18103
+ PHASE_DONE_SEEN_OPT = "@hanoman_phase_done_seen";
18104
+ phaseDoneSeen = /* @__PURE__ */ new Map();
17932
18105
  claudeBin = () => effectiveStr("HANOMAN_CLAUDE_BIN") ?? "claude";
17933
18106
  shellBin = () => effectiveStr("HANOMAN_SHELL") ?? process.env.SHELL ?? "/bin/bash";
17934
18107
  codexBin = () => effectiveStr("HANOMAN_CODEX_BIN") ?? "codex";
@@ -17941,6 +18114,8 @@ var init_pty = __esm({
17941
18114
  goalStatePath = (id2) => `${tmpdir3()}/hanoman-goal-gates/${id2}.count`;
17942
18115
  agentTempDir = (id2) => `${tmpdir3()}/hanoman-agents/${id2}`;
17943
18116
  agentsFilePath = (id2) => `${agentTempDir(id2)}/claude.json`;
18117
+ phaseContextFilePath = (id2) => `${agentTempDir(id2)}/phase-context.md`;
18118
+ AGENTS_ARG_SAFE_BYTES = 100 * 1024;
17944
18119
  askpassDenyPath = () => `${tmpdir3()}/hanoman-askpass/deny.sh`;
17945
18120
  ASKPASS_DENY = `#!/bin/sh
17946
18121
  echo "hanoman: tak ada manusia di pane ini \u2014 permintaan ketikan ditolak: $1" >&2
@@ -19087,9 +19262,28 @@ async function getSetting() {
19087
19262
  return {
19088
19263
  ...parsed.data,
19089
19264
  model: RETIRED_MODELS[parsed.data.model] ?? parsed.data.model,
19090
- codex: normalizeCodex(parsed.data.codex)
19265
+ codex: normalizeCodex(parsed.data.codex),
19266
+ orchestration: normalizeOrchestration(parsed.data.orchestration)
19091
19267
  };
19092
19268
  }
19269
+ function normalizePhaseRuntime(runtime, cell2) {
19270
+ if (!cell2.model) return cell2;
19271
+ const model = runtime === "codex" ? normalizeCodexModel(cell2.model) : normalizeClaudeModel(cell2.model);
19272
+ const effort2 = runtime === "codex" && cell2.effort ? coerceCodexEffort(model, cell2.effort) : cell2.effort;
19273
+ return { ...cell2, model, effort: effort2 };
19274
+ }
19275
+ function normalizeOrchestration(o) {
19276
+ const out4 = {};
19277
+ for (const [flow2, cfg] of Object.entries(o)) {
19278
+ const cells = (runtime) => Object.fromEntries(Object.entries(cfg[runtime]).map(([phase, cell2]) => [phase, normalizePhaseRuntime(runtime, cell2)]));
19279
+ out4[flow2] = { ...cfg, claude: cells("claude"), codex: cells("codex") };
19280
+ }
19281
+ return out4;
19282
+ }
19283
+ function normalizePhaseOverrides(runtime, o) {
19284
+ if (!o) return o;
19285
+ return Object.fromEntries(Object.entries(o).map(([phase, v]) => [phase, normalizePhaseRuntime(runtime, v)]));
19286
+ }
19093
19287
  function normalizeCodex(c) {
19094
19288
  const model = codexModel(c.model) ? c.model : RETIRED_CODEX_MODELS[c.model] ?? c.model;
19095
19289
  return { model, effort: coerceCodexEffort(model, c.effort) };
@@ -19118,13 +19312,13 @@ async function terminalAgentDefaults(o) {
19118
19312
  const effort2 = o.effort ?? base2.effort;
19119
19313
  return base2.agent === "codex" ? { agent: "codex", model, effort: coerceCodexEffort(model, effort2) } : { agent: "claude", model, effort: effort2 };
19120
19314
  }
19121
- var STEP, DEFAULT_SETTING, RETIRED_MODELS;
19315
+ var STEP, DEFAULT_SETTING, normalizeClaudeModel, normalizeCodexModel, RETIRED_MODELS;
19122
19316
  var init_settings3 = __esm({
19123
19317
  "src/services/settings.ts"() {
19124
19318
  "use strict";
19125
19319
  init_db();
19126
19320
  init_src();
19127
- STEP = { model: "claude-sonnet-5", effort: "medium" };
19321
+ STEP = { model: "sonnet", effort: "medium" };
19128
19322
  DEFAULT_SETTING = {
19129
19323
  ...STEP,
19130
19324
  autoDefault: true,
@@ -19173,7 +19367,9 @@ var init_settings3 = __esm({
19173
19367
  builtinRuntimeDefaults: BUILTIN_RUNTIME_DEFAULTS
19174
19368
  // seed rekomendasi model/fase; user edit dipertahankan
19175
19369
  };
19176
- RETIRED_MODELS = { "claude-opus-4-8": "claude-opus-5" };
19370
+ normalizeClaudeModel = (m) => RETIRED_MODELS[m] ?? m;
19371
+ normalizeCodexModel = (m) => codexModel(m) ? m : RETIRED_CODEX_MODELS[m] ?? m;
19372
+ RETIRED_MODELS = { "claude-opus-4-8": "opus" };
19177
19373
  }
19178
19374
  });
19179
19375
 
@@ -20699,13 +20895,13 @@ function inferLegacyStates(raw) {
20699
20895
  }
20700
20896
  }
20701
20897
  }
20702
- const claudeModel = !has("model") || object2.model === LEGACY_CLAUDE_RUNTIME_DEFAULTS.model ? "seeded" : "user";
20898
+ const claudeModel2 = !has("model") || object2.model === LEGACY_CLAUDE_RUNTIME_DEFAULTS.model ? "seeded" : "user";
20703
20899
  const claudeEffort = !has("effort") || object2.effort === LEGACY_CLAUDE_RUNTIME_DEFAULTS.effort ? "seeded" : "user";
20704
20900
  const rawCodexObject = isRecord(rawCodex) ? rawCodex : {};
20705
20901
  const codexModel2 = !has("codex") || !Object.prototype.hasOwnProperty.call(rawCodexObject, "model") || rawCodexObject.model === LEGACY_CODEX_RUNTIME_DEFAULTS.model ? "seeded" : "user";
20706
20902
  const codexEffort = !has("codex") || !Object.prototype.hasOwnProperty.call(rawCodexObject, "effort") || rawCodexObject.effort === LEGACY_CODEX_RUNTIME_DEFAULTS.effort ? "seeded" : "user";
20707
20903
  return {
20708
- claude: { model: claudeModel, effort: claudeEffort },
20904
+ claude: { model: claudeModel2, effort: claudeEffort },
20709
20905
  codex: { model: codexModel2, effort: codexEffort },
20710
20906
  orchestration
20711
20907
  };
@@ -21578,7 +21774,7 @@ var init_engine_command = __esm({
21578
21774
  init_src();
21579
21775
  AGENT_LABEL = { claude: "Claude Code", codex: "Codex CLI" };
21580
21776
  TELEGRAM_CONTROL_KIND = "gateway-control";
21581
- claudeModels = () => MODELS.map((m) => m.id);
21777
+ claudeModels = () => [...new Set(MODELS.flatMap((m) => m.resolved ? [m.id, m.resolved] : [m.id]))];
21582
21778
  codexModels = () => CODEX_MODELS.map((m) => m.id);
21583
21779
  effortsFor = (t) => t.agent === "codex" ? codexEfforts(t.model) : claudeEfforts(t.model);
21584
21780
  usage = [
@@ -24246,7 +24442,7 @@ var require_sender = __commonJS({
24246
24442
  */
24247
24443
  static frame(data, options2) {
24248
24444
  let mask;
24249
- let merge2 = false;
24445
+ let merge = false;
24250
24446
  let offset = 2;
24251
24447
  let skipMasking = false;
24252
24448
  if (options2.mask) {
@@ -24279,7 +24475,7 @@ var require_sender = __commonJS({
24279
24475
  }
24280
24476
  } else {
24281
24477
  dataLength = data.length;
24282
- merge2 = options2.mask && options2.readOnly && !skipMasking;
24478
+ merge = options2.mask && options2.readOnly && !skipMasking;
24283
24479
  }
24284
24480
  let payloadLength = dataLength;
24285
24481
  if (dataLength >= 65536) {
@@ -24289,7 +24485,7 @@ var require_sender = __commonJS({
24289
24485
  offset += 2;
24290
24486
  payloadLength = 126;
24291
24487
  }
24292
- const target3 = Buffer.allocUnsafe(merge2 ? dataLength + offset : offset);
24488
+ const target3 = Buffer.allocUnsafe(merge ? dataLength + offset : offset);
24293
24489
  target3[0] = options2.fin ? options2.opcode | 128 : options2.opcode;
24294
24490
  if (options2.rsv1) target3[0] |= 64;
24295
24491
  target3[1] = payloadLength;
@@ -24306,7 +24502,7 @@ var require_sender = __commonJS({
24306
24502
  target3[offset - 2] = mask[2];
24307
24503
  target3[offset - 1] = mask[3];
24308
24504
  if (skipMasking) return [target3, data];
24309
- if (merge2) {
24505
+ if (merge) {
24310
24506
  applyMask(data, mask, target3, offset, dataLength);
24311
24507
  return [target3];
24312
24508
  }
@@ -27501,7 +27697,7 @@ function recommendedModel(row, runtime) {
27501
27697
  if (!builtin) return null;
27502
27698
  const model = builtin.models[runtime];
27503
27699
  if (runtime === "claude" && (model === "haiku" || model === "sonnet")) return model;
27504
- return modelsForRuntime(runtime).some((entry) => entry.id === model) ? model : null;
27700
+ return modelKnownForRuntime(runtime, model) ? model : null;
27505
27701
  }
27506
27702
  function toRuntimeDef(row, runtime) {
27507
27703
  return { ...toDef(row), model: recommendedModel(row, runtime) };
@@ -41239,6 +41435,24 @@ async function reconciledSpecIdsSince(cutoff) {
41239
41435
  });
41240
41436
  return rows.map((r) => r.specId);
41241
41437
  }
41438
+ async function reconciledRuntimesSince(cutoff) {
41439
+ const rows = await prisma.sessionHistory.findMany({
41440
+ where: { endedReason: RECONCILED, reconciledAt: { gte: cutoff }, specId: { not: null } },
41441
+ select: { specId: true, agent: true, model: true, effort: true },
41442
+ orderBy: { startedAt: "desc" }
41443
+ });
41444
+ const out4 = /* @__PURE__ */ new Map();
41445
+ for (const r of rows) {
41446
+ if (out4.has(r.specId)) continue;
41447
+ out4.set(r.specId, {
41448
+ // `agent` kolom String — nilai asing jatuh ke claude, cermin pembacaan @hanoman_agent (pty.ts).
41449
+ agent: r.agent === "codex" ? "codex" : "claude",
41450
+ ...r.model ? { model: r.model } : {},
41451
+ ...r.effort ? { effort: r.effort } : {}
41452
+ });
41453
+ }
41454
+ return out4;
41455
+ }
41242
41456
  function installSessionHistory() {
41243
41457
  registerSessionHooks({
41244
41458
  onBirth: (b) => {
@@ -41905,13 +42119,15 @@ init_pty();
41905
42119
  // src/services/orchestration.ts
41906
42120
  init_src();
41907
42121
  init_pty();
42122
+ init_settings3();
41908
42123
  function sessionPhasePlan(setting, flow2, agent, orchestrator, phaseOverrides) {
41909
42124
  return resolvePhasePlan({
41910
42125
  flow: flow2,
41911
42126
  runtime: agent,
41912
42127
  orchestration: setting.orchestration,
41913
42128
  orchestrator,
41914
- phaseOverrides,
42129
+ // S6 · override transient dinormalisasi sama seperti sel Setting (model pensiun, koersi codex).
42130
+ phaseOverrides: normalizePhaseOverrides(agent, phaseOverrides),
41915
42131
  nativeAgents: nativeAgentsAvailable(agent)
41916
42132
  });
41917
42133
  }
@@ -42061,7 +42277,9 @@ async function startSpecSession(spec, opts) {
42061
42277
  method,
42062
42278
  verifyScope,
42063
42279
  context: isGoalFlow ? goalContext(brief) : specContext(brief),
42064
- fromAudit: fromAuditOf(spec.payload)
42280
+ fromAudit: fromAuditOf(spec.payload),
42281
+ // S2 · SPEC-394 · subagent tak melihat `resumeClause` orchestrator — ia dapat catatannya sendiri.
42282
+ ...resumeCtx ? { resume: resumeCtx } : {}
42065
42283
  }) : [];
42066
42284
  const scopeEnv = { HANOMAN_BASE_SHA: baseSha, HANOMAN_VERIFY_SCOPE: verifyScope };
42067
42285
  const s2 = createSession(spec.projectId, worktree, {
@@ -42077,6 +42295,9 @@ async function startSpecSession(spec, opts) {
42077
42295
  prompt,
42078
42296
  legacyPrompt,
42079
42297
  phaseAgents,
42298
+ // S3 · SPEC-172 · berkas fase continue masih memuat `Execute done` run lama; fase yang DIKERJAKAN
42299
+ // ULANG sesi ini tak boleh dihitung `doneAtBirth`, supaya ⚠ bukti tetap bisa menyala.
42300
+ ...isContinue && plan ? { rerunPhases: plan.phases.map((p3) => p3.phase) } : {},
42080
42301
  env: scopeEnv
42081
42302
  });
42082
42303
  return resume ? { id: s2.id, resumed: true } : { id: s2.id };
@@ -42534,6 +42755,7 @@ import { isAbsolute as isAbsolute8, relative as relative4, resolve as resolve22
42534
42755
  // src/services/session-event-relay.ts
42535
42756
  init_session_event_token();
42536
42757
  init_session_event_spool();
42758
+ init_ingress_policy();
42537
42759
  import { constants as constants3 } from "node:fs";
42538
42760
  import { mkdir as mkdir6, open as open3, readdir as readdir7, rm as rm6 } from "node:fs/promises";
42539
42761
  import { join as join25 } from "node:path";
@@ -42552,18 +42774,22 @@ function sessionEventRelayStatus(root = sessionEventSpoolRoot()) {
42552
42774
  droppedEvents: 0
42553
42775
  } };
42554
42776
  }
42555
- async function drainSessionEventSpool(app3, root = sessionEventSpoolRoot()) {
42777
+ async function drainSessionEventSpool(app3, root = sessionEventSpoolRoot(), host2 = null) {
42556
42778
  const previous = sessionEventRelayStatus(root);
42557
- const current = { retries: 0, dropped: 0 };
42779
+ const current = { retries: 0, dropped: 0, rejected: /* @__PURE__ */ new Map() };
42558
42780
  let delivered = 0;
42559
42781
  let failed = false;
42560
42782
  try {
42561
- delivered = await drainSpool(app3, root, current);
42783
+ delivered = await drainSpool(app3, root, current, host2);
42562
42784
  return delivered;
42563
42785
  } catch (error) {
42564
42786
  failed = true;
42565
42787
  throw error;
42566
42788
  } finally {
42789
+ if (current.rejected.size) {
42790
+ const summary = [...current.rejected].map(([code, n2]) => `${n2}\xD7 ${code}`).join(", ");
42791
+ console.warn(`session event relay: route menolak event (${summary}) \u2014 event dibuang`);
42792
+ }
42567
42793
  const now = (/* @__PURE__ */ new Date()).toISOString();
42568
42794
  observations.set(root, {
42569
42795
  state: failed || current.retries > 0 ? "degraded" : "ready",
@@ -42576,7 +42802,7 @@ async function drainSessionEventSpool(app3, root = sessionEventSpoolRoot()) {
42576
42802
  });
42577
42803
  }
42578
42804
  }
42579
- async function drainSpool(app3, root, observation) {
42805
+ async function drainSpool(app3, root, observation, host2) {
42580
42806
  await mkdir6(root, { recursive: true, mode: 448 });
42581
42807
  let delivered = 0;
42582
42808
  let examined = 0;
@@ -42590,8 +42816,10 @@ async function drainSpool(app3, root, observation) {
42590
42816
  } catch {
42591
42817
  continue;
42592
42818
  }
42819
+ entries3.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
42593
42820
  for (const entry of entries3) {
42594
42821
  if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
42822
+ const eventAt = /^(\d{1,15})-/.exec(entry.name)?.[1];
42595
42823
  if (++examined > MAX_FILES_PER_DRAIN) return delivered;
42596
42824
  const path = join25(dir2, entry.name);
42597
42825
  let payload;
@@ -42623,7 +42851,11 @@ async function drainSpool(app3, root, observation) {
42623
42851
  url: "/api/session-events",
42624
42852
  headers: {
42625
42853
  authorization: `Bearer ${sessionEventToken(session.name)}`,
42626
- "x-hanoman-session": session.name
42854
+ "x-hanoman-session": session.name,
42855
+ ...host2 ? { host: host2 } : {},
42856
+ // S1 · waktu KEJADIAN event (bukan waktu drain) — route memakainya untuk membedakan
42857
+ // replay dari lanjutan relay subagent yang sama.
42858
+ ...eventAt ? { "x-hanoman-event-at": eventAt } : {}
42627
42859
  },
42628
42860
  payload
42629
42861
  });
@@ -42634,7 +42866,10 @@ async function drainSpool(app3, root, observation) {
42634
42866
  await rm6(path, { force: true }).catch(() => {
42635
42867
  });
42636
42868
  if (response.statusCode >= 200 && response.statusCode < 300) delivered++;
42637
- else observation.dropped++;
42869
+ else {
42870
+ observation.dropped++;
42871
+ observation.rejected.set(response.statusCode, (observation.rejected.get(response.statusCode) ?? 0) + 1);
42872
+ }
42638
42873
  } catch {
42639
42874
  observation.retries++;
42640
42875
  }
@@ -42644,12 +42879,13 @@ async function drainSpool(app3, root, observation) {
42644
42879
  }
42645
42880
  function startSessionEventRelay(app3, options2 = {}) {
42646
42881
  const root = options2.root ?? sessionEventSpoolRoot();
42882
+ const host2 = controlHost(loadIngressPolicy(options2.env ?? process.env));
42647
42883
  let running = false;
42648
42884
  const tick6 = async () => {
42649
42885
  if (running) return;
42650
42886
  running = true;
42651
42887
  try {
42652
- await drainSessionEventSpool({ inject: async (request) => app3.inject(request) }, root);
42888
+ await drainSessionEventSpool({ inject: async (request) => app3.inject(request) }, root, host2);
42653
42889
  } catch (error) {
42654
42890
  console.error("session event relay gagal:", error);
42655
42891
  } finally {
@@ -42762,6 +42998,15 @@ async function startAgentInvocation(input, io = {}) {
42762
42998
  runtimeInvocationId: input.runtimeInvocationId
42763
42999
  } }
42764
43000
  });
43001
+ if (existing?.endedAt && startedAt.getTime() > existing.endedAt.getTime()) {
43002
+ const row2 = await prisma.agentInvocation.update({
43003
+ where: { id: existing.id },
43004
+ data: { status: "running", endedAt: null, durationMs: null }
43005
+ });
43006
+ const before3 = snapshot2(input.cwd, io.gitStatus);
43007
+ if (before3 !== null) snapshotHashes.set(keyOf(input), before3);
43008
+ return { row: row2, duplicate: false };
43009
+ }
42765
43010
  const row = await prisma.agentInvocation.upsert({
42766
43011
  where: { sessionId_runtimeInvocationId: {
42767
43012
  sessionId: input.sessionId,
@@ -42812,7 +43057,8 @@ async function stopAgentInvocation(input, io = {}) {
42812
43057
  ...usage2,
42813
43058
  resultExcerpt: cleanResult === null ? null : utf8Prefix(cleanResult, MAX_EXCERPT_BYTES),
42814
43059
  resultHash: cleanResult === null ? null : hash3(cleanResult),
42815
- workspaceChanged: before2 !== void 0 && after !== null && before2 !== after,
43060
+ // S1 · putaran relay (baris dibuka ulang) tak menghapus perubahan workspace putaran sebelumnya.
43061
+ workspaceChanged: (existing?.workspaceChanged ?? false) || before2 !== void 0 && after !== null && before2 !== after,
42816
43062
  // ADR-0164 · effort yang BENAR-BENAR dipakai runtime menang atas nilai start-time.
42817
43063
  ...input.effort ? { effort: input.effort } : {}
42818
43064
  };
@@ -44402,6 +44648,7 @@ var ensureWorktree = (repoDir, wt, branchFrom) => {
44402
44648
  var resumeNote = (reused) => reused ? `
44403
44649
 
44404
44650
  ${RESUMED_WORKTREE_NOTE}` : "";
44651
+ var phaseResume = (reused, repoDir, id2, flow2) => reused ? { resume: { worktreeKept: true, recorded: readPhases(phaseFilePath(repoDir, id2), flow2).filter((p3) => p3.state === "done" || p3.state === "skipped").map((p3) => `${p3.name} ${p3.state}`) } } : {};
44405
44652
  async function terminal_default(app3, opts) {
44406
44653
  app3.get("/terminal/sessions", async () => listSessions());
44407
44654
  app3.post("/terminal/sessions", async (req, reply) => {
@@ -44496,7 +44743,12 @@ async function terminal_default(app3, opts) {
44496
44743
  decisionFile: decisionFilePath(repoDir, id2),
44497
44744
  prompt: plan ? startProjectPrompt("reverse", brief, "reverse-docs", plan) + resumeNote(reused) : legacyPrompt,
44498
44745
  legacyPrompt,
44499
- phaseAgents: plan ? buildPhaseAgents(plan, { flow: "reverse", method: PROJECT_METHOD, context: projectContext(brief) }) : []
44746
+ phaseAgents: plan ? buildPhaseAgents(plan, {
44747
+ flow: "reverse",
44748
+ method: PROJECT_METHOD,
44749
+ context: projectContext(brief),
44750
+ ...phaseResume(reused, repoDir, id2, "reverse")
44751
+ }) : []
44500
44752
  });
44501
44753
  return { code: 201, body: { id: s3.id } };
44502
44754
  }, (pane) => ({ code: 201, body: { id: pane.id } }));
@@ -44528,7 +44780,12 @@ async function terminal_default(app3, opts) {
44528
44780
  decisionFile: decisionFilePath(repoDir, id2),
44529
44781
  prompt: plan ? startScaffoldPrompt(brief, "scaffold-docs", plan) + resumeNote(reused) : legacyPrompt,
44530
44782
  legacyPrompt,
44531
- phaseAgents: plan ? buildPhaseAgents(plan, { flow: "scaffold", method: PROJECT_METHOD, context: scaffoldContext(brief) }) : []
44783
+ phaseAgents: plan ? buildPhaseAgents(plan, {
44784
+ flow: "scaffold",
44785
+ method: PROJECT_METHOD,
44786
+ context: scaffoldContext(brief),
44787
+ ...phaseResume(reused, repoDir, id2, "scaffold")
44788
+ }) : []
44532
44789
  });
44533
44790
  return { code: 201, body: { id: s3.id } };
44534
44791
  }, (pane) => ({ code: 201, body: { id: pane.id } }));
@@ -44569,7 +44826,8 @@ async function terminal_default(app3, opts) {
44569
44826
  flow: "prd",
44570
44827
  method: PROJECT_METHOD,
44571
44828
  context: prdContext(project_, brief, audit),
44572
- prd: { slug }
44829
+ prd: { slug },
44830
+ ...phaseResume(reused, repoDir, id2, "prd")
44573
44831
  }) : []
44574
44832
  });
44575
44833
  return { code: 201, body: { id: s3.id } };
@@ -44615,7 +44873,8 @@ async function terminal_default(app3, opts) {
44615
44873
  flow: "breakdown",
44616
44874
  method: PROJECT_METHOD,
44617
44875
  context: breakdownContext(project_, prd),
44618
- prd: { slug, title }
44876
+ prd: { slug, title },
44877
+ ...phaseResume(reused, repoDir, id2, "breakdown")
44619
44878
  }) : []
44620
44879
  });
44621
44880
  return { code: 201, body: { id: s3.id } };
@@ -45891,7 +46150,7 @@ async function vps_default(app3) {
45891
46150
  init_config2();
45892
46151
  import { execFileSync as execFileSync5 } from "node:child_process";
45893
46152
  import { readFileSync as readFileSync18 } from "node:fs";
45894
- import { homedir as homedir8 } from "node:os";
46153
+ import { homedir as homedir8, userInfo } from "node:os";
45895
46154
  import { join as join31 } from "node:path";
45896
46155
  var USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
45897
46156
  var TTL_MS2 = 3e4;
@@ -45907,19 +46166,30 @@ var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
45907
46166
  function credsFile() {
45908
46167
  return join31(effectiveStr("CLAUDE_CONFIG_DIR") ?? join31(homedir8(), ".claude"), ".credentials.json");
45909
46168
  }
45910
- function readAccessToken() {
45911
- if (process.platform === "darwin" && !effectiveStr("CLAUDE_CONFIG_DIR")) {
46169
+ var securityRun = (args) => execFileSync5("security", args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
46170
+ var osAccount = () => {
46171
+ try {
46172
+ return userInfo().username || null;
46173
+ } catch {
46174
+ return null;
46175
+ }
46176
+ };
46177
+ function keychainAccessToken(run4 = securityRun, account = osAccount()) {
46178
+ const base2 = ["find-generic-password", "-s", "Claude Code-credentials"];
46179
+ for (const scope of account ? [["-a", account], []] : [[]]) {
45912
46180
  try {
45913
- const blob = execFileSync5(
45914
- "security",
45915
- ["find-generic-password", "-s", "Claude Code-credentials", "-w"],
45916
- { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }
45917
- );
45918
- const tok = JSON.parse(blob)?.claudeAiOauth?.accessToken;
45919
- if (tok) return tok;
46181
+ const tok = JSON.parse(run4([...base2, ...scope, "-w"]))?.claudeAiOauth?.accessToken;
46182
+ if (typeof tok === "string" && tok) return tok;
45920
46183
  } catch {
45921
46184
  }
45922
46185
  }
46186
+ return null;
46187
+ }
46188
+ function readAccessToken() {
46189
+ if (process.platform === "darwin" && !effectiveStr("CLAUDE_CONFIG_DIR")) {
46190
+ const tok = keychainAccessToken();
46191
+ if (tok) return tok;
46192
+ }
45923
46193
  try {
45924
46194
  return JSON.parse(readFileSync18(credsFile(), "utf8"))?.claudeAiOauth?.accessToken ?? null;
45925
46195
  } catch {
@@ -46125,19 +46395,25 @@ var claudeRow = external_exports.object({
46125
46395
  });
46126
46396
  function parseClaudeModels(raw) {
46127
46397
  const rows = external_exports.array(claudeRow).min(1).max(500).parse(raw);
46398
+ let preferred = null;
46128
46399
  const models2 = /* @__PURE__ */ new Map();
46129
46400
  for (const row of rows) {
46401
+ const resolved = row.resolvedModel ?? row.value;
46402
+ const head = row.description?.split(" \xB7 ")[0];
46130
46403
  const model = {
46131
- id: row.resolvedModel ?? row.value,
46132
- label: row.description?.split(" \xB7 ")[0] || row.displayName,
46404
+ id: row.value,
46405
+ label: row.value === "default" && head ? `${row.displayName} \xB7 ${head}` : head || row.displayName,
46406
+ resolved,
46133
46407
  ...row.supportedEffortLevels ? { efforts: sorted(row.supportedEffortLevels) } : {}
46134
46408
  };
46135
- if (!models2.has(model.id)) models2.set(model.id, model);
46136
- if (row.value !== model.id && row.value !== "default") {
46137
- models2.set(row.value, { ...model, id: row.value, label: row.displayName });
46409
+ if (row.value === "default") {
46410
+ preferred ??= model;
46411
+ continue;
46138
46412
  }
46413
+ const existing = models2.get(resolved);
46414
+ if (!existing || existing.id === resolved && row.value !== resolved) models2.set(resolved, model);
46139
46415
  }
46140
- return [...models2.values()];
46416
+ return preferred ? [preferred, ...models2.values()] : [...models2.values()];
46141
46417
  }
46142
46418
  var codexRow = external_exports.object({
46143
46419
  slug: id,
@@ -46171,7 +46447,7 @@ function parseCachedCatalog(raw) {
46171
46447
  error: external_exports.string().nullable()
46172
46448
  });
46173
46449
  return external_exports.object({
46174
- claude: external_exports.array(external_exports.object({ id, label, efforts: efforts.optional() })).min(1).max(1e3),
46450
+ claude: external_exports.array(external_exports.object({ id, label, resolved: id.optional(), efforts: efforts.optional() })).min(1).max(1e3),
46175
46451
  codex: external_exports.array(external_exports.object({ id, label, efforts: efforts.min(1), fallback: id, minClient: external_exports.string() })).min(1).max(1e3),
46176
46452
  providers: external_exports.object({ claude: status2, codex: status2 })
46177
46453
  }).parse(raw);
@@ -46297,7 +46573,6 @@ async function probeModelCatalog(agent) {
46297
46573
  }
46298
46574
 
46299
46575
  // src/services/model-catalog.ts
46300
- var merge = (old, fresh) => [...new Map([...old, ...fresh].map((m) => [m.id, m])).values()];
46301
46576
  function createModelCatalogService(deps) {
46302
46577
  let state = bundledModelCatalog();
46303
46578
  let initialized = false;
@@ -46324,7 +46599,7 @@ function createModelCatalogService(deps) {
46324
46599
  try {
46325
46600
  const models2 = await deps.probe(agent);
46326
46601
  if (!models2.length) throw new Error("empty catalog");
46327
- state = agent === "claude" ? { ...state, claude: merge(bundledModelCatalog().claude, models2) } : { ...state, codex: merge(bundledModelCatalog().codex, models2) };
46602
+ state = agent === "claude" ? { ...state, claude: models2 } : { ...state, codex: models2 };
46328
46603
  state = { ...state, providers: { ...state.providers, [agent]: {
46329
46604
  source: "cli",
46330
46605
  checkedAt,
@@ -48187,6 +48462,13 @@ init_pty();
48187
48462
  var bearer = (h2) => /^Bearer (.+)$/.exec(h2 ?? "")?.[1] ?? "";
48188
48463
  var recordOf = (value) => value && typeof value === "object" ? value : null;
48189
48464
  var boundedString = (value, max) => typeof value === "string" && value.length > 0 && value.length <= max ? value : void 0;
48465
+ var EVENT_AT_PAST_MS = 7 * 24 * 60 * 6e4;
48466
+ var EVENT_AT_FUTURE_MS = 6e4;
48467
+ var eventAtOf = (header2, now = Date.now()) => {
48468
+ if (typeof header2 !== "string" || !/^\d{1,15}$/.test(header2)) return void 0;
48469
+ const ms = Number(header2);
48470
+ return ms >= now - EVENT_AT_PAST_MS && ms <= now + EVENT_AT_FUTURE_MS ? new Date(ms) : void 0;
48471
+ };
48190
48472
  async function session_events_default(app3) {
48191
48473
  app3.post("/session-events", async (req, reply) => {
48192
48474
  const sessionId2 = String(req.headers["x-hanoman-session"] ?? "");
@@ -48219,8 +48501,10 @@ async function session_events_default(app3) {
48219
48501
  ...meta.effort ? { effort: meta.effort } : {},
48220
48502
  cwd: s2.cwd
48221
48503
  };
48222
- const outcome = lifecycle === "SubagentStart" ? await startAgentInvocation(identity) : await stopAgentInvocation({
48504
+ const eventAt = eventAtOf(req.headers["x-hanoman-event-at"]);
48505
+ const outcome = lifecycle === "SubagentStart" ? await startAgentInvocation({ ...identity, ...eventAt ? { startedAt: eventAt } : {} }) : await stopAgentInvocation({
48223
48506
  ...identity,
48507
+ ...eventAt ? { endedAt: eventAt } : {},
48224
48508
  status: body.status === "interrupted" ? "interrupted" : "completed",
48225
48509
  result: boundedString(body.last_assistant_message ?? body.result, 1e6),
48226
48510
  transcriptPath: boundedString(
@@ -49542,7 +49826,7 @@ function toolsProblem(tools, catalogIds) {
49542
49826
  }
49543
49827
  function modelProblem(model, runtime) {
49544
49828
  if (!model) return null;
49545
- const ok2 = modelsForRuntime(runtime).some((m) => m.id === model);
49829
+ const ok2 = modelKnownForRuntime(runtime, model);
49546
49830
  return ok2 ? null : { error: "model tak dikenal untuk runtime ini", model, runtime };
49547
49831
  }
49548
49832
  function effortProblem(effort2, runtime, model) {
@@ -53163,7 +53447,11 @@ var prodDeps2 = {
53163
53447
  // dependency ADR-0093 TETAP berlaku (tak diberi `force`). Keputusan sadar risiko: mesin 8 GB
53164
53448
  // operator sudah pernah kernel panic akibat sesi paralel berlebih (memori
53165
53449
  // mac-mini-8gb-panic-agen-paralel) — operator memilih "semua kembali" di atas throttle.
53166
- startSpec: (spec) => startSpecSession(spec, { flow: flowForSource(spec.source), bypassCapacity: true }),
53450
+ startSpec: (spec, runtime) => startSpecSession(spec, {
53451
+ flow: flowForSource(spec.source),
53452
+ bypassCapacity: true,
53453
+ ...runtime ?? {}
53454
+ }),
53167
53455
  recordFail: recordFailure
53168
53456
  };
53169
53457
  async function resumeReconciledSessions(cutoff, deps = prodDeps2) {
@@ -53171,9 +53459,10 @@ async function resumeReconciledSessions(cutoff, deps = prodDeps2) {
53171
53459
  const specIds = await reconciledSpecIdsSince(cutoff);
53172
53460
  if (!specIds.length) return report;
53173
53461
  const specs = await prisma.spec.findMany({ where: { id: { in: specIds }, stage: { not: "done" } } });
53462
+ const runtimes = await reconciledRuntimesSince(cutoff);
53174
53463
  for (const spec of specs) {
53175
53464
  try {
53176
- await deps.startSpec(spec);
53465
+ await deps.startSpec(spec, runtimes.get(spec.id));
53177
53466
  report.resumed.push(spec.id);
53178
53467
  } catch (e) {
53179
53468
  const reason = e instanceof Error ? e.message : String(e);