hanoman 0.9.1 → 0.9.3

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,
@@ -17055,19 +17148,45 @@ import { chmodSync as chmodSync6, mkdirSync as mkdirSync7, readFileSync as readF
17055
17148
  import { stat as stat2 } from "node:fs/promises";
17056
17149
  import { dirname as dirname8 } from "node:path";
17057
17150
  import { tmpdir as tmpdir3 } from "node:os";
17058
- function phaseView(p3, a, phases2) {
17059
- const now = Date.now();
17060
- trackDoneSeen(phases2, a.doneSeenAt, now);
17151
+ function doneSeenOf(id2) {
17152
+ const known2 = phaseDoneSeen.get(id2);
17153
+ if (known2) return known2;
17154
+ const seen2 = /* @__PURE__ */ new Map();
17155
+ try {
17156
+ const raw = tmux("show-options", "-v", "-q", "-t", name(id2), PHASE_DONE_SEEN_OPT).trim();
17157
+ const parsed = raw ? JSON.parse(raw) : {};
17158
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
17159
+ for (const [phase, at] of Object.entries(parsed))
17160
+ if (typeof at === "number" && Number.isFinite(at)) seen2.set(phase, at);
17161
+ }
17162
+ } catch {
17163
+ }
17164
+ phaseDoneSeen.set(id2, seen2);
17165
+ return seen2;
17166
+ }
17167
+ function trackPhaseDoneSeen(id2, phases2, now) {
17168
+ const seen2 = doneSeenOf(id2);
17169
+ const before2 = JSON.stringify([...seen2]);
17170
+ trackDoneSeen(phases2, seen2, now);
17171
+ if (JSON.stringify([...seen2]) !== before2) {
17172
+ void tmuxAsync("set-option", "-t", name(id2), PHASE_DONE_SEEN_OPT, JSON.stringify(Object.fromEntries(seen2))).catch(() => {
17173
+ });
17174
+ }
17175
+ return seen2;
17176
+ }
17177
+ function phaseView(p3, phases2) {
17061
17178
  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(
17179
+ if (!roster.length) return phases2;
17180
+ const now = Date.now();
17181
+ return enrichPhases(
17063
17182
  phases2,
17064
17183
  roster,
17065
17184
  phaseInvocations.get(p3.id) ?? [],
17066
- a.doneSeenAt,
17185
+ trackPhaseDoneSeen(p3.id, phases2, now),
17067
17186
  now,
17068
17187
  p3.startedAt * 1e3,
17069
17188
  new Set(p3.doneAtBirth ?? [])
17070
- ) : phases2;
17189
+ );
17071
17190
  }
17072
17191
  function noTtyPromptEnv() {
17073
17192
  const path = askpassDenyPath();
@@ -17289,6 +17408,7 @@ function createSession(projectId, cwd, opts = {}) {
17289
17408
  const existing = getSession(id2);
17290
17409
  if (existing && !existing.exited) return existing;
17291
17410
  if (existing) killSession(id2);
17411
+ phaseDoneSeen.delete(id2);
17292
17412
  const eventDir = opts.command ? void 0 : sessionEventDir(id2);
17293
17413
  if (eventDir) {
17294
17414
  mkdirSync7(eventDir, { recursive: true, mode: 448 });
@@ -17296,26 +17416,30 @@ function createSession(projectId, cwd, opts = {}) {
17296
17416
  }
17297
17417
  const agentForDefs = opts.agent ?? "claude";
17298
17418
  const sessionEffort = agentForDefs === "codex" && opts.model && opts.effort ? coerceCodexEffort(opts.model, opts.effort) : opts.effort;
17419
+ const requestedPhaseDefs = opts.command ? [] : opts.phaseAgents ?? [];
17420
+ const bornPrompt = (orch) => orch ? opts.prompt : opts.legacyPrompt ?? opts.prompt;
17299
17421
  const selectionContext = {
17300
17422
  projectId,
17301
17423
  runtime: agentForDefs,
17302
17424
  flow: opts.flow,
17303
17425
  cwd,
17304
17426
  baseSha: opts.env?.HANOMAN_BASE_SHA,
17305
- prompt: opts.prompt,
17427
+ prompt: bornPrompt(requestedPhaseDefs.length > 0),
17306
17428
  changedFiles: opts.command ? [] : collectChangedFiles(cwd, opts.env?.HANOMAN_BASE_SHA)
17307
17429
  };
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)
17430
+ const selectCustomDefs = (context, warn) => {
17431
+ const raw = opts.command ? [] : customAgentsFor(context);
17432
+ for (const def2 of raw) {
17433
+ if (warn && isPhaseAgentName(def2.name)) {
17434
+ process.stderr.write(
17435
+ `hanoman: custom agent ${def2.name} diabaikan \u2014 awalan hanoman-fase- dicadangkan (ADR-0164)
17314
17436
  `
17315
- );
17437
+ );
17438
+ }
17316
17439
  }
17317
- }
17318
- const requestedPhaseDefs = opts.command ? [] : opts.phaseAgents ?? [];
17440
+ return raw.filter((def2) => !isPhaseAgentName(def2.name));
17441
+ };
17442
+ let customDefs = selectCustomDefs(selectionContext, true);
17319
17443
  let rosterBlock = "";
17320
17444
  let codexAgentArgs = [];
17321
17445
  let agentsFile;
@@ -17328,14 +17452,32 @@ function createSession(projectId, cwd, opts = {}) {
17328
17452
  const tempDir = agentTempDir(id2);
17329
17453
  agentConfigDir = tempDir;
17330
17454
  mkdirSync7(tempDir, { recursive: true, mode: 448 });
17331
- const readOnlyHook = customDefs.some((def2) => def2.workspacePolicy === "read-only") ? writeReadOnlyHook(tempDir) : void 0;
17455
+ let hook;
17456
+ const readOnlyHookFor = () => customDefs.some((def2) => def2.workspacePolicy === "read-only") ? hook ??= writeReadOnlyHook(tempDir) : void 0;
17332
17457
  const attempt = (phaseDefs) => {
17333
17458
  const defs = [...phaseDefs, ...customDefs];
17334
17459
  if (defs.length === 0) return [];
17460
+ const readOnlyHook = readOnlyHookFor();
17335
17461
  if (agentForDefs === "claude") {
17336
17462
  const file = agentsFilePath(id2);
17337
17463
  try {
17338
- writeFileSync8(file, renderAgentsJson(defs, { readOnlyHookCommand: readOnlyHook?.command }), { mode: 384 });
17464
+ const shared = phaseDefs.find((d) => d.context !== void 0)?.context;
17465
+ let phaseContextFile;
17466
+ if (shared !== void 0) {
17467
+ const path = phaseContextFilePath(id2);
17468
+ writeFileSync8(path, shared, { mode: 384 });
17469
+ chmodSync6(path, 384);
17470
+ phaseContextFile = { context: shared, path };
17471
+ }
17472
+ const json = renderAgentsJson(defs, {
17473
+ readOnlyHookCommand: readOnlyHook?.command,
17474
+ ...phaseContextFile ? { phaseContextFile } : {}
17475
+ });
17476
+ const bytes = Buffer.byteLength(json);
17477
+ if (phaseDefs.length > 0 && bytes > AGENTS_ARG_SAFE_BYTES) {
17478
+ return [`argumen --agents ${bytes} B melewati ambang aman ${AGENTS_ARG_SAFE_BYTES} B (batas satu argumen exec Linux 128 KiB)`];
17479
+ }
17480
+ writeFileSync8(file, json, { mode: 384 });
17339
17481
  } catch (error) {
17340
17482
  if (phaseDefs.length === 0) throw error;
17341
17483
  return [error instanceof Error ? error.message : String(error)];
@@ -17371,11 +17513,13 @@ function createSession(projectId, cwd, opts = {}) {
17371
17513
  orchestrated = phaseFailReasons.length === 0;
17372
17514
  }
17373
17515
  if (!orchestrated) {
17374
- if (requestedPhaseDefs.length > 0)
17516
+ if (requestedPhaseDefs.length > 0) {
17375
17517
  process.stderr.write(
17376
17518
  `hanoman: agen fase sesi ${id2} gagal dimaterialisasi \u2014 sesi lahir mode tunggal: ${phaseFailReasons.join("; ")}
17377
17519
  `
17378
17520
  );
17521
+ customDefs = selectCustomDefs({ ...selectionContext, prompt: bornPrompt(false) }, false);
17522
+ }
17379
17523
  attempt([]);
17380
17524
  }
17381
17525
  if (orchestrated && agentForDefs === "claude") {
@@ -17391,7 +17535,7 @@ function createSession(projectId, cwd, opts = {}) {
17391
17535
  }
17392
17536
  let promptArg = "";
17393
17537
  let promptFile;
17394
- const sessionPrompt = orchestrated ? opts.prompt : opts.legacyPrompt ?? opts.prompt;
17538
+ const sessionPrompt = bornPrompt(orchestrated);
17395
17539
  if (!opts.command && sessionPrompt) {
17396
17540
  promptFile = promptFilePath(id2);
17397
17541
  mkdirSync7(dirname8(promptFile), { recursive: true, mode: 448 });
@@ -17561,7 +17705,8 @@ function createSession(projectId, cwd, opts = {}) {
17561
17705
  }
17562
17706
  if (opts.phaseFile) tmux("set-option", "-t", name(id2), "@hanoman_phase_file", opts.phaseFile);
17563
17707
  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);
17708
+ const rerun = new Set(opts.rerunPhases ?? []);
17709
+ const doneAtBirth = readPhases(opts.phaseFile, opts.flow).filter((p3) => (p3.state === "done" || p3.state === "skipped") && !rerun.has(p3.name)).map((p3) => p3.name);
17565
17710
  if (doneAtBirth.length > 0)
17566
17711
  tmux("set-option", "-t", name(id2), "@hanoman_done_at_birth", doneAtBirth.join(","));
17567
17712
  }
@@ -17700,8 +17845,7 @@ function open(id2) {
17700
17845
  scrollback: "",
17701
17846
  clients: /* @__PURE__ */ new Set(),
17702
17847
  lastPhases: "",
17703
- pending: "",
17704
- doneSeenAt: /* @__PURE__ */ new Map()
17848
+ pending: ""
17705
17849
  };
17706
17850
  pty.onData((d) => {
17707
17851
  a.pending += d;
@@ -17731,13 +17875,14 @@ function end(id2, code) {
17731
17875
  flushOutput(a);
17732
17876
  broadcast(a, { t: "exit", code });
17733
17877
  drop(id2);
17878
+ phaseDoneSeen.delete(id2);
17734
17879
  }
17735
17880
  async function pollPhases(p3, a) {
17736
17881
  if (!p3.flow || !p3.phaseFile) return;
17737
17882
  const raw = await readPhasesAsync(p3.phaseFile, p3.flow);
17738
17883
  const complete = await sessionCompleteAsync(raw, p3.cwd, p3.specId);
17739
17884
  if (attached.get(p3.id) !== a) return;
17740
- const phases2 = phaseView(p3, a, raw);
17885
+ const phases2 = phaseView(p3, raw);
17741
17886
  const json = phaseKey(phases2, complete);
17742
17887
  if (json === a.lastPhases) return;
17743
17888
  a.lastPhases = json;
@@ -17814,7 +17959,7 @@ function attachLive(id2, c, p3) {
17814
17959
  c.send(frame({ t: "alt", on: p3.altScreen }));
17815
17960
  if (p3.flow && p3.phaseFile) {
17816
17961
  const raw = readPhases(p3.phaseFile, p3.flow);
17817
- const phases2 = phaseView(p3, a, raw);
17962
+ const phases2 = phaseView(p3, raw);
17818
17963
  const complete = sessionComplete(raw, p3.cwd, p3.specId);
17819
17964
  a.lastPhases = phaseKey(phases2, complete);
17820
17965
  c.send(frame({ t: "phase", phases: phases2, complete }));
@@ -17831,6 +17976,7 @@ function resize(id2, cols, rows) {
17831
17976
  }
17832
17977
  function killSession(id2) {
17833
17978
  phaseInvocations.delete(id2);
17979
+ phaseDoneSeen.delete(id2);
17834
17980
  const p3 = getSession(id2);
17835
17981
  if (!p3) return false;
17836
17982
  const transcript = captureTranscript(id2);
@@ -17869,7 +18015,7 @@ function spawnPty(...args) {
17869
18015
  );
17870
18016
  }
17871
18017
  }
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;
18018
+ 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
18019
  var init_pty = __esm({
17874
18020
  "src/services/pty.ts"() {
17875
18021
  "use strict";
@@ -17929,6 +18075,8 @@ var init_pty = __esm({
17929
18075
  };
17930
18076
  attached = /* @__PURE__ */ new Map();
17931
18077
  phaseInvocations = /* @__PURE__ */ new Map();
18078
+ PHASE_DONE_SEEN_OPT = "@hanoman_phase_done_seen";
18079
+ phaseDoneSeen = /* @__PURE__ */ new Map();
17932
18080
  claudeBin = () => effectiveStr("HANOMAN_CLAUDE_BIN") ?? "claude";
17933
18081
  shellBin = () => effectiveStr("HANOMAN_SHELL") ?? process.env.SHELL ?? "/bin/bash";
17934
18082
  codexBin = () => effectiveStr("HANOMAN_CODEX_BIN") ?? "codex";
@@ -17941,6 +18089,8 @@ var init_pty = __esm({
17941
18089
  goalStatePath = (id2) => `${tmpdir3()}/hanoman-goal-gates/${id2}.count`;
17942
18090
  agentTempDir = (id2) => `${tmpdir3()}/hanoman-agents/${id2}`;
17943
18091
  agentsFilePath = (id2) => `${agentTempDir(id2)}/claude.json`;
18092
+ phaseContextFilePath = (id2) => `${agentTempDir(id2)}/phase-context.md`;
18093
+ AGENTS_ARG_SAFE_BYTES = 100 * 1024;
17944
18094
  askpassDenyPath = () => `${tmpdir3()}/hanoman-askpass/deny.sh`;
17945
18095
  ASKPASS_DENY = `#!/bin/sh
17946
18096
  echo "hanoman: tak ada manusia di pane ini \u2014 permintaan ketikan ditolak: $1" >&2
@@ -19087,9 +19237,28 @@ async function getSetting() {
19087
19237
  return {
19088
19238
  ...parsed.data,
19089
19239
  model: RETIRED_MODELS[parsed.data.model] ?? parsed.data.model,
19090
- codex: normalizeCodex(parsed.data.codex)
19240
+ codex: normalizeCodex(parsed.data.codex),
19241
+ orchestration: normalizeOrchestration(parsed.data.orchestration)
19091
19242
  };
19092
19243
  }
19244
+ function normalizePhaseRuntime(runtime, cell2) {
19245
+ if (!cell2.model) return cell2;
19246
+ const model = runtime === "codex" ? normalizeCodexModel(cell2.model) : normalizeClaudeModel(cell2.model);
19247
+ const effort2 = runtime === "codex" && cell2.effort ? coerceCodexEffort(model, cell2.effort) : cell2.effort;
19248
+ return { ...cell2, model, effort: effort2 };
19249
+ }
19250
+ function normalizeOrchestration(o) {
19251
+ const out4 = {};
19252
+ for (const [flow2, cfg] of Object.entries(o)) {
19253
+ const cells = (runtime) => Object.fromEntries(Object.entries(cfg[runtime]).map(([phase, cell2]) => [phase, normalizePhaseRuntime(runtime, cell2)]));
19254
+ out4[flow2] = { ...cfg, claude: cells("claude"), codex: cells("codex") };
19255
+ }
19256
+ return out4;
19257
+ }
19258
+ function normalizePhaseOverrides(runtime, o) {
19259
+ if (!o) return o;
19260
+ return Object.fromEntries(Object.entries(o).map(([phase, v]) => [phase, normalizePhaseRuntime(runtime, v)]));
19261
+ }
19093
19262
  function normalizeCodex(c) {
19094
19263
  const model = codexModel(c.model) ? c.model : RETIRED_CODEX_MODELS[c.model] ?? c.model;
19095
19264
  return { model, effort: coerceCodexEffort(model, c.effort) };
@@ -19118,13 +19287,13 @@ async function terminalAgentDefaults(o) {
19118
19287
  const effort2 = o.effort ?? base2.effort;
19119
19288
  return base2.agent === "codex" ? { agent: "codex", model, effort: coerceCodexEffort(model, effort2) } : { agent: "claude", model, effort: effort2 };
19120
19289
  }
19121
- var STEP, DEFAULT_SETTING, RETIRED_MODELS;
19290
+ var STEP, DEFAULT_SETTING, normalizeClaudeModel, normalizeCodexModel, RETIRED_MODELS;
19122
19291
  var init_settings3 = __esm({
19123
19292
  "src/services/settings.ts"() {
19124
19293
  "use strict";
19125
19294
  init_db();
19126
19295
  init_src();
19127
- STEP = { model: "claude-sonnet-5", effort: "medium" };
19296
+ STEP = { model: "sonnet", effort: "medium" };
19128
19297
  DEFAULT_SETTING = {
19129
19298
  ...STEP,
19130
19299
  autoDefault: true,
@@ -19173,7 +19342,9 @@ var init_settings3 = __esm({
19173
19342
  builtinRuntimeDefaults: BUILTIN_RUNTIME_DEFAULTS
19174
19343
  // seed rekomendasi model/fase; user edit dipertahankan
19175
19344
  };
19176
- RETIRED_MODELS = { "claude-opus-4-8": "claude-opus-5" };
19345
+ normalizeClaudeModel = (m) => RETIRED_MODELS[m] ?? m;
19346
+ normalizeCodexModel = (m) => codexModel(m) ? m : RETIRED_CODEX_MODELS[m] ?? m;
19347
+ RETIRED_MODELS = { "claude-opus-4-8": "opus" };
19177
19348
  }
19178
19349
  });
19179
19350
 
@@ -20699,13 +20870,13 @@ function inferLegacyStates(raw) {
20699
20870
  }
20700
20871
  }
20701
20872
  }
20702
- const claudeModel = !has("model") || object2.model === LEGACY_CLAUDE_RUNTIME_DEFAULTS.model ? "seeded" : "user";
20873
+ const claudeModel2 = !has("model") || object2.model === LEGACY_CLAUDE_RUNTIME_DEFAULTS.model ? "seeded" : "user";
20703
20874
  const claudeEffort = !has("effort") || object2.effort === LEGACY_CLAUDE_RUNTIME_DEFAULTS.effort ? "seeded" : "user";
20704
20875
  const rawCodexObject = isRecord(rawCodex) ? rawCodex : {};
20705
20876
  const codexModel2 = !has("codex") || !Object.prototype.hasOwnProperty.call(rawCodexObject, "model") || rawCodexObject.model === LEGACY_CODEX_RUNTIME_DEFAULTS.model ? "seeded" : "user";
20706
20877
  const codexEffort = !has("codex") || !Object.prototype.hasOwnProperty.call(rawCodexObject, "effort") || rawCodexObject.effort === LEGACY_CODEX_RUNTIME_DEFAULTS.effort ? "seeded" : "user";
20707
20878
  return {
20708
- claude: { model: claudeModel, effort: claudeEffort },
20879
+ claude: { model: claudeModel2, effort: claudeEffort },
20709
20880
  codex: { model: codexModel2, effort: codexEffort },
20710
20881
  orchestration
20711
20882
  };
@@ -21578,7 +21749,7 @@ var init_engine_command = __esm({
21578
21749
  init_src();
21579
21750
  AGENT_LABEL = { claude: "Claude Code", codex: "Codex CLI" };
21580
21751
  TELEGRAM_CONTROL_KIND = "gateway-control";
21581
- claudeModels = () => MODELS.map((m) => m.id);
21752
+ claudeModels = () => [...new Set(MODELS.flatMap((m) => m.resolved ? [m.id, m.resolved] : [m.id]))];
21582
21753
  codexModels = () => CODEX_MODELS.map((m) => m.id);
21583
21754
  effortsFor = (t) => t.agent === "codex" ? codexEfforts(t.model) : claudeEfforts(t.model);
21584
21755
  usage = [
@@ -24246,7 +24417,7 @@ var require_sender = __commonJS({
24246
24417
  */
24247
24418
  static frame(data, options2) {
24248
24419
  let mask;
24249
- let merge2 = false;
24420
+ let merge = false;
24250
24421
  let offset = 2;
24251
24422
  let skipMasking = false;
24252
24423
  if (options2.mask) {
@@ -24279,7 +24450,7 @@ var require_sender = __commonJS({
24279
24450
  }
24280
24451
  } else {
24281
24452
  dataLength = data.length;
24282
- merge2 = options2.mask && options2.readOnly && !skipMasking;
24453
+ merge = options2.mask && options2.readOnly && !skipMasking;
24283
24454
  }
24284
24455
  let payloadLength = dataLength;
24285
24456
  if (dataLength >= 65536) {
@@ -24289,7 +24460,7 @@ var require_sender = __commonJS({
24289
24460
  offset += 2;
24290
24461
  payloadLength = 126;
24291
24462
  }
24292
- const target3 = Buffer.allocUnsafe(merge2 ? dataLength + offset : offset);
24463
+ const target3 = Buffer.allocUnsafe(merge ? dataLength + offset : offset);
24293
24464
  target3[0] = options2.fin ? options2.opcode | 128 : options2.opcode;
24294
24465
  if (options2.rsv1) target3[0] |= 64;
24295
24466
  target3[1] = payloadLength;
@@ -24306,7 +24477,7 @@ var require_sender = __commonJS({
24306
24477
  target3[offset - 2] = mask[2];
24307
24478
  target3[offset - 1] = mask[3];
24308
24479
  if (skipMasking) return [target3, data];
24309
- if (merge2) {
24480
+ if (merge) {
24310
24481
  applyMask(data, mask, target3, offset, dataLength);
24311
24482
  return [target3];
24312
24483
  }
@@ -27501,7 +27672,7 @@ function recommendedModel(row, runtime) {
27501
27672
  if (!builtin) return null;
27502
27673
  const model = builtin.models[runtime];
27503
27674
  if (runtime === "claude" && (model === "haiku" || model === "sonnet")) return model;
27504
- return modelsForRuntime(runtime).some((entry) => entry.id === model) ? model : null;
27675
+ return modelKnownForRuntime(runtime, model) ? model : null;
27505
27676
  }
27506
27677
  function toRuntimeDef(row, runtime) {
27507
27678
  return { ...toDef(row), model: recommendedModel(row, runtime) };
@@ -41231,6 +41402,32 @@ async function reconcileHistory(liveSessionIds) {
41231
41402
  }
41232
41403
  return closed;
41233
41404
  }
41405
+ async function reconciledSpecIdsSince(cutoff) {
41406
+ const rows = await prisma.sessionHistory.findMany({
41407
+ where: { endedReason: RECONCILED, reconciledAt: { gte: cutoff }, specId: { not: null } },
41408
+ select: { specId: true },
41409
+ distinct: ["specId"]
41410
+ });
41411
+ return rows.map((r) => r.specId);
41412
+ }
41413
+ async function reconciledRuntimesSince(cutoff) {
41414
+ const rows = await prisma.sessionHistory.findMany({
41415
+ where: { endedReason: RECONCILED, reconciledAt: { gte: cutoff }, specId: { not: null } },
41416
+ select: { specId: true, agent: true, model: true, effort: true },
41417
+ orderBy: { startedAt: "desc" }
41418
+ });
41419
+ const out4 = /* @__PURE__ */ new Map();
41420
+ for (const r of rows) {
41421
+ if (out4.has(r.specId)) continue;
41422
+ out4.set(r.specId, {
41423
+ // `agent` kolom String — nilai asing jatuh ke claude, cermin pembacaan @hanoman_agent (pty.ts).
41424
+ agent: r.agent === "codex" ? "codex" : "claude",
41425
+ ...r.model ? { model: r.model } : {},
41426
+ ...r.effort ? { effort: r.effort } : {}
41427
+ });
41428
+ }
41429
+ return out4;
41430
+ }
41234
41431
  function installSessionHistory() {
41235
41432
  registerSessionHooks({
41236
41433
  onBirth: (b) => {
@@ -41897,13 +42094,15 @@ init_pty();
41897
42094
  // src/services/orchestration.ts
41898
42095
  init_src();
41899
42096
  init_pty();
42097
+ init_settings3();
41900
42098
  function sessionPhasePlan(setting, flow2, agent, orchestrator, phaseOverrides) {
41901
42099
  return resolvePhasePlan({
41902
42100
  flow: flow2,
41903
42101
  runtime: agent,
41904
42102
  orchestration: setting.orchestration,
41905
42103
  orchestrator,
41906
- phaseOverrides,
42104
+ // S6 · override transient dinormalisasi sama seperti sel Setting (model pensiun, koersi codex).
42105
+ phaseOverrides: normalizePhaseOverrides(agent, phaseOverrides),
41907
42106
  nativeAgents: nativeAgentsAvailable(agent)
41908
42107
  });
41909
42108
  }
@@ -41938,7 +42137,7 @@ function buildResumeCtx(repoDir, id2, flow2, worktreeKept) {
41938
42137
  }
41939
42138
  async function startSpecSession(spec, opts) {
41940
42139
  const id2 = sessionIdForSpec(spec.id);
41941
- return withSessionAdmission({ id: id2, force: opts.force }, async () => {
42140
+ return withSessionAdmission({ id: id2, force: opts.force || opts.bypassCapacity }, async () => {
41942
42141
  const pane = await getSessionAsync(id2);
41943
42142
  if (!pane) {
41944
42143
  const view13 = await presenceView();
@@ -42053,7 +42252,9 @@ async function startSpecSession(spec, opts) {
42053
42252
  method,
42054
42253
  verifyScope,
42055
42254
  context: isGoalFlow ? goalContext(brief) : specContext(brief),
42056
- fromAudit: fromAuditOf(spec.payload)
42255
+ fromAudit: fromAuditOf(spec.payload),
42256
+ // S2 · SPEC-394 · subagent tak melihat `resumeClause` orchestrator — ia dapat catatannya sendiri.
42257
+ ...resumeCtx ? { resume: resumeCtx } : {}
42057
42258
  }) : [];
42058
42259
  const scopeEnv = { HANOMAN_BASE_SHA: baseSha, HANOMAN_VERIFY_SCOPE: verifyScope };
42059
42260
  const s2 = createSession(spec.projectId, worktree, {
@@ -42069,6 +42270,9 @@ async function startSpecSession(spec, opts) {
42069
42270
  prompt,
42070
42271
  legacyPrompt,
42071
42272
  phaseAgents,
42273
+ // S3 · SPEC-172 · berkas fase continue masih memuat `Execute done` run lama; fase yang DIKERJAKAN
42274
+ // ULANG sesi ini tak boleh dihitung `doneAtBirth`, supaya ⚠ bukti tetap bisa menyala.
42275
+ ...isContinue && plan ? { rerunPhases: plan.phases.map((p3) => p3.phase) } : {},
42072
42276
  env: scopeEnv
42073
42277
  });
42074
42278
  return resume ? { id: s2.id, resumed: true } : { id: s2.id };
@@ -42526,6 +42730,7 @@ import { isAbsolute as isAbsolute8, relative as relative4, resolve as resolve22
42526
42730
  // src/services/session-event-relay.ts
42527
42731
  init_session_event_token();
42528
42732
  init_session_event_spool();
42733
+ init_ingress_policy();
42529
42734
  import { constants as constants3 } from "node:fs";
42530
42735
  import { mkdir as mkdir6, open as open3, readdir as readdir7, rm as rm6 } from "node:fs/promises";
42531
42736
  import { join as join25 } from "node:path";
@@ -42544,18 +42749,22 @@ function sessionEventRelayStatus(root = sessionEventSpoolRoot()) {
42544
42749
  droppedEvents: 0
42545
42750
  } };
42546
42751
  }
42547
- async function drainSessionEventSpool(app3, root = sessionEventSpoolRoot()) {
42752
+ async function drainSessionEventSpool(app3, root = sessionEventSpoolRoot(), host2 = null) {
42548
42753
  const previous = sessionEventRelayStatus(root);
42549
- const current = { retries: 0, dropped: 0 };
42754
+ const current = { retries: 0, dropped: 0, rejected: /* @__PURE__ */ new Map() };
42550
42755
  let delivered = 0;
42551
42756
  let failed = false;
42552
42757
  try {
42553
- delivered = await drainSpool(app3, root, current);
42758
+ delivered = await drainSpool(app3, root, current, host2);
42554
42759
  return delivered;
42555
42760
  } catch (error) {
42556
42761
  failed = true;
42557
42762
  throw error;
42558
42763
  } finally {
42764
+ if (current.rejected.size) {
42765
+ const summary = [...current.rejected].map(([code, n2]) => `${n2}\xD7 ${code}`).join(", ");
42766
+ console.warn(`session event relay: route menolak event (${summary}) \u2014 event dibuang`);
42767
+ }
42559
42768
  const now = (/* @__PURE__ */ new Date()).toISOString();
42560
42769
  observations.set(root, {
42561
42770
  state: failed || current.retries > 0 ? "degraded" : "ready",
@@ -42568,7 +42777,7 @@ async function drainSessionEventSpool(app3, root = sessionEventSpoolRoot()) {
42568
42777
  });
42569
42778
  }
42570
42779
  }
42571
- async function drainSpool(app3, root, observation) {
42780
+ async function drainSpool(app3, root, observation, host2) {
42572
42781
  await mkdir6(root, { recursive: true, mode: 448 });
42573
42782
  let delivered = 0;
42574
42783
  let examined = 0;
@@ -42582,8 +42791,10 @@ async function drainSpool(app3, root, observation) {
42582
42791
  } catch {
42583
42792
  continue;
42584
42793
  }
42794
+ entries3.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
42585
42795
  for (const entry of entries3) {
42586
42796
  if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
42797
+ const eventAt = /^(\d{1,15})-/.exec(entry.name)?.[1];
42587
42798
  if (++examined > MAX_FILES_PER_DRAIN) return delivered;
42588
42799
  const path = join25(dir2, entry.name);
42589
42800
  let payload;
@@ -42615,7 +42826,11 @@ async function drainSpool(app3, root, observation) {
42615
42826
  url: "/api/session-events",
42616
42827
  headers: {
42617
42828
  authorization: `Bearer ${sessionEventToken(session.name)}`,
42618
- "x-hanoman-session": session.name
42829
+ "x-hanoman-session": session.name,
42830
+ ...host2 ? { host: host2 } : {},
42831
+ // S1 · waktu KEJADIAN event (bukan waktu drain) — route memakainya untuk membedakan
42832
+ // replay dari lanjutan relay subagent yang sama.
42833
+ ...eventAt ? { "x-hanoman-event-at": eventAt } : {}
42619
42834
  },
42620
42835
  payload
42621
42836
  });
@@ -42626,7 +42841,10 @@ async function drainSpool(app3, root, observation) {
42626
42841
  await rm6(path, { force: true }).catch(() => {
42627
42842
  });
42628
42843
  if (response.statusCode >= 200 && response.statusCode < 300) delivered++;
42629
- else observation.dropped++;
42844
+ else {
42845
+ observation.dropped++;
42846
+ observation.rejected.set(response.statusCode, (observation.rejected.get(response.statusCode) ?? 0) + 1);
42847
+ }
42630
42848
  } catch {
42631
42849
  observation.retries++;
42632
42850
  }
@@ -42636,12 +42854,13 @@ async function drainSpool(app3, root, observation) {
42636
42854
  }
42637
42855
  function startSessionEventRelay(app3, options2 = {}) {
42638
42856
  const root = options2.root ?? sessionEventSpoolRoot();
42857
+ const host2 = controlHost(loadIngressPolicy(options2.env ?? process.env));
42639
42858
  let running = false;
42640
42859
  const tick6 = async () => {
42641
42860
  if (running) return;
42642
42861
  running = true;
42643
42862
  try {
42644
- await drainSessionEventSpool({ inject: async (request) => app3.inject(request) }, root);
42863
+ await drainSessionEventSpool({ inject: async (request) => app3.inject(request) }, root, host2);
42645
42864
  } catch (error) {
42646
42865
  console.error("session event relay gagal:", error);
42647
42866
  } finally {
@@ -42754,6 +42973,15 @@ async function startAgentInvocation(input, io = {}) {
42754
42973
  runtimeInvocationId: input.runtimeInvocationId
42755
42974
  } }
42756
42975
  });
42976
+ if (existing?.endedAt && startedAt.getTime() > existing.endedAt.getTime()) {
42977
+ const row2 = await prisma.agentInvocation.update({
42978
+ where: { id: existing.id },
42979
+ data: { status: "running", endedAt: null, durationMs: null }
42980
+ });
42981
+ const before3 = snapshot2(input.cwd, io.gitStatus);
42982
+ if (before3 !== null) snapshotHashes.set(keyOf(input), before3);
42983
+ return { row: row2, duplicate: false };
42984
+ }
42757
42985
  const row = await prisma.agentInvocation.upsert({
42758
42986
  where: { sessionId_runtimeInvocationId: {
42759
42987
  sessionId: input.sessionId,
@@ -42804,7 +43032,8 @@ async function stopAgentInvocation(input, io = {}) {
42804
43032
  ...usage2,
42805
43033
  resultExcerpt: cleanResult === null ? null : utf8Prefix(cleanResult, MAX_EXCERPT_BYTES),
42806
43034
  resultHash: cleanResult === null ? null : hash3(cleanResult),
42807
- workspaceChanged: before2 !== void 0 && after !== null && before2 !== after,
43035
+ // S1 · putaran relay (baris dibuka ulang) tak menghapus perubahan workspace putaran sebelumnya.
43036
+ workspaceChanged: (existing?.workspaceChanged ?? false) || before2 !== void 0 && after !== null && before2 !== after,
42808
43037
  // ADR-0164 · effort yang BENAR-BENAR dipakai runtime menang atas nilai start-time.
42809
43038
  ...input.effort ? { effort: input.effort } : {}
42810
43039
  };
@@ -44394,6 +44623,7 @@ var ensureWorktree = (repoDir, wt, branchFrom) => {
44394
44623
  var resumeNote = (reused) => reused ? `
44395
44624
 
44396
44625
  ${RESUMED_WORKTREE_NOTE}` : "";
44626
+ 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}`) } } : {};
44397
44627
  async function terminal_default(app3, opts) {
44398
44628
  app3.get("/terminal/sessions", async () => listSessions());
44399
44629
  app3.post("/terminal/sessions", async (req, reply) => {
@@ -44488,7 +44718,12 @@ async function terminal_default(app3, opts) {
44488
44718
  decisionFile: decisionFilePath(repoDir, id2),
44489
44719
  prompt: plan ? startProjectPrompt("reverse", brief, "reverse-docs", plan) + resumeNote(reused) : legacyPrompt,
44490
44720
  legacyPrompt,
44491
- phaseAgents: plan ? buildPhaseAgents(plan, { flow: "reverse", method: PROJECT_METHOD, context: projectContext(brief) }) : []
44721
+ phaseAgents: plan ? buildPhaseAgents(plan, {
44722
+ flow: "reverse",
44723
+ method: PROJECT_METHOD,
44724
+ context: projectContext(brief),
44725
+ ...phaseResume(reused, repoDir, id2, "reverse")
44726
+ }) : []
44492
44727
  });
44493
44728
  return { code: 201, body: { id: s3.id } };
44494
44729
  }, (pane) => ({ code: 201, body: { id: pane.id } }));
@@ -44520,7 +44755,12 @@ async function terminal_default(app3, opts) {
44520
44755
  decisionFile: decisionFilePath(repoDir, id2),
44521
44756
  prompt: plan ? startScaffoldPrompt(brief, "scaffold-docs", plan) + resumeNote(reused) : legacyPrompt,
44522
44757
  legacyPrompt,
44523
- phaseAgents: plan ? buildPhaseAgents(plan, { flow: "scaffold", method: PROJECT_METHOD, context: scaffoldContext(brief) }) : []
44758
+ phaseAgents: plan ? buildPhaseAgents(plan, {
44759
+ flow: "scaffold",
44760
+ method: PROJECT_METHOD,
44761
+ context: scaffoldContext(brief),
44762
+ ...phaseResume(reused, repoDir, id2, "scaffold")
44763
+ }) : []
44524
44764
  });
44525
44765
  return { code: 201, body: { id: s3.id } };
44526
44766
  }, (pane) => ({ code: 201, body: { id: pane.id } }));
@@ -44561,7 +44801,8 @@ async function terminal_default(app3, opts) {
44561
44801
  flow: "prd",
44562
44802
  method: PROJECT_METHOD,
44563
44803
  context: prdContext(project_, brief, audit),
44564
- prd: { slug }
44804
+ prd: { slug },
44805
+ ...phaseResume(reused, repoDir, id2, "prd")
44565
44806
  }) : []
44566
44807
  });
44567
44808
  return { code: 201, body: { id: s3.id } };
@@ -44607,7 +44848,8 @@ async function terminal_default(app3, opts) {
44607
44848
  flow: "breakdown",
44608
44849
  method: PROJECT_METHOD,
44609
44850
  context: breakdownContext(project_, prd),
44610
- prd: { slug, title }
44851
+ prd: { slug, title },
44852
+ ...phaseResume(reused, repoDir, id2, "breakdown")
44611
44853
  }) : []
44612
44854
  });
44613
44855
  return { code: 201, body: { id: s3.id } };
@@ -45883,7 +46125,7 @@ async function vps_default(app3) {
45883
46125
  init_config2();
45884
46126
  import { execFileSync as execFileSync5 } from "node:child_process";
45885
46127
  import { readFileSync as readFileSync18 } from "node:fs";
45886
- import { homedir as homedir8 } from "node:os";
46128
+ import { homedir as homedir8, userInfo } from "node:os";
45887
46129
  import { join as join31 } from "node:path";
45888
46130
  var USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
45889
46131
  var TTL_MS2 = 3e4;
@@ -45899,19 +46141,30 @@ var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
45899
46141
  function credsFile() {
45900
46142
  return join31(effectiveStr("CLAUDE_CONFIG_DIR") ?? join31(homedir8(), ".claude"), ".credentials.json");
45901
46143
  }
45902
- function readAccessToken() {
45903
- if (process.platform === "darwin" && !effectiveStr("CLAUDE_CONFIG_DIR")) {
46144
+ var securityRun = (args) => execFileSync5("security", args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
46145
+ var osAccount = () => {
46146
+ try {
46147
+ return userInfo().username || null;
46148
+ } catch {
46149
+ return null;
46150
+ }
46151
+ };
46152
+ function keychainAccessToken(run4 = securityRun, account = osAccount()) {
46153
+ const base2 = ["find-generic-password", "-s", "Claude Code-credentials"];
46154
+ for (const scope of account ? [["-a", account], []] : [[]]) {
45904
46155
  try {
45905
- const blob = execFileSync5(
45906
- "security",
45907
- ["find-generic-password", "-s", "Claude Code-credentials", "-w"],
45908
- { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }
45909
- );
45910
- const tok = JSON.parse(blob)?.claudeAiOauth?.accessToken;
45911
- if (tok) return tok;
46156
+ const tok = JSON.parse(run4([...base2, ...scope, "-w"]))?.claudeAiOauth?.accessToken;
46157
+ if (typeof tok === "string" && tok) return tok;
45912
46158
  } catch {
45913
46159
  }
45914
46160
  }
46161
+ return null;
46162
+ }
46163
+ function readAccessToken() {
46164
+ if (process.platform === "darwin" && !effectiveStr("CLAUDE_CONFIG_DIR")) {
46165
+ const tok = keychainAccessToken();
46166
+ if (tok) return tok;
46167
+ }
45915
46168
  try {
45916
46169
  return JSON.parse(readFileSync18(credsFile(), "utf8"))?.claudeAiOauth?.accessToken ?? null;
45917
46170
  } catch {
@@ -46117,19 +46370,25 @@ var claudeRow = external_exports.object({
46117
46370
  });
46118
46371
  function parseClaudeModels(raw) {
46119
46372
  const rows = external_exports.array(claudeRow).min(1).max(500).parse(raw);
46373
+ let preferred = null;
46120
46374
  const models2 = /* @__PURE__ */ new Map();
46121
46375
  for (const row of rows) {
46376
+ const resolved = row.resolvedModel ?? row.value;
46377
+ const head = row.description?.split(" \xB7 ")[0];
46122
46378
  const model = {
46123
- id: row.resolvedModel ?? row.value,
46124
- label: row.description?.split(" \xB7 ")[0] || row.displayName,
46379
+ id: row.value,
46380
+ label: row.value === "default" && head ? `${row.displayName} \xB7 ${head}` : head || row.displayName,
46381
+ resolved,
46125
46382
  ...row.supportedEffortLevels ? { efforts: sorted(row.supportedEffortLevels) } : {}
46126
46383
  };
46127
- if (!models2.has(model.id)) models2.set(model.id, model);
46128
- if (row.value !== model.id && row.value !== "default") {
46129
- models2.set(row.value, { ...model, id: row.value, label: row.displayName });
46384
+ if (row.value === "default") {
46385
+ preferred ??= model;
46386
+ continue;
46130
46387
  }
46388
+ const existing = models2.get(resolved);
46389
+ if (!existing || existing.id === resolved && row.value !== resolved) models2.set(resolved, model);
46131
46390
  }
46132
- return [...models2.values()];
46391
+ return preferred ? [preferred, ...models2.values()] : [...models2.values()];
46133
46392
  }
46134
46393
  var codexRow = external_exports.object({
46135
46394
  slug: id,
@@ -46163,7 +46422,7 @@ function parseCachedCatalog(raw) {
46163
46422
  error: external_exports.string().nullable()
46164
46423
  });
46165
46424
  return external_exports.object({
46166
- claude: external_exports.array(external_exports.object({ id, label, efforts: efforts.optional() })).min(1).max(1e3),
46425
+ claude: external_exports.array(external_exports.object({ id, label, resolved: id.optional(), efforts: efforts.optional() })).min(1).max(1e3),
46167
46426
  codex: external_exports.array(external_exports.object({ id, label, efforts: efforts.min(1), fallback: id, minClient: external_exports.string() })).min(1).max(1e3),
46168
46427
  providers: external_exports.object({ claude: status2, codex: status2 })
46169
46428
  }).parse(raw);
@@ -46289,7 +46548,6 @@ async function probeModelCatalog(agent) {
46289
46548
  }
46290
46549
 
46291
46550
  // src/services/model-catalog.ts
46292
- var merge = (old, fresh) => [...new Map([...old, ...fresh].map((m) => [m.id, m])).values()];
46293
46551
  function createModelCatalogService(deps) {
46294
46552
  let state = bundledModelCatalog();
46295
46553
  let initialized = false;
@@ -46316,7 +46574,7 @@ function createModelCatalogService(deps) {
46316
46574
  try {
46317
46575
  const models2 = await deps.probe(agent);
46318
46576
  if (!models2.length) throw new Error("empty catalog");
46319
- state = agent === "claude" ? { ...state, claude: merge(bundledModelCatalog().claude, models2) } : { ...state, codex: merge(bundledModelCatalog().codex, models2) };
46577
+ state = agent === "claude" ? { ...state, claude: models2 } : { ...state, codex: models2 };
46320
46578
  state = { ...state, providers: { ...state.providers, [agent]: {
46321
46579
  source: "cli",
46322
46580
  checkedAt,
@@ -48179,6 +48437,13 @@ init_pty();
48179
48437
  var bearer = (h2) => /^Bearer (.+)$/.exec(h2 ?? "")?.[1] ?? "";
48180
48438
  var recordOf = (value) => value && typeof value === "object" ? value : null;
48181
48439
  var boundedString = (value, max) => typeof value === "string" && value.length > 0 && value.length <= max ? value : void 0;
48440
+ var EVENT_AT_PAST_MS = 7 * 24 * 60 * 6e4;
48441
+ var EVENT_AT_FUTURE_MS = 6e4;
48442
+ var eventAtOf = (header2, now = Date.now()) => {
48443
+ if (typeof header2 !== "string" || !/^\d{1,15}$/.test(header2)) return void 0;
48444
+ const ms = Number(header2);
48445
+ return ms >= now - EVENT_AT_PAST_MS && ms <= now + EVENT_AT_FUTURE_MS ? new Date(ms) : void 0;
48446
+ };
48182
48447
  async function session_events_default(app3) {
48183
48448
  app3.post("/session-events", async (req, reply) => {
48184
48449
  const sessionId2 = String(req.headers["x-hanoman-session"] ?? "");
@@ -48211,8 +48476,10 @@ async function session_events_default(app3) {
48211
48476
  ...meta.effort ? { effort: meta.effort } : {},
48212
48477
  cwd: s2.cwd
48213
48478
  };
48214
- const outcome = lifecycle === "SubagentStart" ? await startAgentInvocation(identity) : await stopAgentInvocation({
48479
+ const eventAt = eventAtOf(req.headers["x-hanoman-event-at"]);
48480
+ const outcome = lifecycle === "SubagentStart" ? await startAgentInvocation({ ...identity, ...eventAt ? { startedAt: eventAt } : {} }) : await stopAgentInvocation({
48215
48481
  ...identity,
48482
+ ...eventAt ? { endedAt: eventAt } : {},
48216
48483
  status: body.status === "interrupted" ? "interrupted" : "completed",
48217
48484
  result: boundedString(body.last_assistant_message ?? body.result, 1e6),
48218
48485
  transcriptPath: boundedString(
@@ -49534,7 +49801,7 @@ function toolsProblem(tools, catalogIds) {
49534
49801
  }
49535
49802
  function modelProblem(model, runtime) {
49536
49803
  if (!model) return null;
49537
- const ok2 = modelsForRuntime(runtime).some((m) => m.id === model);
49804
+ const ok2 = modelKnownForRuntime(runtime, model);
49538
49805
  return ok2 ? null : { error: "model tak dikenal untuk runtime ini", model, runtime };
49539
49806
  }
49540
49807
  function effortProblem(effort2, runtime, model) {
@@ -53146,6 +53413,41 @@ function registerTriaseSource() {
53146
53413
  registerSchedulerSource({ id: "triase", check: checkTriase });
53147
53414
  }
53148
53415
 
53416
+ // src/services/session-boot-resume.ts
53417
+ init_src();
53418
+ init_db();
53419
+ init_notifications2();
53420
+ var prodDeps2 = {
53421
+ // ADR-0169 · `bypassCapacity: true` — SATU-SATUNYA jalur yang melewati cap ADR-0161; gerbang
53422
+ // dependency ADR-0093 TETAP berlaku (tak diberi `force`). Keputusan sadar risiko: mesin 8 GB
53423
+ // operator sudah pernah kernel panic akibat sesi paralel berlebih (memori
53424
+ // mac-mini-8gb-panic-agen-paralel) — operator memilih "semua kembali" di atas throttle.
53425
+ startSpec: (spec, runtime) => startSpecSession(spec, {
53426
+ flow: flowForSource(spec.source),
53427
+ bypassCapacity: true,
53428
+ ...runtime ?? {}
53429
+ }),
53430
+ recordFail: recordFailure
53431
+ };
53432
+ async function resumeReconciledSessions(cutoff, deps = prodDeps2) {
53433
+ const report = { resumed: [], failed: [] };
53434
+ const specIds = await reconciledSpecIdsSince(cutoff);
53435
+ if (!specIds.length) return report;
53436
+ const specs = await prisma.spec.findMany({ where: { id: { in: specIds }, stage: { not: "done" } } });
53437
+ const runtimes = await reconciledRuntimesSince(cutoff);
53438
+ for (const spec of specs) {
53439
+ try {
53440
+ await deps.startSpec(spec, runtimes.get(spec.id));
53441
+ report.resumed.push(spec.id);
53442
+ } catch (e) {
53443
+ const reason = e instanceof Error ? e.message : String(e);
53444
+ await deps.recordFail(spec.id, spec.title, spec.projectId, `gagal dilanjutkan otomatis \u2014 ${reason}`);
53445
+ report.failed.push(spec.id);
53446
+ }
53447
+ }
53448
+ return report;
53449
+ }
53450
+
53149
53451
  // src/server.ts
53150
53452
  init_event_log();
53151
53453
  init_settings3();
@@ -53905,11 +54207,19 @@ bootstrapReady.then(async () => {
53905
54207
  if (bootSetting.logShipping.server) installConsoleTap();
53906
54208
  try {
53907
54209
  const liveIds = listSessions().map((s2) => s2.id);
54210
+ const reconcileCutoff = /* @__PURE__ */ new Date();
53908
54211
  void reconcileHistory(liveIds).then(async (n2) => {
53909
54212
  if (n2) console.log(`riwayat sesi: ${n2} baris berjalan direkonsiliasi`);
53910
54213
  for (const row of await detectOrphanWorktrees()) {
53911
54214
  console.log(`worktree yatim: ${row.projectId} \u2014 ${row.count} menunggu konfirmasi di tab Worktrees`);
53912
54215
  }
54216
+ if (n2) {
54217
+ const { resumed, failed } = await resumeReconciledSessions(reconcileCutoff);
54218
+ if (resumed.length)
54219
+ console.log(`auto-resume: ${resumed.length} sesi dilanjutkan otomatis (${resumed.join(", ")})`);
54220
+ if (failed.length)
54221
+ console.log(`auto-resume: ${failed.length} sesi gagal dilanjutkan otomatis, lihat notifikasi (${failed.join(", ")})`);
54222
+ }
53913
54223
  }).catch((e) => console.error("rekonsiliasi riwayat sesi:", e));
53914
54224
  void reconcileAgentInvocations(liveIds).then((n2) => {
53915
54225
  if (n2) console.log(`custom agent: ${n2} invocation ditandai abandoned`);