hanoman 0.9.36 → 0.9.38

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.
Files changed (26) hide show
  1. package/dist/build-info.json +3 -3
  2. package/dist/cli.js +88 -11
  3. package/dist/server.js +712 -407
  4. package/docs/agent-integration.md +9 -2
  5. package/package.json +1 -1
  6. package/web/assets/{DalangHanomanScreen-NmpJIcg6.js → DalangHanomanScreen-wpAO0xvb.js} +1 -1
  7. package/web/assets/{DocPreviewModal-DK597e3s.js → DocPreviewModal-CbhkDUJ2.js} +1 -1
  8. package/web/assets/{DocsWorkspace-BQX-HlzW.js → DocsWorkspace-CcB0LGFV.js} +1 -1
  9. package/web/assets/{IdeScreen-LeUPaFsS.js → IdeScreen-DI4v3ZkU.js} +1 -1
  10. package/web/assets/{LeadScreen-BVCKv46y.js → LeadScreen-BdS_xX_f.js} +1 -1
  11. package/web/assets/{ReviewScreen-C6NcJChq.js → ReviewScreen-Ddw5U2sM.js} +1 -1
  12. package/web/assets/{SchedulerScreen-2AH0R3TJ.js → SchedulerScreen-D6LGVrhM.js} +1 -1
  13. package/web/assets/{SettingsScreen-B4SVzh9m.js → SettingsScreen-CFNcg1T8.js} +1 -1
  14. package/web/assets/{TeamScreen-6XEfuZUm.js → TeamScreen-BkxPDI5P.js} +1 -1
  15. package/web/assets/TerminalScreen-CEQFK9Rc.js +2 -0
  16. package/web/assets/{TriageScreen-BYJxyCar.js → TriageScreen-B-HdZsEt.js} +1 -1
  17. package/web/assets/{VpsScreen-Df37dgKx.js → VpsScreen-CWp29NL_.js} +2 -2
  18. package/web/assets/{fields-C94HI0V8.js → fields-BY7SYerd.js} +1 -1
  19. package/web/assets/{index-C3KwOJxI.css → index-B4b6i2tH.css} +1 -1
  20. package/web/assets/index-C4Py9MJV.js +1032 -0
  21. package/web/assets/{live-5ezoV9Fs.js → live-CBa_7-hn.js} +1 -1
  22. package/web/assets/{sched-lead-shared-BpZu6N2A.js → sched-lead-shared-CQIoYIzG.js} +1 -1
  23. package/web/assets/{settings-rebase-DaTGgcuO.js → settings-rebase-C3611gs8.js} +1 -1
  24. package/web/index.html +2 -2
  25. package/web/assets/TerminalScreen-DubD6vT-.js +0 -2
  26. package/web/assets/index-BeFSh2Ba.js +0 -1012
package/dist/server.js CHANGED
@@ -4273,12 +4273,13 @@ function stampSpecMethod(payload, methodId) {
4273
4273
  if (typeof payload !== "object" || Array.isArray(payload)) return null;
4274
4274
  return { ...payload, method: methodId };
4275
4275
  }
4276
- var DEFAULT_METHOD, VERIFICATION_GATE, MATT_CLAUSE, AGENT_SKILLS_CLAUSE, METHODS, METHOD_IDS, uniq, PLAN_DIRS, SPEC_DIRS;
4276
+ var DEFAULT_METHOD, VERIFICATION_GATE, MOCKUP_SKILL, MATT_CLAUSE, AGENT_SKILLS_CLAUSE, METHODS, METHOD_IDS, uniq, PLAN_DIRS, SPEC_DIRS;
4277
4277
  var init_method_catalog = __esm({
4278
4278
  "../shared/src/method-catalog.ts"() {
4279
4279
  "use strict";
4280
4280
  DEFAULT_METHOD = "superpowers";
4281
4281
  VERIFICATION_GATE = "superpowers:verification-before-completion";
4282
+ MOCKUP_SKILL = "codex-image";
4282
4283
  MATT_CLAUSE = "Skill mattpocock yang kontraknya mewawancarai manusia (`/grill-me`, `/to-spec`, `/triage`) JANGAN dipakai: pakai primitif model-invoked-nya untuk menguji rancanganmu sendiri, bukan untuk mewawancarai siapa pun. Hal yang masih ambigu dan akan mempengaruhi hasil tetap JANGAN diputuskan sendiri \u2014 bila kamu agen fase, daftarkan di `Keputusan terbuka:` laporanmu; bila tidak, ajukan lewat jalur tanya di klausa otonomi prompt ini. `to-tickets` di fase Plan dipakai HANYA sebagai penghasil berkas plan berkotak `- [ ]` di `docs/matt/plans/`, BUKAN penerbit tiket \u2014 hanoman sendiri adalah issue tracker-nya, jadi jangan menulis ke tracker eksternal mana pun.";
4283
4284
  AGENT_SKILLS_CLAUSE = "Pakai skill Agent Skills langsung sesuai fase, bukan slash-command atau meta-router `using-agent-skills`. Kontrak fase, instruksi project/pengguna, dan jalur keputusan/review hanoman tetap berlaku: `spec-driven-development` hanya menyusun spec pada fase Spec, jangan menjalankan Plan atau Execute dari fase itu. Simpan spec di `docs/agent-skills/specs/` dan plan beserta SELURUH checklist task `- [ ]` di `docs/agent-skills/plans/`, dengan id backlog pada nama berkas; ini menggantikan default `SPEC.md`, `tasks/plan.md`, dan `tasks/todo.md` upstream. Jangan menimpa plan lain yang belum tuntas atau membuat tiket di tracker eksternal: hanoman adalah issue tracker-nya. Centang task di berkas plan yang sama setelah implementasi dan verifikasi. Hal ambigu yang mempengaruhi hasil mengikuti jalur tanya prompt ini; bila kamu agen fase, daftarkan di `Keputusan terbuka:` laporanmu.";
4284
4285
  METHODS = {
@@ -4293,6 +4294,7 @@ var init_method_catalog = __esm({
4293
4294
  phaseSkills: {
4294
4295
  Brainstorm: ["superpowers:brainstorming"],
4295
4296
  Audit: ["superpowers:systematic-debugging"],
4297
+ Mockup: [MOCKUP_SKILL],
4296
4298
  Plan: ["superpowers:writing-plans"],
4297
4299
  Execute: [
4298
4300
  "superpowers:executing-plans",
@@ -4338,6 +4340,7 @@ var init_method_catalog = __esm({
4338
4340
  phaseSkills: {
4339
4341
  Brainstorm: ["mattpocock-skills:grilling"],
4340
4342
  Audit: ["mattpocock-skills:diagnosing-bugs"],
4343
+ Mockup: [MOCKUP_SKILL],
4341
4344
  Plan: ["mattpocock-skills:to-tickets"],
4342
4345
  Execute: [
4343
4346
  "mattpocock-skills:implement",
@@ -4376,6 +4379,7 @@ var init_method_catalog = __esm({
4376
4379
  phaseSkills: {
4377
4380
  Brainstorm: ["agent-skills:idea-refine"],
4378
4381
  Spec: ["agent-skills:spec-driven-development"],
4382
+ Mockup: [MOCKUP_SKILL],
4379
4383
  Audit: [
4380
4384
  "agent-skills:debugging-and-error-recovery",
4381
4385
  "agent-skills:code-review-and-quality"
@@ -4419,7 +4423,7 @@ var init_method_catalog = __esm({
4419
4423
 
4420
4424
  // ../shared/src/method-status.ts
4421
4425
  function methodSkills(m) {
4422
- return [.../* @__PURE__ */ new Set([...Object.values(m.phaseSkills).flat(), ...m.exitSkills])];
4426
+ return [.../* @__PURE__ */ new Set([...Object.values(m.phaseSkills).flat(), ...m.exitSkills])].filter((id2) => !HANOMAN_PROVIDED_SKILLS.has(id2));
4423
4427
  }
4424
4428
  function methodStatus(m, agent, installed2) {
4425
4429
  const haveSkills = new Set(installed2.skills);
@@ -4437,10 +4441,12 @@ function methodStatus(m, agent, installed2) {
4437
4441
  install: [...m.install[agent]]
4438
4442
  };
4439
4443
  }
4440
- var ANTIGRAVITY_METHOD_UNAVAILABLE_REASON;
4444
+ var HANOMAN_PROVIDED_SKILLS, ANTIGRAVITY_METHOD_UNAVAILABLE_REASON;
4441
4445
  var init_method_status = __esm({
4442
4446
  "../shared/src/method-status.ts"() {
4443
4447
  "use strict";
4448
+ init_method_catalog();
4449
+ HANOMAN_PROVIDED_SKILLS = /* @__PURE__ */ new Set([MOCKUP_SKILL]);
4444
4450
  ANTIGRAVITY_METHOD_UNAVAILABLE_REASON = "Installer metode Antigravity belum terverifikasi dari upstream";
4445
4451
  }
4446
4452
  });
@@ -5174,7 +5180,7 @@ var init_logs = __esm({
5174
5180
  function phaseAgentName(phase) {
5175
5181
  return PHASE_AGENT_PREFIX + phase.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
5176
5182
  }
5177
- var ORCHESTRATION_FLOWS, FLOW_PHASES, PHASE_AGENT_PREFIX, isPhaseAgentName, PHASE_EVIDENCE_GRACE_MS, EXECUTE_MODE_FLOWS, REVIEWER_CELL, REVIEWER_FLOWS, REVIEWER_DEFAULTS, UNSKIPPABLE_PHASES;
5183
+ var ORCHESTRATION_FLOWS, FLOW_PHASES, PHASE_AGENT_PREFIX, isPhaseAgentName, PHASE_EVIDENCE_GRACE_MS, EXECUTE_MODE_FLOWS, REVIEWER_CELL, REVIEWER_FLOWS, REVIEWER_DEFAULTS, UNSKIPPABLE_PHASES, MOCKUP_PHASE, OPTIONAL_PHASES;
5178
5184
  var init_orchestration = __esm({
5179
5185
  "../shared/src/orchestration.ts"() {
5180
5186
  "use strict";
@@ -5191,8 +5197,10 @@ var init_orchestration = __esm({
5191
5197
  ];
5192
5198
  FLOW_PHASES = {
5193
5199
  // ADR-0230 · `Review` = fase resmi sesudah Execute (dulu langkah R di dalam Execute, ADR-0170 #4).
5194
- feature: ["Brainstorm", "Objective", "Spec", "Plan", "Execute", "Review"],
5195
- qa: ["Audit", "Spec", "Plan", "Execute", "Review"],
5200
+ // ADR-0233 · `Mockup` = fase opsional sesudah Spec: gambar UI disetujui sebelum Plan, dilewati agen
5201
+ // bila backlog tak menyentuh UI.
5202
+ feature: ["Brainstorm", "Objective", "Spec", "Mockup", "Plan", "Execute", "Review"],
5203
+ qa: ["Audit", "Spec", "Mockup", "Plan", "Execute", "Review"],
5196
5204
  scaffold: ["Brainstorm", "Objective", "Doc index"],
5197
5205
  reverse: ["Scan", "Docs teknis", "Wawancara", "Konvensi & index", "Serah terima"],
5198
5206
  prd: ["Brainstorm", "PRD"],
@@ -5224,6 +5232,8 @@ var init_orchestration = __esm({
5224
5232
  antigravity: { model: "inherit", effort: null }
5225
5233
  };
5226
5234
  UNSKIPPABLE_PHASES = /* @__PURE__ */ new Set([REVIEWER_CELL]);
5235
+ MOCKUP_PHASE = "Mockup";
5236
+ OPTIONAL_PHASES = /* @__PURE__ */ new Set([MOCKUP_PHASE]);
5227
5237
  }
5228
5238
  });
5229
5239
 
@@ -5234,7 +5244,7 @@ var init_runtime_defaults = __esm({
5234
5244
  "use strict";
5235
5245
  init_orchestration();
5236
5246
  init_zod();
5237
- RUNTIME_DEFAULTS_VERSION = "2026-10-25-v1";
5247
+ RUNTIME_DEFAULTS_VERSION = "2026-10-31-v1";
5238
5248
  zRuntimeDefaultState = external_exports.enum(["seeded", "user"]);
5239
5249
  zBuiltinRuntimeDefaults = external_exports.object({
5240
5250
  version: external_exports.string().default(RUNTIME_DEFAULTS_VERSION),
@@ -5290,6 +5300,7 @@ var init_runtime_defaults = __esm({
5290
5300
  Brainstorm: cell("sonnet", "medium"),
5291
5301
  Objective: cell("sonnet", "medium"),
5292
5302
  Spec: cell("opus", "high"),
5303
+ Mockup: cell("sonnet", "medium"),
5293
5304
  Plan: cell("opus", "high"),
5294
5305
  Execute: cell("sonnet", "high"),
5295
5306
  Review: cell("opus", "high")
@@ -5298,6 +5309,7 @@ var init_runtime_defaults = __esm({
5298
5309
  Brainstorm: cell("gpt-5.6-terra", "medium"),
5299
5310
  Objective: cell("gpt-5.6-terra", "medium"),
5300
5311
  Spec: cell("gpt-5.6-sol", "high"),
5312
+ Mockup: cell("gpt-5.6-terra", "medium"),
5301
5313
  Plan: cell("gpt-5.6-sol", "high"),
5302
5314
  Execute: cell("gpt-5.6-terra", "high"),
5303
5315
  Review: cell("gpt-5.6-sol", "high")
@@ -5307,6 +5319,7 @@ var init_runtime_defaults = __esm({
5307
5319
  {
5308
5320
  Audit: cell("opus", "high"),
5309
5321
  Spec: cell("opus", "high"),
5322
+ Mockup: cell("sonnet", "medium"),
5310
5323
  Plan: cell("sonnet", "high"),
5311
5324
  Execute: cell("sonnet", "high"),
5312
5325
  Review: cell("opus", "high")
@@ -5314,6 +5327,7 @@ var init_runtime_defaults = __esm({
5314
5327
  {
5315
5328
  Audit: cell("gpt-5.6-sol", "high"),
5316
5329
  Spec: cell("gpt-5.6-sol", "high"),
5330
+ Mockup: cell("gpt-5.6-terra", "medium"),
5317
5331
  Plan: cell("gpt-5.6-terra", "high"),
5318
5332
  Execute: cell("gpt-5.6-terra", "high"),
5319
5333
  Review: cell("gpt-5.6-sol", "high")
@@ -7699,7 +7713,7 @@ function subKey(topic, params) {
7699
7713
  for (const k of Object.keys(params).sort()) if (params[k] !== void 0) canon[k] = params[k];
7700
7714
  return `${topic}|${JSON.stringify(canon)}`;
7701
7715
  }
7702
- var zIssueDeviceToken, zSessionResult, zSessionHistory, zCreateProject, zProjectId, zRenameProject, zUpdateProject, zCreateSpec, zPatchSpec, zChangeSpecSource, zMarkSpecDone, zMarkSpecReviewed, zIntegrate, zSessionSummary, zProjectView, zSchedulerQueueItem, zSchedulerSourceView, zSchedulerSessionView, zSchedulerQueueCounts, zSchedulerState, zLeadDecisionView, zLeadFlowView, zLeadAnswer, zLeadProjectStatus, zLeadStatusView, zFlow, isGoalShapedFlow, zPrdBrief, zPrdDoc, zBreakdownItem, zBreakdownDoc, zBatchCreateSpec, zEscalationTarget, zEscalationPrefill, zAuditEscalation, zAuditEscalationView, zTerminalSession, zDocFileContent, zDocIndexCat, zDocIndex, HOST_RE, USER_RE, zCreateVps, zPatchVps, USER_ROLES, zUserRole, zLogin, zSignup, zSetup, zSetupApply, zChangePassword, zAdminPasswordReset, zCreateClientAccount, zUpdateClientAccount, zVpsCheck, zMarkNa, zMarkNaBulk, zAttest, zRemediate, PROVISION_PROFILES, COMPONENT_IDS, zProvision, UPDATE_RESTART_EXIT, CONFIG_RESTART_EXIT, MANUAL_RESTART_EXIT, EXIT_INTENT_FILE, zUpdateApplyBody, MAX_SUBS, zSubPage, zSubLimit, zTopicParams, zEventsClientMsg, zTicketView, zTicketAttachmentView, zSpecAttachmentView, zTicketDetail, zTicketEditInput, zHelpInfo, zPublicTicketStatus, zCronExpr, zCreateCron, zPatchCron, zSchedulerCron, zSchedulerCronRun;
7716
+ var zIssueDeviceToken, zSessionResult, zSessionHistory, zCreateProject, zProjectId, zRenameProject, zUpdateProject, zCreateSpec, zPatchSpec, zChangeSpecSource, zMarkSpecDone, zMarkSpecReviewed, zIntegrate, zSessionSummary, zProjectView, zSchedulerQueueItem, zSchedulerSourceView, zSchedulerSessionView, zSchedulerQueueCounts, zSchedulerState, zLeadDecisionView, zLeadFlowView, zLeadAnswer, zLeadProjectStatus, zLeadStatusView, zFlow, isGoalShapedFlow, zPrdBrief, zPrdDoc, zBreakdownItem, zBreakdownDoc, zBatchCreateSpec, zEscalationTarget, zEscalationPrefill, zAuditEscalation, zAuditEscalationView, zTerminalSession, zDocFileContent, zDocIndexCat, zDocIndex, HOST_RE, USER_RE, zCreateVps, zPatchVps, USER_ROLES, zUserRole, zLogin, zSignup, zSetup, zSetupApply, zChangePassword, zAdminPasswordReset, zCreateClientAccount, zUpdateClientAccount, zVpsCheck, zMarkNa, zMarkNaBulk, zAttest, zRemediate, PROVISION_PROFILES, COMPONENT_IDS, zProvision, UPDATE_RESTART_EXIT, CONFIG_RESTART_EXIT, MANUAL_RESTART_EXIT, EXIT_INTENT_FILE, zUpdateApplyBody, MAX_SUBS, zSubPage, zSubLimit, zTopicParams, zEventsClientMsg, zTicketView, zTicketAttachmentView, zSpecAttachmentView, zMockupItem, zSpecMockups, zTicketDetail, zTicketEditInput, zHelpInfo, zPublicTicketStatus, zCronExpr, zCreateCron, zPatchCron, zSchedulerCron, zSchedulerCronRun;
7703
7717
  var init_dto = __esm({
7704
7718
  "../shared/src/dto.ts"() {
7705
7719
  "use strict";
@@ -8115,7 +8129,13 @@ var init_dto = __esm({
8115
8129
  agent: zAgent.optional(),
8116
8130
  model: external_exports.string().optional(),
8117
8131
  effort: external_exports.string().optional(),
8118
- force: external_exports.boolean().optional()
8132
+ force: external_exports.boolean().optional(),
8133
+ // ADR-0232 · opsional: `worktree:true` → sesi lahir di `.worktrees/<id>` pada branch baru
8134
+ // `term/<id>` dari `branchFrom` (kosong → HEAD repo), alih-alih langsung di repoDir. Tanpa
8135
+ // keduanya body-nya byte-identik dengan sebelumnya. `branchFrom` tanpa `worktree` ditolak di
8136
+ // route — ia tak berarti apa-apa di repoDir dan diam-diam mengabaikannya menyesatkan.
8137
+ worktree: external_exports.boolean().optional(),
8138
+ branchFrom: external_exports.string().min(1).max(255).optional()
8119
8139
  }),
8120
8140
  // SPEC-252 · ADR-0061 — model & effort per SESI: override opsional saat Start; kosong → global.
8121
8141
  // SPEC-332 · ADR-0073 — mode goal per SESI: `goal` undefined → ikut Setting.goal.enabled,
@@ -8317,6 +8337,12 @@ var init_dto = __esm({
8317
8337
  size: external_exports.number().int(),
8318
8338
  createdAt: external_exports.string()
8319
8339
  });
8340
+ zMockupItem = external_exports.object({ name: external_exports.string(), size: external_exports.number().int(), modifiedAt: external_exports.string() });
8341
+ zSpecMockups = external_exports.object({
8342
+ source: external_exports.enum(["worktree", "repo"]).nullable(),
8343
+ dir: external_exports.string().nullable(),
8344
+ items: external_exports.array(zMockupItem)
8345
+ });
8320
8346
  zTicketDetail = zTicketView.extend({
8321
8347
  detail: external_exports.string(),
8322
8348
  attachments: external_exports.array(zTicketAttachmentView),
@@ -8467,6 +8493,9 @@ var init_api = __esm({
8467
8493
  // SPEC-843 · ADR-0124 · lampiran backlog item (multi-berkas per request).
8468
8494
  specAttachments: (id2) => `${API}/specs/${id2}/attachments`,
8469
8495
  specAttachment: (id2, attId) => `${API}/specs/${id2}/attachments/${attId}`,
8496
+ // ADR-0233 · mockup UI backlog (docs/mockups/<spec-id>/).
8497
+ specMockups: (id2) => `${API}/specs/${id2}/mockups`,
8498
+ specMockup: (id2, name2) => `${API}/specs/${id2}/mockups/${encodeURIComponent(name2)}`,
8470
8499
  specReview: (id2) => `${API}/specs/${id2}/review`,
8471
8500
  specReviewFile: (id2, path) => `${API}/specs/${id2}/review/${path}`,
8472
8501
  // ADR-0213 · hapus worktree satu backlog item (cabang & commit tetap).
@@ -11222,6 +11251,19 @@ var init_backlog = __esm({
11222
11251
  build: (a) => ({ method: "GET", path: `/specs/${enc(String(a.spec))}/attachments` }),
11223
11252
  shape: (raw) => raw
11224
11253
  },
11254
+ // ADR-0233 · mockup UI hasil fase Mockup, dibaca dari worktree sesi atau checkout project.
11255
+ {
11256
+ name: "hanoman_backlog_mockups_list",
11257
+ title: "Daftar mockup backlog",
11258
+ description: "Daftar gambar mockup UI sebuah backlog item (hasil fase Mockup, `docs/mockups/<spec-id>/`): `source` (`worktree` sesi atau `repo` checkout project, null bila belum ada), `dir` (path absolut folder di MESIN SERVER hanoman \u2014 baca langsung bila kamu di mesin yang sama), dan `items` {name, size, modifiedAt}. Unduh satu gambar ke disk klien dengan hanoman_backlog_mockup_download.",
11259
+ inputSchema: obj({ properties: { spec: str(ID_HINT) }, required: ["spec"] }),
11260
+ mode: "read",
11261
+ capability: "backlog:read",
11262
+ samplePath: "/specs/SPEC-1/mockups",
11263
+ sampleMethod: "GET",
11264
+ build: (a) => ({ method: "GET", path: `/specs/${enc(String(a.spec))}/mockups` }),
11265
+ shape: (raw) => raw
11266
+ },
11225
11267
  {
11226
11268
  name: "hanoman_backlog_attachment_delete",
11227
11269
  title: "Hapus lampiran backlog",
@@ -13238,6 +13280,8 @@ var init_sessions = __esm({
13238
13280
  } })
13239
13281
  },
13240
13282
  prdPath: str("flow `breakdown`: jalur PRD yang dipecah. WAJIB untuk alur itu."),
13283
+ worktree: bool("Terminal agen biasa (hanya `project`, tanpa `flow`/`shell`): true = lahir di worktree terpisah pada branch baru `term/<id>`, bukan langsung di repo project. Kosong = di repo project."),
13284
+ branchFrom: str("Bersama `worktree: true`: branch/ref asal worktree (lokal atau origin). Kosong = HEAD repo."),
13241
13285
  force: bool("Lewati gerbang dependency. Pakai HANYA sesudah melihat daftar pemblokirnya.")
13242
13286
  },
13243
13287
  allOf: [
@@ -13267,7 +13311,8 @@ var init_sessions = __esm({
13267
13311
  if (flow2 === "breakdown") return { method: "POST", path: "/terminal/sessions", body: { project, flow: flow2, prdPath: String(a.prdPath) } };
13268
13312
  if (flow2 === "reverse" || flow2 === "scaffold") return { method: "POST", path: "/terminal/sessions", body: { project, flow: flow2 } };
13269
13313
  const body = { project };
13270
- for (const k of ["agent", "model", "effort"]) if (s(a[k]) !== void 0) body[k] = s(a[k]);
13314
+ for (const k of ["agent", "model", "effort", "branchFrom"]) if (s(a[k]) !== void 0) body[k] = s(a[k]);
13315
+ if (typeof a.worktree === "boolean") body.worktree = a.worktree;
13271
13316
  return { method: "POST", path: "/terminal/sessions", body };
13272
13317
  },
13273
13318
  shape: (raw) => raw
@@ -14837,6 +14882,26 @@ var init_transfer = __esm({
14837
14882
  build: (a) => ({ method: "GET", path: `${specOne(a)}/${enc(String(a.attachment))}`, download: dlFile(a, "lampiran") }),
14838
14883
  shape: (raw) => raw
14839
14884
  },
14885
+ // ADR-0233 · mockup UI backlog (`docs/mockups/<spec-id>/`), biner → transfer tool (preseden ADR-0183).
14886
+ {
14887
+ name: "hanoman_backlog_mockup_download",
14888
+ title: "Unduh mockup backlog",
14889
+ description: `Mengunduh SATU gambar mockup UI backlog item ke disk lokal klien MCP. Nama berkas dari hanoman_backlog_mockups_list. ${DOWNLOAD_RESULT}`,
14890
+ inputSchema: obj({
14891
+ properties: { spec: SPEC, name: str("Nama berkas mockup, mis. `01-backlog-detail.png`."), savePath: SAVE_PATH, overwrite: OVERWRITE },
14892
+ required: ["spec", "name"]
14893
+ }),
14894
+ mode: "write",
14895
+ capability: "backlog:read",
14896
+ samplePath: "/specs/SPEC-1/mockups/01-x.png",
14897
+ sampleMethod: "GET",
14898
+ build: (a) => ({
14899
+ method: "GET",
14900
+ path: `/specs/${enc(String(a.spec))}/mockups/${enc(String(a.name))}`,
14901
+ download: dlFile(a, String(a.name ?? "mockup.png"))
14902
+ }),
14903
+ shape: (raw) => raw
14904
+ },
14840
14905
  // ───────────────────────── lampiran tiket ─────────────────────────
14841
14906
  {
14842
14907
  name: "hanoman_ticket_attachment_download",
@@ -17386,6 +17451,7 @@ function orchestratorClause(plan, o = {}) {
17386
17451
  const askEscalation = codex2 || antigravity2 ? "tanyakan di terminal ini lalu tunggu jawaban" : ASK_ESCALATION_TOOL;
17387
17452
  const askDecisions = antigravity2 ? "di terminal ini sekaligus dalam satu pesan, tiap pertanyaan bernomor dan diakhiri `?` beserta opsi, rekomendasi dan alasannya, lalu tunggu jawaban" : codex2 ? "di terminal ini sekaligus dalam satu pesan, tiap pertanyaan bernomor dan diakhiri `?` beserta opsinya, lalu tunggu jawaban" : ASK_DECISIONS_TOOL;
17388
17453
  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." : "";
17454
+ const mockupStep = plan.phases.some((p3) => p3.phase === MOCKUP_PHASE) ? 'M. Fase Mockup: laporan `Status: selesai` dengan `Rekomendasi fase: lewati` \u2192 jalankan `echo "Mockup skipped" >> "$HANOMAN_PHASE_FILE"` (gerbang yang sama seperti langkah 2), BUKAN `Mockup done`. Permintaan persetujuan mockup datang sebagai `Keputusan terbuka:` \u2014 relay lewat langkah 4 apa adanya, termasuk path gambar dan baris `Lihat di hanoman:`; jangan menyetujuinya sendiri.' : "";
17389
17455
  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." : "";
17390
17456
  const runtimeLabel = (entry2) => antigravity2 ? `tier native ${entry2.model} \xB7 effort native unknown` : `${entry2.model} \xB7 ${entry2.effort}`;
17391
17457
  const phaseList = plan.phases.map((p3, i) => `${i + 1}. ${p3.phase} \u2192 \`${p3.agentName}\` \xB7 ${runtimeLabel(p3)}`).join("\n");
@@ -17416,7 +17482,8 @@ ${artefactRule} ${describeRule}`,
17416
17482
  '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.' + (review ? " Khusus Review: marker hanya sesudah `Verdict: lulus` (langkah R)." : ""),
17417
17483
  `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.`,
17418
17484
  `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}`,
17419
- 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.' : "",
17485
+ o.fastPath ? '5. `Rekomendasi fase: jalur-cepat` sesudah Audit \u2192 SEBELUM lanjut ke Execute: jalankan `echo "Spec skipped" >> "$HANOMAN_PHASE_FILE"`, `echo "Mockup 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 Mockup \u2192 Plan \u2192 Execute.' : "",
17486
+ mockupStep,
17420
17487
  reviewStep,
17421
17488
  handoff,
17422
17489
  "DILARANG mengerjakan isi fase sendiri \u2014 termasuk saat subagent gagal. Menulis `skipped` untuk fase yang dilewati bukan mengerjakannya.",
@@ -17655,7 +17722,7 @@ Stack: ${project.stack || "\u2014"}`,
17655
17722
  ${cron.prompt}`
17656
17723
  ].join("\n\n");
17657
17724
  }
17658
- var PIPELINES, WORK_PHASES, DECIDER, ASK_ROUTE, ASK_ESCALATION_TOOL, ASK_DECISIONS_TOOL, AMBIGUOUS, AUTONOMY_CLAUSE, AUTONOMY_CLAUSE_FULL, autonomyClause, PLAN_SHAPE, phaseInstruction, SELF_REVIEW_CLAUSE, skillInstruction, auditDecisionInstruction, ESCALATION_CONTRACT, auditOnlyInstruction, auditContinuationInstruction, auditContinuationForOrchestrator, writesCode, scopeClause, codeStyleClause, methodClause, attachmentClause, orchestratorAttachmentClause, PROJECT_METHOD, specContext, goalDetail, goalBlock, goalContext, projectContext, scaffoldContext, prdBriefBlock, prdAuditBlock, prdContext, breakdownContext, prdPhaseLines, breakdownPhaseLines, resumeClause, RESUMED_WORKTREE_NOTE, REVERSE_PHASE_GUIDE, SCAFFOLD_PHASE_GUIDE;
17725
+ var PIPELINES, WORK_PHASES, DECIDER, ASK_ROUTE, ASK_ESCALATION_TOOL, ASK_DECISIONS_TOOL, AMBIGUOUS, AUTONOMY_CLAUSE, AUTONOMY_CLAUSE_FULL, autonomyClause, PLAN_SHAPE, phaseInstruction, SELF_REVIEW_CLAUSE, mockupGuide, MOCKUP_FOLLOW_UP, MOCKUP_SINGLE, skillInstruction, auditDecisionInstruction, ESCALATION_CONTRACT, auditOnlyInstruction, auditContinuationInstruction, auditContinuationForOrchestrator, writesCode, scopeClause, codeStyleClause, methodClause, attachmentClause, orchestratorAttachmentClause, PROJECT_METHOD, specContext, goalDetail, goalBlock, goalContext, projectContext, scaffoldContext, prdBriefBlock, prdAuditBlock, prdContext, breakdownContext, prdPhaseLines, breakdownPhaseLines, resumeClause, RESUMED_WORKTREE_NOTE, REVERSE_PHASE_GUIDE, SCAFFOLD_PHASE_GUIDE;
17659
17726
  var init_prompt = __esm({
17660
17727
  "../runner/src/prompt.ts"() {
17661
17728
  "use strict";
@@ -17679,12 +17746,23 @@ var init_prompt = __esm({
17679
17746
  const base3 = `Kerjakan fase berurutan: ${phases2.join(" \u2192 ")}.
17680
17747
  Setiap kali sebuah fase selesai (atau kamu putuskan dilewati), append satu baris ke berkas di $HANOMAN_PHASE_FILE \u2014 persis: \`echo "<Nama Fase> done" >> "$HANOMAN_PHASE_FILE"\`, atau \`skipped\` sebagai ganti \`done\`. Nama fase ditulis apa adanya seperti di atas.`;
17681
17748
  if (!phases2.includes("Plan") || !phases2.includes("Execute")) return base3;
17682
- return base3 + `
17749
+ return base3 + (phases2.includes(MOCKUP_PHASE) ? `
17750
+ ${MOCKUP_SINGLE}
17751
+ ${MOCKUP_FOLLOW_UP.Plan} ${MOCKUP_FOLLOW_UP.Execute}` : "") + `
17683
17752
  Execute BELUM selesai selama plan (\`${method.planDir}/**\`) masih punya task \`- [ ]\`: kerjakan SEMUA PR/task sampai tiap kotak jadi \`- [x]\` sebelum menulis \`Execute done\`. hanoman menahan backlog di \`executing\`, bukan \`done\`, selama masih ada \`- [ ]\`.
17684
17753
  ${PLAN_SHAPE}` + (phases2.includes(REVIEWER_CELL) ? `
17685
17754
  ${SELF_REVIEW_CLAUSE}` : "");
17686
17755
  };
17687
- SELF_REVIEW_CLAUSE = "Fase Review (sesudah `Execute done`): periksa ulang hasil Execute terhadap dokumen spec & plan backlog ini \u2014 baca seluruh perubahan sesi ini (diff dari commit awal worktree) dan jalankan test sesuai scope verifikasi. Tulis tabel `AC \u2192 putusan \u2192 bukti` (setiap acceptance criteria \u2192 perintah & hasil yang kamu baca, atau path:baris) lalu satu baris `Verdict: lulus` atau `Verdict: rework`. `rework` \u2192 perbaiki temuannya, lalu review ulang; maks 2 putaran rework, sisanya jadi keputusan terbuka. Tulis `Review done` HANYA sesudah `Verdict: lulus`; Review TIDAK PERNAH `skipped`.";
17756
+ SELF_REVIEW_CLAUSE = "Fase Review (sesudah `Execute done`): periksa ulang hasil Execute terhadap dokumen spec & plan backlog ini \u2014 baca seluruh perubahan sesi ini (diff dari commit awal worktree) dan jalankan test sesuai scope verifikasi. Tulis tabel `AC \u2192 putusan \u2192 bukti` (setiap acceptance criteria \u2192 perintah & hasil yang kamu baca, atau path:baris) lalu satu baris `Verdict: lulus` atau `Verdict: rework`. `rework` \u2192 perbaiki temuannya, lalu review ulang; maks 2 putaran rework, sisanya jadi keputusan terbuka. Tulis `Review done` HANYA sesudah `Verdict: lulus`; Review TIDAK PERNAH `skipped`. Bila `docs/mockups/<spec-id>/` ada, tabel itu juga memuat kesesuaian hasil dengan mockup yang disetujui.";
17757
+ mockupGuide = (ask, close) => `putuskan dulu dari dokumen spec (qa jalur cepat: dokumen audit) apakah backlog ini mengubah atau membuat UI \u2014 layar, komponen, atau state yang terlihat pengguna. Tidak ada (perbaikan backend, CLI, refactor, docs) \u2192 fase ini dilewati, sebutkan alasannya dalam satu baris; ragu \u2192 anggap ada UI. Ada \u2192 baca design system project bila ada (mis. \`internal/docs/design-system/**\`, \`DESIGN.md\`) dan kode layar yang akan berubah, muat skill \`codex-image\`, lalu hasilkan SATU gambar per layar/state yang berubah, maks 6: desktop \`1536x1024\`, tambah mobile \`1024x1536\` bila layout responsif tersentuh, ke \`docs/mockups/<spec-id>/<NN>-<slug>.png\` (spec-id huruf kecil, NN dua digit urut). Tambahkan bagian \`## Mockup\` di dokumen spec: tiap berkas, layar/state yang ditunjukkannya, dan apa yang berubah. Lalu minta persetujuan ${ask}: sebutkan path absolut setiap gambar dan baris \`Lihat di hanoman: Backlog <SPEC-ID> \u2192 Mockup\`, dengan opsi Setujui \xB7 Revisi (beserta catatan) \xB7 Lewati mockup. Revisi \u2192 buat ulang gambar yang diminta dengan spec gambar LENGKAP ditambah koreksinya (bukan koreksinya saja), timpa berkas yang sama, lalu minta persetujuan lagi. Lewati \u2192 hapus berkas mockup yang belum di-commit. Disetujui \u2192 commit \`docs/mockups/<spec-id>/\` beserta dokumen spec. Bila gambar tak bisa dihasilkan (Codex tak terpasang/belum login, image_gen gagal dua kali), JANGAN membuat pengganti (SVG/CSS/HTML yang dirasterisasi, emoji, atau placeholder bernama sama) \u2014 tanyakan dengan jalur yang sama: tunggu operator menjalankan \`codex login\` lalu lanjut, atau lewati mockup. ${close}`;
17758
+ MOCKUP_FOLLOW_UP = {
17759
+ Plan: "Bila `docs/mockups/<spec-id>/` ada, setiap task UI di plan merujuk berkas mockup yang diwujudkannya.",
17760
+ Execute: "Bila `docs/mockups/<spec-id>/` ada, bangun UI dengan kode sesuai mockup itu \u2014 PNG mockup bukan aset produk."
17761
+ };
17762
+ MOCKUP_SINGLE = "Fase Mockup: " + mockupGuide(
17763
+ "lewat jalur tanya di klausa otonomi prompt ini",
17764
+ "Tulis `Mockup done` sesudah disetujui & di-commit, atau `Mockup skipped` bila dilewati."
17765
+ );
17688
17766
  skillInstruction = (flow2, phases2, method) => {
17689
17767
  const lines2 = phases2.map((p3) => {
17690
17768
  const skills2 = phaseSkillsFor(flow2, p3, method);
@@ -17693,7 +17771,7 @@ ${SELF_REVIEW_CLAUSE}` : "");
17693
17771
  return lines2.length ? `Skills ${method.label} WAJIB: sebelum mengerjakan fase di bawah, muat & ikuti skill-nya dengan mekanisme yang tersedia di agenmu \u2014 bila skill relevan tersedia, pakai.
17694
17772
  ${lines2.join("\n")}` : "";
17695
17773
  };
17696
- auditDecisionInstruction = (flow2) => flow2 !== "qa" ? "" : 'Keputusan pasca-Audit (qa): bila temuan berconfidence tinggi dan perbaikannya bisa dikerjakan langsung (diff kecil, akar masalah jelas), LEWATI Spec dan Plan \u2014 tandai keduanya `skipped` (`echo "Spec skipped" >> "$HANOMAN_PHASE_FILE"` lalu `echo "Plan skipped" >> "$HANOMAN_PHASE_FILE"`) dan langsung ke Execute; dokumen audit menjadi doc-of-record perbaikan itu. Bila temuan luas, berisiko, atau ambigu, jalankan Spec \u2192 Plan \u2192 Execute penuh. Keputusan ini milikmu berdasarkan hasil Audit, bukan default \u2014 jangan bayar perencanaan yang tak perlu untuk perbaikan sepele.';
17774
+ auditDecisionInstruction = (flow2) => flow2 !== "qa" ? "" : 'Keputusan pasca-Audit (qa): bila temuan berconfidence tinggi dan perbaikannya bisa dikerjakan langsung (diff kecil, akar masalah jelas), LEWATI Spec, Mockup, dan Plan \u2014 tandai ketiganya `skipped` (`echo "Spec skipped" >> "$HANOMAN_PHASE_FILE"`, `echo "Mockup skipped" >> "$HANOMAN_PHASE_FILE"`, lalu `echo "Plan skipped" >> "$HANOMAN_PHASE_FILE"`) dan langsung ke Execute; dokumen audit menjadi doc-of-record perbaikan itu. Bila temuan luas, berisiko, atau ambigu, jalankan Spec \u2192 Mockup \u2192 Plan \u2192 Execute penuh. Keputusan ini milikmu berdasarkan hasil Audit, bukan default \u2014 jangan bayar perencanaan yang tak perlu untuk perbaikan sepele.';
17697
17775
  ESCALATION_CONTRACT = [
17698
17776
  "REKOMENDASI ESKALASI (wajib, terbaca mesin). Di bagian akhir dokumen audit, tulis bagian",
17699
17777
  "`## Rekomendasi eskalasi` berisi penjelasan singkat untuk manusia, LALU tepat SATU blok ```json",
@@ -17724,7 +17802,7 @@ ${lines2.join("\n")}` : "";
17724
17802
  const doc = `internal/docs/research/audit-${fromAudit.toLowerCase()}-*.md`;
17725
17803
  if (flow2 === "feature")
17726
17804
  return `Backlog brief ini LANJUTAN dari audit ${fromAudit}. Worktree ini lahir dari branch audit itu, jadi dokumen audit sudah ada di ${doc}. BACA dokumen itu lebih dulu dan pakai sebagai bahan fase Brainstorm & Objective \u2014 temuannya sudah terbukti, jangan menginvestigasi ulang dari nol. Semua fase tetap dijalankan: dokumen audit memuat TEMUAN, bukan bentuk solusi, jadi perancangan fitur tetap pekerjaanmu.`;
17727
- return `Backlog qa ini LANJUTAN dari audit ${fromAudit}. Worktree ini lahir dari branch audit itu, jadi dokumen audit sudah ada di ${doc}. JANGAN mengulang investigasi fase Audit dari nol \u2014 baca dokumen audit itu sebagai temuan, tandai fase Audit dilewati (\`echo "Audit skipped" >> "$HANOMAN_PHASE_FILE"\`), lalu ambil keputusan pasca-Audit: perbaikan jelas & kecil \u2192 langsung Execute (tandai \`Spec skipped\` dan \`Plan skipped\` bila sesuai); selain itu Spec \u2192 Plan \u2192 Execute penuh.`;
17805
+ return `Backlog qa ini LANJUTAN dari audit ${fromAudit}. Worktree ini lahir dari branch audit itu, jadi dokumen audit sudah ada di ${doc}. JANGAN mengulang investigasi fase Audit dari nol \u2014 baca dokumen audit itu sebagai temuan, tandai fase Audit dilewati (\`echo "Audit skipped" >> "$HANOMAN_PHASE_FILE"\`), lalu ambil keputusan pasca-Audit: perbaikan jelas & kecil \u2192 langsung Execute (tandai \`Spec skipped\`, \`Mockup skipped\`, dan \`Plan skipped\` bila sesuai); selain itu Spec \u2192 Mockup \u2192 Plan \u2192 Execute penuh.`;
17728
17806
  };
17729
17807
  auditContinuationForOrchestrator = (flow2, spec) => {
17730
17808
  if (flow2 !== "qa" && flow2 !== "feature") return "";
@@ -17733,7 +17811,7 @@ ${lines2.join("\n")}` : "";
17733
17811
  const doc = `internal/docs/research/audit-${fromAudit.toLowerCase()}-*.md`;
17734
17812
  if (flow2 === "feature")
17735
17813
  return `Backlog brief ini LANJUTAN dari audit ${fromAudit}. Worktree ini lahir dari branch audit itu, jadi dokumen audit sudah ada di ${doc}. Saat memanggil agen fase Brainstorm, cantumkan path itu di baris \`Artefak fase sebelumnya:\` blok serah-terima \u2014 JANGAN membacanya sendiri untuk merancang fitur ini. Saat memanggil agen fase Objective, baris itu biasanya sudah membawa path artefak Brainstorm (dokumen spec yang ditulis fase itu); tambahkan path dokumen audit di samping path itu, bukan menggantikannya. Temuannya sudah terbukti, tapi bentuk solusinya belum; itu tetap dikerjakan agen fasenya masing-masing, bukan olehmu.`;
17736
- return `Backlog qa ini LANJUTAN dari audit ${fromAudit}. Temuannya sudah terbukti di ${doc} \u2014 JANGAN mendelegasikan ulang fase Audit. Sebelum fase lain: jalankan persis \`echo "Audit skipped" >> "$HANOMAN_PHASE_FILE"\`, verifikasi dengan \`tail -1 "$HANOMAN_PHASE_FILE"\` bahwa barisnya benar tertulis (gerbang yang sama seperti langkah 2), lalu cantumkan path dokumen audit itu di baris \`Artefak fase sebelumnya:\` saat memanggil agen fase Spec. Keputusan routing di sini menggantikan langkah 5 di atas \u2014 JANGAN menunggu frasa \`Rekomendasi fase: jalur-cepat\` dari agen Audit karena Audit tidak dijalankan pada kelanjutan ini. Kamu diizinkan mengambil SATU keputusan ROUTING dari isi dokumen audit itu SAJA, dibaca langsung dari kriterianya: temuan berconfidence tinggi dan perbaikan langsung berdiff kecil \u2192 tandai \`Spec skipped\` lalu \`Plan skipped\` (gerbang yang sama) lalu panggil agen fase Execute; selebihnya panggil Spec \u2192 Plan \u2192 Execute penuh lewat agen fasenya masing-masing. Kamu TIDAK menginvestigasi ulang maupun merancang perbaikannya sendiri \u2014 itu tetap pekerjaan agen fase.`;
17814
+ return `Backlog qa ini LANJUTAN dari audit ${fromAudit}. Temuannya sudah terbukti di ${doc} \u2014 JANGAN mendelegasikan ulang fase Audit. Sebelum fase lain: jalankan persis \`echo "Audit skipped" >> "$HANOMAN_PHASE_FILE"\`, verifikasi dengan \`tail -1 "$HANOMAN_PHASE_FILE"\` bahwa barisnya benar tertulis (gerbang yang sama seperti langkah 2), lalu cantumkan path dokumen audit itu di baris \`Artefak fase sebelumnya:\` saat memanggil agen fase Spec. Keputusan routing di sini menggantikan langkah 5 di atas \u2014 JANGAN menunggu frasa \`Rekomendasi fase: jalur-cepat\` dari agen Audit karena Audit tidak dijalankan pada kelanjutan ini. Kamu diizinkan mengambil SATU keputusan ROUTING dari isi dokumen audit itu SAJA, dibaca langsung dari kriterianya: temuan berconfidence tinggi dan perbaikan langsung berdiff kecil \u2192 tandai \`Spec skipped\`, \`Mockup skipped\`, lalu \`Plan skipped\` (gerbang yang sama) lalu panggil agen fase Execute; selebihnya panggil Spec \u2192 Mockup \u2192 Plan \u2192 Execute penuh lewat agen fasenya masing-masing. Kamu TIDAK menginvestigasi ulang maupun merancang perbaikannya sendiri \u2014 itu tetap pekerjaan agen fase.`;
17737
17815
  };
17738
17816
  writesCode = (flow2) => PIPELINES[flow2].some((p3) => WORK_PHASES.includes(p3));
17739
17817
  scopeClause = (flow2, scope) => scope && writesCode(flow2) ? verifyScopeClause(scope) : "";
@@ -18053,7 +18131,7 @@ function defaultGoalCondition({ flow: flow2, specId, branchTo, spec }) {
18053
18131
  const phases2 = PIPELINES[flow2];
18054
18132
  const planGate = phases2.includes("Plan") && phases2.includes("Execute");
18055
18133
  const clauses = [
18056
- `1. output \`cat "$HANOMAN_PHASE_FILE"\` yang memuat satu baris untuk SETIAP fase ${phases2.join(" \u2192 ")}, masing-masing berakhiran \`done\` atau \`skipped\`` + phases2.filter((p3) => UNSKIPPABLE_PHASES.has(p3)).map((p3) => ` (\`${p3}\` wajib \`done\`)`).join("") + ";"
18134
+ `1. output \`cat "$HANOMAN_PHASE_FILE"\` yang memuat satu baris untuk SETIAP fase ${phases2.join(" \u2192 ")}, masing-masing berakhiran \`done\` atau \`skipped\`` + phases2.filter((p3) => UNSKIPPABLE_PHASES.has(p3)).map((p3) => ` (\`${p3}\` wajib \`done\`)`).join("") + phases2.filter((p3) => OPTIONAL_PHASES.has(p3)).map((p3) => ` (\`${p3}\` boleh tanpa baris bila fase sesudahnya sudah tercatat)`).join("") + ";"
18057
18135
  ];
18058
18136
  if (planGate) {
18059
18137
  const dirs = PLAN_DIRS.map((d) => `${d}/`).join(" ");
@@ -18124,12 +18202,13 @@ function codexGoalScript(o) {
18124
18202
  `ST=${shq(o.stateFile)}`,
18125
18203
  "missing=''"
18126
18204
  ];
18127
- for (const p3 of phases2) {
18205
+ phases2.forEach((p3, i) => {
18206
+ const name2 = OPTIONAL_PHASES.has(p3) ? `(${phases2.slice(i).join("|")})` : p3;
18128
18207
  lines2.push(
18129
18208
  // ADR-0230 · fase `UNSKIPPABLE_PHASES` (Review) hanya lolos dengan `done`.
18130
- `grep -qE ${shq(`^${p3} ${UNSKIPPABLE_PHASES.has(p3) ? "done" : "(done|skipped)"}[[:space:]]*$`)} "$PF" 2>/dev/null || missing="$missing\\n- fase ${p3} belum tercatat di \\$HANOMAN_PHASE_FILE"`
18209
+ `grep -qE ${shq(`^${name2} ${UNSKIPPABLE_PHASES.has(p3) ? "done" : "(done|skipped)"}[[:space:]]*$`)} "$PF" 2>/dev/null || missing="$missing\\n- fase ${p3} belum tercatat di \\$HANOMAN_PHASE_FILE"`
18131
18210
  );
18132
- }
18211
+ });
18133
18212
  if (planGate) {
18134
18213
  for (const dir2 of PLAN_DIRS) {
18135
18214
  lines2.push(
@@ -18882,9 +18961,16 @@ function backlogGuide(flow2, phase, ctx) {
18882
18961
  // default skill) lolos hampa. Nama dikunci seperti Brainstorm, dan path persisnya diteruskan
18883
18962
  // orchestrator ke Execute & reviewer — bukan pola glob yang bisa mencocoki plan backlog lain.
18884
18963
  case "Plan":
18885
- return `- Plan: tulis plan implementasi berkotak \`- [ ]\` untuk backlog ini dari dokumen spec-nya (path di serah-terima) ke SATU berkas \`${m.planDir}/<YYYY-MM-DD>-<spec-id>-<slug>.md\` (spec-id backlog ini huruf kecil, mis. \`2026-09-25-spec-123-ekspor-csv.md\` \u2014 nama tanpa spec-id membuat hanoman tak menemukan plan-nya). Laporkan path PERSIS berkas itu di \`Artefak:\`; orchestrator meneruskannya ke Execute. ${PLAN_SHAPE}`;
18964
+ return `- Plan: tulis plan implementasi berkotak \`- [ ]\` untuk backlog ini dari dokumen spec-nya (path di serah-terima) ke SATU berkas \`${m.planDir}/<YYYY-MM-DD>-<spec-id>-<slug>.md\` (spec-id backlog ini huruf kecil, mis. \`2026-09-25-spec-123-ekspor-csv.md\` \u2014 nama tanpa spec-id membuat hanoman tak menemukan plan-nya). Laporkan path PERSIS berkas itu di \`Artefak:\`; orchestrator meneruskannya ke Execute. ${PLAN_SHAPE} ${MOCKUP_FOLLOW_UP.Plan}`;
18886
18965
  case "Execute":
18887
- return `- Execute: kerjakan plan backlog ini \u2014 path persisnya di baris \`Artefak fase sebelumnya:\` serah-terima (bila tak ada: berkas di \`${m.planDir}/\` yang namanya memuat spec-id backlog ini). Execute BELUM selesai selama plan masih punya task \`- [ ]\`: kerjakan SEMUA task sampai tiap kotak jadi \`- [x]\` sebelum melapor \`Status: selesai\`. hanoman menahan backlog di \`executing\`, bukan \`done\`, selama masih ada \`- [ ]\`. Laporan \`Status: selesai\` wajib memuat tabel \`AC \u2192 bukti\`: setiap acceptance criteria dokumen spec (qa jalur-cepat: dokumen audit) \u2192 perintah yang kamu jalankan beserta hasil yang kamu baca, atau path:baris yang membuktikannya. AC tanpa bukti = belum selesai; laporanmu diperiksa ulang sebelum fase ditutup.`;
18966
+ return `- Execute: kerjakan plan backlog ini \u2014 path persisnya di baris \`Artefak fase sebelumnya:\` serah-terima (bila tak ada: berkas di \`${m.planDir}/\` yang namanya memuat spec-id backlog ini). Execute BELUM selesai selama plan masih punya task \`- [ ]\`: kerjakan SEMUA task sampai tiap kotak jadi \`- [x]\` sebelum melapor \`Status: selesai\`. hanoman menahan backlog di \`executing\`, bukan \`done\`, selama masih ada \`- [ ]\`. Laporan \`Status: selesai\` wajib memuat tabel \`AC \u2192 bukti\`: setiap acceptance criteria dokumen spec (qa jalur-cepat: dokumen audit) \u2192 perintah yang kamu jalankan beserta hasil yang kamu baca, atau path:baris yang membuktikannya. AC tanpa bukti = belum selesai; laporanmu diperiksa ulang sebelum fase ditutup. ${MOCKUP_FOLLOW_UP.Execute}`;
18967
+ // ADR-0233 · panduan yang SAMA dengan sesi tunggal (`mockupGuide`); bedanya cuma jalur tanya & penutupan:
18968
+ // agen fase tak bisa bertanya langsung dan tak menulis marker sendiri.
18969
+ case "Mockup":
18970
+ return "- Mockup: " + mockupGuide(
18971
+ "sebagai butir `Keputusan terbuka:` laporanmu (`Status: menunggu-keputusan`) \u2014 orchestrator yang menanyakannya",
18972
+ "Disetujui & di-commit \u2192 `Status: selesai`. Dilewati (tanpa UI, atau dipilih Lewati) \u2192 `Status: selesai` dengan `Rekomendasi fase: lewati` beserta alasannya; orchestrator yang menulis marker-nya."
18973
+ );
18888
18974
  case "Audit":
18889
18975
  return flow2 === "audit" ? "- Audit: ini audit-only \u2014 investigasi SAJA, JANGAN menulis perbaikan kode apa pun. Telusuri akar masalah / log / jawaban dan nilai apakah issue terdefinisi dengan baik. Laporkan temuan beserta buktinya; dokumennya ditulis fase Laporan." : "- Audit: telusuri akar masalah dengan bukti (reproduksi, log, kode) dan tulis dokumen audit ke `internal/docs/research/audit-<spec-id>-<slug>.md`. Lalu putuskan jalurnya dari hasil Audit, bukan default: bila temuan berconfidence tinggi dan perbaikannya bisa dikerjakan langsung (diff kecil, akar masalah jelas), tulis `Rekomendasi fase: jalur-cepat` beserta alasannya \u2014 orchestrator akan melewati Spec & Plan dan dokumen audit menjadi doc-of-record perbaikan itu. Bila temuan luas, berisiko, atau ambigu, tulis `Rekomendasi fase: penuh`. " + FRONT_LOADED_DECISIONS("Spec, Plan, dan Execute") + " Catat jawabannya di dokumen audit.";
18890
18976
  case "Laporan":
@@ -18962,7 +19048,7 @@ function reviewerInstructions(plan, ctx) {
18962
19048
  "Prosedur:",
18963
19049
  [
18964
19050
  `1. Baca dokumen spec & plan dari baris \`Artefak:\` serah-terima (qa jalur-cepat: dokumen audit adalah doc-of-record; bila path tak ada, cari berkas ber-spec-id di \`${m.specDir}/\` & \`${m.planDir}/\`). Plan yang menyimpang dari spec adalah temuan.`,
18965
- "2. Ubah acceptance criteria jadi daftar yang diperiksa SATU PER SATU. Kalimat tak terukur ditandai tak terukur, bukan ditafsirkan.",
19051
+ "2. Ubah acceptance criteria jadi daftar yang diperiksa SATU PER SATU. Kalimat tak terukur ditandai tak terukur, bukan ditafsirkan. Bila `docs/mockups/<spec-id>/` ada, kesesuaian UI hasil dengan mockup yang disetujui ikut jadi kriteria.",
18966
19052
  '3. Baca `git diff "$HANOMAN_BASE_SHA"...HEAD` dan `git status --porcelain` (perubahan belum di-commit adalah temuan). Laporan Execute dan kotak `- [x]` hanyalah klaim.',
18967
19053
  "4. Jalankan test yang relevan dengan berkas yang berubah, sesuai scope verifikasi di bawah, dan baca outputnya sendiri.",
18968
19054
  `5. Per kriteria putuskan salah satu: ${SPEC_AUDIT_VERDICT_LIST}. Kriteria tanpa jangkar di diff bukan otomatis gagal \u2014 cari bukti keadaan akhir; tak bisa dibuktikan \u2192 belum terverifikasi.`
@@ -20962,6 +21048,7 @@ __export(src_exports, {
20962
21048
  GOAL_MAX: () => GOAL_MAX,
20963
21049
  GOAL_MAX_BLOCKS: () => GOAL_MAX_BLOCKS,
20964
21050
  GOAL_TUI_PASTE_LIMIT: () => GOAL_TUI_PASTE_LIMIT,
21051
+ MOCKUP_FOLLOW_UP: () => MOCKUP_FOLLOW_UP,
20965
21052
  PHASE_CONTEXT_HEADER: () => PHASE_CONTEXT_HEADER,
20966
21053
  PIPELINES: () => PIPELINES,
20967
21054
  PLAN_SHAPE: () => PLAN_SHAPE,
@@ -21028,6 +21115,7 @@ __export(src_exports, {
21028
21115
  materializeAntigravityAgents: () => materializeAntigravityAgents,
21029
21116
  materializeCodexAgents: () => materializeCodexAgents,
21030
21117
  methodClause: () => methodClause,
21118
+ mockupGuide: () => mockupGuide,
21031
21119
  orchestratorClause: () => orchestratorClause,
21032
21120
  parseConfigEnv: () => parseConfigEnv,
21033
21121
  phaseAgentInstructions: () => phaseAgentInstructions,
@@ -21500,10 +21588,13 @@ var init_session_phases = __esm({
21500
21588
  decisionFilePath = (repoDir, sessionId2) => `${repoDir}/.worktrees/.decisions/${sessionId2}`;
21501
21589
  phaseFileMemo = /* @__PURE__ */ new Map();
21502
21590
  derivePhases = (seen2, flow2) => {
21591
+ const names = PIPELINES[flow2];
21503
21592
  let activeTaken = false;
21504
- return PIPELINES[flow2].map((name2) => {
21593
+ return names.map((name2, i) => {
21505
21594
  const state = seen2.get(name2);
21506
21595
  if (state) return { name: name2, state };
21596
+ if (OPTIONAL_PHASES.has(name2) && names.slice(i + 1).some((later) => seen2.has(later)))
21597
+ return { name: name2, state: "skipped" };
21507
21598
  if (activeTaken) return { name: name2, state: "pending" };
21508
21599
  activeTaken = true;
21509
21600
  return { name: name2, state: "active" };
@@ -21784,18 +21875,146 @@ var init_antigravity_runtime = __esm({
21784
21875
  }
21785
21876
  });
21786
21877
 
21878
+ // src/runner/deps.ts
21879
+ import { existsSync as existsSync9 } from "node:fs";
21880
+ import { dirname as dirname10, join as join15 } from "node:path";
21881
+ function repoRootFrom(startDir) {
21882
+ let dir2 = startDir;
21883
+ for (let i = 0; i < 8; i++) {
21884
+ if (existsSync9(join15(dir2, "pnpm-workspace.yaml"))) return dir2;
21885
+ const parent = dirname10(dir2);
21886
+ if (parent === dir2) break;
21887
+ dir2 = parent;
21888
+ }
21889
+ return startDir;
21890
+ }
21891
+ var repoRoot;
21892
+ var init_deps = __esm({
21893
+ "src/runner/deps.ts"() {
21894
+ "use strict";
21895
+ repoRoot = (startDir = process.cwd()) => repoRootFrom(startDir);
21896
+ }
21897
+ });
21898
+
21899
+ // src/services/bundled-skills.ts
21900
+ import { createHash as createHash7 } from "node:crypto";
21901
+ import { chmodSync as chmodSync7, cpSync as cpSync3, existsSync as existsSync10, mkdirSync as mkdirSync9, readFileSync as readFileSync11, readdirSync as readdirSync8, renameSync as renameSync4, rmSync as rmSync6, statSync as statSync3, writeFileSync as writeFileSync11 } from "node:fs";
21902
+ import { join as join16, relative as relative5, sep as sep6 } from "node:path";
21903
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
21904
+ function bundledSkillsDir() {
21905
+ const packed = packagedDir();
21906
+ return existsSync10(packed) ? packed : join16(repoRoot(moduleDir()), "server", "skills");
21907
+ }
21908
+ function filesUnder(dir2) {
21909
+ const out4 = [];
21910
+ const walk = (d) => {
21911
+ for (const n2 of readdirSync8(d).sort()) {
21912
+ if (n2 === BUNDLED_MARKER || n2 === ".DS_Store") continue;
21913
+ const full = join16(d, n2);
21914
+ if (statSync3(full).isDirectory()) walk(full);
21915
+ else out4.push(full);
21916
+ }
21917
+ };
21918
+ walk(dir2);
21919
+ return out4;
21920
+ }
21921
+ function contentSha(dir2) {
21922
+ const h2 = createHash7("sha256");
21923
+ for (const f of filesUnder(dir2)) {
21924
+ h2.update(relative5(dir2, f).split(sep6).join("/"));
21925
+ h2.update("\0");
21926
+ h2.update(readFileSync11(f));
21927
+ h2.update("\0");
21928
+ }
21929
+ return h2.digest("hex");
21930
+ }
21931
+ function markerSha(dir2) {
21932
+ try {
21933
+ const parsed = JSON.parse(readFileSync11(join16(dir2, BUNDLED_MARKER), "utf8"));
21934
+ const sha2 = parsed?.sha;
21935
+ return typeof sha2 === "string" ? sha2 : null;
21936
+ } catch {
21937
+ return null;
21938
+ }
21939
+ }
21940
+ function install(src, dest, sha2) {
21941
+ const tmp = `${dest}.tmp-${process.pid}`;
21942
+ rmSync6(tmp, { recursive: true, force: true });
21943
+ cpSync3(src, tmp, { recursive: true });
21944
+ for (const f of filesUnder(tmp))
21945
+ if (relative5(tmp, f).split(sep6)[0] === "scripts") chmodSync7(f, statSync3(f).mode | 493);
21946
+ writeFileSync11(join16(tmp, BUNDLED_MARKER), `${JSON.stringify({ sha: sha2, seededAt: (/* @__PURE__ */ new Date()).toISOString() })}
21947
+ `);
21948
+ rmSync6(dest, { recursive: true, force: true });
21949
+ renameSync4(tmp, dest);
21950
+ }
21951
+ function seedBundledSkills(hanomanHome, o = {}) {
21952
+ const out4 = { seeded: [], updated: [], kept: [] };
21953
+ const source = o.source ?? bundledSkillsDir();
21954
+ const log = o.log ?? ((m) => console.warn(m));
21955
+ const names = skillDirsIn(source);
21956
+ if (!names.length) return out4;
21957
+ const root = hanomanSkillsDir(hanomanHome);
21958
+ for (const name2 of names) {
21959
+ try {
21960
+ const src = join16(source, name2);
21961
+ const dest = join16(root, name2);
21962
+ const bundleSha = contentSha(src);
21963
+ if (!existsSync10(dest)) {
21964
+ mkdirSync9(root, { recursive: true });
21965
+ install(src, dest, bundleSha);
21966
+ out4.seeded.push(name2);
21967
+ continue;
21968
+ }
21969
+ const recorded2 = markerSha(dest);
21970
+ if (recorded2 === bundleSha) continue;
21971
+ if (recorded2 === null || contentSha(dest) !== recorded2) {
21972
+ out4.kept.push(name2);
21973
+ log(`skill bundel ${name2} tak diperbarui: salinan di ${dest} milik operator (tanpa marker atau sudah disunting)`);
21974
+ continue;
21975
+ }
21976
+ install(src, dest, bundleSha);
21977
+ out4.updated.push(name2);
21978
+ } catch (e) {
21979
+ log(`skill bundel ${name2} gagal di-seed: ${e.message}`);
21980
+ }
21981
+ }
21982
+ return out4;
21983
+ }
21984
+ var BUNDLED_MARKER, packagedDir, moduleDir, skillDirsIn, isBundledCopy;
21985
+ var init_bundled_skills = __esm({
21986
+ "src/services/bundled-skills.ts"() {
21987
+ "use strict";
21988
+ init_src2();
21989
+ init_deps();
21990
+ BUNDLED_MARKER = ".hanoman-bundled.json";
21991
+ packagedDir = () => fileURLToPath3(new URL("../skills", import.meta.url));
21992
+ moduleDir = () => fileURLToPath3(new URL(".", import.meta.url));
21993
+ skillDirsIn = (source) => {
21994
+ let names;
21995
+ try {
21996
+ names = readdirSync8(source);
21997
+ } catch {
21998
+ return [];
21999
+ }
22000
+ return names.filter((n2) => !n2.startsWith(".") && existsSync10(join16(source, n2, "SKILL.md"))).sort();
22001
+ };
22002
+ isBundledCopy = (dir2) => existsSync10(join16(dir2, BUNDLED_MARKER));
22003
+ }
22004
+ });
22005
+
21787
22006
  // src/services/skill-inject.ts
21788
22007
  import { execFileSync as execFileSync4 } from "node:child_process";
21789
- import { createHash as createHash7 } from "node:crypto";
21790
- import { appendFileSync as appendFileSync2, existsSync as existsSync9, lstatSync as lstatSync7, mkdirSync as mkdirSync9, readFileSync as readFileSync11, readlinkSync as readlinkSync2, symlinkSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync11 } from "node:fs";
21791
- import { isAbsolute as isAbsolute8, join as join15 } from "node:path";
22008
+ import { createHash as createHash8 } from "node:crypto";
22009
+ import { appendFileSync as appendFileSync2, existsSync as existsSync11, lstatSync as lstatSync7, mkdirSync as mkdirSync10, readFileSync as readFileSync12, readlinkSync as readlinkSync2, symlinkSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync12 } from "node:fs";
22010
+ import { isAbsolute as isAbsolute8, join as join17 } from "node:path";
21792
22011
  function excludeFile(repoDir) {
21793
22012
  const common = execFileSync4("git", ["rev-parse", "--git-common-dir"], { cwd: repoDir, env: agentEnvironment(null, process.env), encoding: "utf8" }).trim();
21794
- return join15(isAbsolute8(common) ? common : join15(repoDir, common), "info", "exclude");
22013
+ return join17(isAbsolute8(common) ? common : join17(repoDir, common), "info", "exclude");
21795
22014
  }
21796
22015
  function ownedSkills(tempDir) {
21797
22016
  try {
21798
- const parsed = JSON.parse(readFileSync11(manifestPath(tempDir), "utf8"));
22017
+ const parsed = JSON.parse(readFileSync12(manifestPath(tempDir), "utf8"));
21799
22018
  return Array.isArray(parsed) ? parsed.filter((row) => row && typeof row.link === "string" && typeof row.target === "string" && typeof row.exclude === "string" && typeof row.pair === "string" && typeof row.ino === "number" && typeof row.dev === "number") : [];
21800
22019
  } catch {
21801
22020
  return [];
@@ -21808,8 +22027,8 @@ function cleanupInjectedSkills(tempDir) {
21808
22027
  if (!st2.isSymbolicLink() || st2.ino !== own.ino || st2.dev !== own.dev || readlinkSync2(own.link) !== own.target) continue;
21809
22028
  unlinkSync2(own.link);
21810
22029
  if (own.pair) {
21811
- const bytes = readFileSync11(own.exclude, "utf8");
21812
- if (bytes.includes(own.pair)) writeFileSync11(own.exclude, bytes.replace(own.pair, ""));
22030
+ const bytes = readFileSync12(own.exclude, "utf8");
22031
+ if (bytes.includes(own.pair)) writeFileSync12(own.exclude, bytes.replace(own.pair, ""));
21813
22032
  }
21814
22033
  } catch {
21815
22034
  }
@@ -21817,18 +22036,20 @@ function cleanupInjectedSkills(tempDir) {
21817
22036
  }
21818
22037
  function injectGlobalSkills(o) {
21819
22038
  const out4 = { injected: [], warnings: [] };
21820
- const opts2 = { hanomanHome: o.hanomanHome, env: process.env };
21821
- const global2 = scanGlobalSkills(opts2).filter((s2) => s2.layer === "hanoman");
22039
+ const opts2 = { hanomanHome: o.hanomanHome, env: process.env, ...o.osHome ? { osHome: o.osHome } : {} };
22040
+ const scanned = scanGlobalSkills(opts2);
22041
+ const global2 = scanned.filter((s2) => s2.layer === "hanoman");
21822
22042
  if (!global2.length) return out4;
21823
22043
  const taken = new Set(scanProjectSkills("session", o.cwd, opts2).map((s2) => s2.name));
21824
- const todo = global2.filter((s2) => !taken.has(s2.name));
22044
+ const userOwned = new Set(scanned.filter((s2) => s2.layer === "user" && s2.loadedBy.includes(o.agent)).map((s2) => s2.name));
22045
+ const todo = global2.filter((s2) => !taken.has(s2.name) && !(userOwned.has(s2.name) && isBundledCopy(s2.dir)));
21825
22046
  if (!todo.length) return out4;
21826
22047
  if (o.agent === "claude") {
21827
- const root = join15(o.tempDir, "skills-root");
22048
+ const root = join17(o.tempDir, "skills-root");
21828
22049
  try {
21829
- mkdirSync9(join15(root, ".claude", "skills"), { recursive: true });
22050
+ mkdirSync10(join17(root, ".claude", "skills"), { recursive: true });
21830
22051
  for (const s2 of todo) {
21831
- const link2 = join15(root, ".claude", "skills", s2.name);
22052
+ const link2 = join17(root, ".claude", "skills", s2.name);
21832
22053
  if (!lstatSync7(link2, { throwIfNoEntry: false })) symlinkSync(s2.dir, link2);
21833
22054
  out4.injected.push(s2.name);
21834
22055
  }
@@ -21849,25 +22070,25 @@ function injectGlobalSkills(o) {
21849
22070
  let ownedLink;
21850
22071
  let ownedPair = "";
21851
22072
  try {
21852
- const link2 = join15(o.cwd, ".agents", "skills", s2.name);
22073
+ const link2 = join17(o.cwd, ".agents", "skills", s2.name);
21853
22074
  if (o.agent === "antigravity") {
21854
- for (const dir2 of [join15(o.cwd, ".agents"), join15(o.cwd, ".agents", "skills")]) {
22075
+ for (const dir2 of [join17(o.cwd, ".agents"), join17(o.cwd, ".agents", "skills")]) {
21855
22076
  const st2 = lstatSync7(dir2, { throwIfNoEntry: false });
21856
22077
  if (st2 && (!st2.isDirectory() || st2.isSymbolicLink())) throw new Error("akar skill bukan direktori aman");
21857
22078
  }
21858
22079
  }
21859
22080
  if (lstatSync7(link2, { throwIfNoEntry: false })) continue;
21860
- mkdirSync9(join15(o.cwd, ".agents", "skills"), { recursive: true });
22081
+ mkdirSync10(join17(o.cwd, ".agents", "skills"), { recursive: true });
21861
22082
  symlinkSync(s2.dir, link2);
21862
22083
  ownedLink = link2;
21863
- mkdirSync9(join15(exclude, ".."), { recursive: true });
21864
- const cur = existsSync9(exclude) ? readFileSync11(exclude, "utf8") : "";
22084
+ mkdirSync10(join17(exclude, ".."), { recursive: true });
22085
+ const cur = existsSync11(exclude) ? readFileSync12(exclude, "utf8") : "";
21865
22086
  const line = excludeLine(s2.name);
21866
22087
  const lines2 = cur.split("\n");
21867
22088
  const hasOperatorLine = lines2.some((entry2, index) => entry2 === line && !lines2[index - 1]?.startsWith(`${INJECT_MARKER}:antigravity:`));
21868
22089
  if (o.agent === "antigravity" ? !hasOperatorLine : !lines2.includes(line)) {
21869
22090
  if (o.agent === "antigravity") {
21870
- const hash4 = createHash7("sha256").update(`${link2}\0${s2.dir}`).digest("hex");
22091
+ const hash4 = createHash8("sha256").update(`${link2}\0${s2.dir}`).digest("hex");
21871
22092
  ownedPair = `${INJECT_MARKER}:antigravity:${hash4}
21872
22093
  ${line}
21873
22094
  `;
@@ -21880,9 +22101,9 @@ ${line}
21880
22101
  }
21881
22102
  }
21882
22103
  if (o.agent === "antigravity") {
21883
- mkdirSync9(o.tempDir, { recursive: true, mode: 448 });
22104
+ mkdirSync10(o.tempDir, { recursive: true, mode: 448 });
21884
22105
  const st2 = lstatSync7(link2);
21885
- writeFileSync11(manifestPath(o.tempDir), JSON.stringify([...ownedSkills(o.tempDir), { link: link2, target: s2.dir, exclude, pair: ownedPair, ino: st2.ino, dev: st2.dev }]), { mode: 384 });
22106
+ writeFileSync12(manifestPath(o.tempDir), JSON.stringify([...ownedSkills(o.tempDir), { link: link2, target: s2.dir, exclude, pair: ownedPair, ino: st2.ino, dev: st2.dev }]), { mode: 384 });
21886
22107
  }
21887
22108
  out4.injected.push(s2.name);
21888
22109
  } catch (e) {
@@ -21892,7 +22113,7 @@ ${line}
21892
22113
  } catch {
21893
22114
  }
21894
22115
  try {
21895
- if (ownedPair) writeFileSync11(exclude, readFileSync11(exclude, "utf8").replace(ownedPair, ""));
22116
+ if (ownedPair) writeFileSync12(exclude, readFileSync12(exclude, "utf8").replace(ownedPair, ""));
21896
22117
  } catch {
21897
22118
  }
21898
22119
  }
@@ -21904,9 +22125,9 @@ ${line}
21904
22125
  function unexcludeInjected(repoDir, name2) {
21905
22126
  try {
21906
22127
  const f = excludeFile(repoDir);
21907
- if (!existsSync9(f)) return;
22128
+ if (!existsSync11(f)) return;
21908
22129
  const line = excludeLine(name2);
21909
- writeFileSync11(f, readFileSync11(f, "utf8").split("\n").filter((l) => l !== line).join("\n"));
22130
+ writeFileSync12(f, readFileSync12(f, "utf8").split("\n").filter((l) => l !== line).join("\n"));
21910
22131
  } catch {
21911
22132
  }
21912
22133
  }
@@ -21915,17 +22136,18 @@ var init_skill_inject = __esm({
21915
22136
  "src/services/skill-inject.ts"() {
21916
22137
  "use strict";
21917
22138
  init_src2();
22139
+ init_bundled_skills();
21918
22140
  INJECT_MARKER = "# hanoman:skill-inject";
21919
22141
  excludeLine = (name2) => `/.agents/skills/${name2}`;
21920
- manifestPath = (tempDir) => join15(tempDir, "antigravity-skills.json");
22142
+ manifestPath = (tempDir) => join17(tempDir, "antigravity-skills.json");
21921
22143
  }
21922
22144
  });
21923
22145
 
21924
22146
  // src/services/antigravity-trust.ts
21925
22147
  import { randomUUID as randomUUID2 } from "node:crypto";
21926
- import { closeSync as closeSync2, lstatSync as lstatSync8, mkdirSync as mkdirSync10, openSync as openSync2, readFileSync as readFileSync12, realpathSync as realpathSync6, renameSync as renameSync4, unlinkSync as unlinkSync3, writeFileSync as writeFileSync12 } from "node:fs";
22148
+ import { closeSync as closeSync2, lstatSync as lstatSync8, mkdirSync as mkdirSync11, openSync as openSync2, readFileSync as readFileSync13, realpathSync as realpathSync6, renameSync as renameSync5, unlinkSync as unlinkSync3, writeFileSync as writeFileSync13 } from "node:fs";
21927
22149
  import { homedir as homedir4 } from "node:os";
21928
- import { join as join16 } from "node:path";
22150
+ import { join as join18 } from "node:path";
21929
22151
  function safeOwned(path, directory) {
21930
22152
  const st2 = lstatSync8(path);
21931
22153
  if (st2.isSymbolicLink() || (directory ? !st2.isDirectory() : !st2.isFile()) || process.getuid && st2.uid !== process.getuid()) throw new Error("unsafe path");
@@ -21935,18 +22157,18 @@ function ensureAntigravityTrust(cwd, home3 = homedir4()) {
21935
22157
  let fd;
21936
22158
  try {
21937
22159
  const canonical = realpathSync6(cwd);
21938
- mkdirSync10(home3, { recursive: true });
22160
+ mkdirSync11(home3, { recursive: true });
21939
22161
  const root = realpathSync6(home3);
21940
- for (const dir2 of [join16(root, ".gemini"), join16(root, ".gemini", "antigravity-cli")]) {
21941
- if (!lstatSync8(dir2, { throwIfNoEntry: false })) mkdirSync10(dir2, { mode: 448 });
22162
+ for (const dir2 of [join18(root, ".gemini"), join18(root, ".gemini", "antigravity-cli")]) {
22163
+ if (!lstatSync8(dir2, { throwIfNoEntry: false })) mkdirSync11(dir2, { mode: 448 });
21942
22164
  safeOwned(dir2, true);
21943
22165
  }
21944
- const file = join16(root, ".gemini", "antigravity-cli", "settings.json");
22166
+ const file = join18(root, ".gemini", "antigravity-cli", "settings.json");
21945
22167
  const original = lstatSync8(file, { throwIfNoEntry: false });
21946
22168
  let settings = {};
21947
22169
  if (original) {
21948
22170
  safeOwned(file, false);
21949
- const parsed = JSON.parse(readFileSync12(file, "utf8"));
22171
+ const parsed = JSON.parse(readFileSync13(file, "utf8"));
21950
22172
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("invalid settings");
21951
22173
  settings = parsed;
21952
22174
  }
@@ -21958,14 +22180,14 @@ function ensureAntigravityTrust(cwd, home3 = homedir4()) {
21958
22180
  const candidate = `${file}.${randomUUID2()}.tmp`;
21959
22181
  fd = openSync2(candidate, "wx", 384);
21960
22182
  temp = candidate;
21961
- writeFileSync12(fd, `${JSON.stringify({ ...settings, trustedWorkspaces: [...workspaces, canonical] }, null, 2)}
22183
+ writeFileSync13(fd, `${JSON.stringify({ ...settings, trustedWorkspaces: [...workspaces, canonical] }, null, 2)}
21962
22184
  `);
21963
22185
  closeSync2(fd);
21964
22186
  fd = void 0;
21965
22187
  const latest = lstatSync8(file, { throwIfNoEntry: false });
21966
22188
  if (original ? !latest || latest.ino !== original.ino || latest.mtimeMs !== original.mtimeMs : latest)
21967
22189
  throw new Error("settings changed during preparation");
21968
- renameSync4(temp, file);
22190
+ renameSync5(temp, file);
21969
22191
  temp = void 0;
21970
22192
  } catch {
21971
22193
  throw new Error("antigravity-trust-preparation-failed");
@@ -22015,14 +22237,14 @@ var init_local_binding = __esm({
22015
22237
  });
22016
22238
 
22017
22239
  // src/services/skill-library.ts
22018
- import { join as join17 } from "node:path";
22019
- import { existsSync as existsSync10 } from "node:fs";
22240
+ import { join as join19 } from "node:path";
22241
+ import { existsSync as existsSync12 } from "node:fs";
22020
22242
  async function libraryAll() {
22021
22243
  const global2 = scanGlobalSkills(opts());
22022
22244
  const projects = await prisma.project.findMany({ orderBy: { name: "asc" }, select: { id: true, name: true } });
22023
22245
  const groups = await Promise.all(projects.map(async (p3) => {
22024
22246
  const repo = await resolveRepoDir(p3.id);
22025
- if (!repo || !existsSync10(repo)) return { projectId: p3.id, name: p3.name, skills: [], error: "repo tidak ditemukan" };
22247
+ if (!repo || !existsSync12(repo)) return { projectId: p3.id, name: p3.name, skills: [], error: "repo tidak ditemukan" };
22026
22248
  return { projectId: p3.id, name: p3.name, skills: scanProjectSkills(p3.id, repo, opts()) };
22027
22249
  }));
22028
22250
  return { global: global2, projects: groups };
@@ -22045,17 +22267,17 @@ async function targetParent(layer, source, projectId) {
22045
22267
  if (layer === "user") {
22046
22268
  const s2 = source ?? ".claude";
22047
22269
  if (!USER_SOURCES.includes(s2)) throw new SkillError(400, "source user harus .claude/.codex/.agents/.gemini/antigravity-cli");
22048
- if (s2 === ".agents") return join17(agentsSkillHome(process.env), "skills");
22049
- if (s2 === ".gemini/antigravity-cli") return join17(agentSkillHome("antigravity", process.env), "skills");
22050
- return join17(agentSkillHome(s2 === ".codex" ? "codex" : "claude", process.env), "skills");
22270
+ if (s2 === ".agents") return join19(agentsSkillHome(process.env), "skills");
22271
+ if (s2 === ".gemini/antigravity-cli") return join19(agentSkillHome("antigravity", process.env), "skills");
22272
+ return join19(agentSkillHome(s2 === ".codex" ? "codex" : "claude", process.env), "skills");
22051
22273
  }
22052
22274
  if (layer === "project") {
22053
22275
  if (!projectId) throw new SkillError(400, "projectId wajib untuk lapis project");
22054
22276
  const s2 = source ?? ".claude";
22055
22277
  if (!PROJECT_SOURCES.includes(s2)) throw new SkillError(400, "source project harus .claude/.agents/.codex");
22056
22278
  const repo = await resolveRepoDir(projectId);
22057
- if (!repo || !existsSync10(repo)) throw new SkillError(404, "repo project tidak ditemukan");
22058
- return join17(repo, s2, "skills");
22279
+ if (!repo || !existsSync12(repo)) throw new SkillError(404, "repo project tidak ditemukan");
22280
+ return join19(repo, s2, "skills");
22059
22281
  }
22060
22282
  throw new SkillError(400, "layer harus hanoman, user, atau project");
22061
22283
  }
@@ -35858,7 +36080,7 @@ var require_snapshot_utils = __commonJS({
35858
36080
  var require_snapshot_recorder = __commonJS({
35859
36081
  "../node_modules/.pnpm/undici@7.29.1/node_modules/undici/lib/mock/snapshot-recorder.js"(exports, module) {
35860
36082
  "use strict";
35861
- var { writeFile: writeFile9, readFile: readFile11, mkdir: mkdir13 } = __require("node:fs/promises");
36083
+ var { writeFile: writeFile9, readFile: readFile12, mkdir: mkdir13 } = __require("node:fs/promises");
35862
36084
  var { dirname: dirname20, resolve: resolve25 } = __require("node:path");
35863
36085
  var { setTimeout: setTimeout2, clearTimeout: clearTimeout2 } = __require("node:timers");
35864
36086
  var { InvalidArgumentError, UndiciError } = require_errors();
@@ -36060,7 +36282,7 @@ var require_snapshot_recorder = __commonJS({
36060
36282
  throw new InvalidArgumentError("Snapshot path is required");
36061
36283
  }
36062
36284
  try {
36063
- const data = await readFile11(resolve25(path), "utf8");
36285
+ const data = await readFile12(resolve25(path), "utf8");
36064
36286
  const parsed = JSON.parse(data);
36065
36287
  if (Array.isArray(parsed)) {
36066
36288
  this.#snapshots.clear();
@@ -47911,7 +48133,7 @@ ${captureLines}` : capture.stack;
47911
48133
  module.exports.mockErrors = mockErrors;
47912
48134
  var { EventSource } = require_eventsource();
47913
48135
  module.exports.EventSource = EventSource;
47914
- function install() {
48136
+ function install2() {
47915
48137
  globalThis.fetch = module.exports.fetch;
47916
48138
  globalThis.Headers = module.exports.Headers;
47917
48139
  globalThis.Response = module.exports.Response;
@@ -47923,7 +48145,7 @@ ${captureLines}` : capture.stack;
47923
48145
  globalThis.MessageEvent = module.exports.MessageEvent;
47924
48146
  globalThis.EventSource = module.exports.EventSource;
47925
48147
  }
47926
- module.exports.install = install;
48148
+ module.exports.install = install2;
47927
48149
  }
47928
48150
  });
47929
48151
 
@@ -48169,7 +48391,7 @@ var init_safe_outbound_request = __esm({
48169
48391
  // src/services/uploads.ts
48170
48392
  import { randomUUID as randomUUID3 } from "node:crypto";
48171
48393
  import { mkdir, writeFile, readFile as readFile2, unlink, rm } from "node:fs/promises";
48172
- import { join as join18, resolve as resolve9 } from "node:path";
48394
+ import { join as join20, resolve as resolve9 } from "node:path";
48173
48395
  function extFor(mimeType) {
48174
48396
  return EXT[mimeType] ?? ".bin";
48175
48397
  }
@@ -48180,17 +48402,17 @@ async function saveUpload(buf, mimeType) {
48180
48402
  const dir2 = uploadDir();
48181
48403
  await mkdir(dir2, { recursive: true, mode: 448 });
48182
48404
  const storageKey = `${randomUUID3()}${extFor(mimeType)}`;
48183
- await writeFile(join18(dir2, storageKey), buf, { mode: 384 });
48405
+ await writeFile(join20(dir2, storageKey), buf, { mode: 384 });
48184
48406
  return { storageKey, size: buf.length };
48185
48407
  }
48186
48408
  function sessionUploadDir(sessionId2) {
48187
48409
  if (!SESSION_ID.test(sessionId2)) throw new Error(`sessionId tak sah: ${sessionId2}`);
48188
- return join18(uploadDir(), "terminal", sessionId2);
48410
+ return join20(uploadDir(), "terminal", sessionId2);
48189
48411
  }
48190
48412
  async function saveSessionUpload(sessionId2, buf, mimeType) {
48191
48413
  const dir2 = sessionUploadDir(sessionId2);
48192
48414
  await mkdir(dir2, { recursive: true, mode: 448 });
48193
- const path = join18(dir2, `${randomUUID3()}${extFor(mimeType)}`);
48415
+ const path = join20(dir2, `${randomUUID3()}${extFor(mimeType)}`);
48194
48416
  await writeFile(path, buf, { mode: 384 });
48195
48417
  return { path, size: buf.length };
48196
48418
  }
@@ -48206,11 +48428,11 @@ async function dropSessionUploads(sessionId2) {
48206
48428
  }
48207
48429
  async function readUpload(storageKey) {
48208
48430
  const safe3 = storageKey.replace(/[/\\]/g, "");
48209
- return readFile2(join18(uploadDir(), safe3));
48431
+ return readFile2(join20(uploadDir(), safe3));
48210
48432
  }
48211
48433
  async function readUploadOrFetch(storageKey) {
48212
48434
  const safe3 = storageKey.replace(/[/\\]/g, "");
48213
- const target3 = join18(uploadDir(), safe3);
48435
+ const target3 = join20(uploadDir(), safe3);
48214
48436
  try {
48215
48437
  return await readFile2(target3);
48216
48438
  } catch {
@@ -48237,7 +48459,7 @@ async function readUploadOrFetch(storageKey) {
48237
48459
  }
48238
48460
  async function deleteUpload(storageKey) {
48239
48461
  const safe3 = storageKey.replace(/[/\\]/g, "");
48240
- await unlink(join18(uploadDir(), safe3)).catch(() => {
48462
+ await unlink(join20(uploadDir(), safe3)).catch(() => {
48241
48463
  });
48242
48464
  }
48243
48465
  var EXT, SESSION_ID;
@@ -48680,14 +48902,14 @@ var init_session_sandbox = __esm({
48680
48902
  });
48681
48903
 
48682
48904
  // src/services/session-event-spool.ts
48683
- import { join as join19 } from "node:path";
48905
+ import { join as join21 } from "node:path";
48684
48906
  var sessionEventSpoolRoot, sessionEventDir;
48685
48907
  var init_session_event_spool = __esm({
48686
48908
  "src/services/session-event-spool.ts"() {
48687
48909
  "use strict";
48688
48910
  init_src2();
48689
- sessionEventSpoolRoot = (env = process.env) => join19(resolveHome(env), "session-events");
48690
- sessionEventDir = (sessionId2, env = process.env) => join19(sessionEventSpoolRoot(env), sessionId2);
48911
+ sessionEventSpoolRoot = (env = process.env) => join21(resolveHome(env), "session-events");
48912
+ sessionEventDir = (sessionId2, env = process.env) => join21(sessionEventSpoolRoot(env), sessionId2);
48691
48913
  }
48692
48914
  });
48693
48915
 
@@ -48711,9 +48933,9 @@ import { spawn } from "node-pty";
48711
48933
  import { execFile as execFile2, execFileSync as execFileSync5 } from "node:child_process";
48712
48934
  import { createRequire } from "node:module";
48713
48935
  import { randomUUID as randomUUID4 } from "node:crypto";
48714
- import { chmodSync as chmodSync7, mkdirSync as mkdirSync11, readFileSync as readFileSync13, rmSync as rmSync6, statSync as statSync3, writeFileSync as writeFileSync13 } from "node:fs";
48936
+ import { chmodSync as chmodSync8, mkdirSync as mkdirSync12, readFileSync as readFileSync14, rmSync as rmSync7, statSync as statSync4, writeFileSync as writeFileSync14 } from "node:fs";
48715
48937
  import { stat as stat2 } from "node:fs/promises";
48716
- import { dirname as dirname10, resolve as resolvePath } from "node:path";
48938
+ import { dirname as dirname11, resolve as resolvePath } from "node:path";
48717
48939
  import { tmpdir as tmpdir4 } from "node:os";
48718
48940
  function doneSeenOf(id2) {
48719
48941
  const known2 = phaseDoneSeen.get(id2);
@@ -48774,8 +48996,8 @@ function fitCustomAgentsToArgBudget(customDefs, render, limit = AGENTS_ARG_SAFE_
48774
48996
  }
48775
48997
  function noTtyPromptEnv() {
48776
48998
  const path = askpassDenyPath();
48777
- mkdirSync11(dirname10(path), { recursive: true, mode: 448 });
48778
- writeFileSync13(path, ASKPASS_DENY, { mode: 448 });
48999
+ mkdirSync12(dirname11(path), { recursive: true, mode: 448 });
49000
+ writeFileSync14(path, ASKPASS_DENY, { mode: 448 });
48779
49001
  return { SSH_ASKPASS: path, SSH_ASKPASS_REQUIRE: "force", GIT_TERMINAL_PROMPT: "0" };
48780
49002
  }
48781
49003
  function hanomanDbInheritUnsets(env = process.env) {
@@ -49028,8 +49250,8 @@ function createSession(projectId, cwd, opts2 = {}) {
49028
49250
  phaseDoneSeen.delete(id2);
49029
49251
  const eventDir = opts2.command ? void 0 : sessionEventDir(id2);
49030
49252
  if (eventDir) {
49031
- mkdirSync11(eventDir, { recursive: true, mode: 448 });
49032
- chmodSync7(eventDir, 448);
49253
+ mkdirSync12(eventDir, { recursive: true, mode: 448 });
49254
+ chmodSync8(eventDir, 448);
49033
49255
  }
49034
49256
  const agentForDefs = opts2.agent ?? "claude";
49035
49257
  const sessionEffort = agentForDefs === "codex" && opts2.model && opts2.effort ? coerceCodexEffort(opts2.model, opts2.effort) : opts2.effort;
@@ -49073,7 +49295,7 @@ function createSession(projectId, cwd, opts2 = {}) {
49073
49295
  if (customDefs.length > 0 || requestedPhaseDefs.length > 0) {
49074
49296
  const tempDir = agentTempDir(id2);
49075
49297
  agentConfigDir = tempDir;
49076
- mkdirSync11(tempDir, { recursive: true, mode: 448 });
49298
+ mkdirSync12(tempDir, { recursive: true, mode: 448 });
49077
49299
  let hook;
49078
49300
  const readOnlyHookFor = () => customDefs.some((def2) => def2.workspacePolicy === "read-only") ? hook ??= writeReadOnlyHook(tempDir) : void 0;
49079
49301
  const attempt = (requested) => {
@@ -49088,16 +49310,16 @@ function createSession(projectId, cwd, opts2 = {}) {
49088
49310
  let phaseContextFile;
49089
49311
  if (shared !== void 0) {
49090
49312
  const path = phaseContextFilePath(id2);
49091
- writeFileSync13(path, shared, { mode: 384 });
49092
- chmodSync7(path, 384);
49313
+ writeFileSync14(path, shared, { mode: 384 });
49314
+ chmodSync8(path, 384);
49093
49315
  phaseContextFile = { context: shared, path };
49094
49316
  }
49095
49317
  const agentContractFiles = [];
49096
49318
  for (const policy of new Set(customDefs.map((d) => d.workspacePolicy ?? "inherit"))) {
49097
49319
  const path = agentContractFilePath(id2, policy);
49098
49320
  const content = agentContractOf(policy);
49099
- writeFileSync13(path, content, { mode: 384 });
49100
- chmodSync7(path, 384);
49321
+ writeFileSync14(path, content, { mode: 384 });
49322
+ chmodSync8(path, 384);
49101
49323
  agentContractFiles.push({ policy, content, path });
49102
49324
  }
49103
49325
  const fit = fitCustomAgentsToArgBudget(customDefs, (kept) => renderAgentsJson(
@@ -49119,7 +49341,7 @@ function createSession(projectId, cwd, opts2 = {}) {
49119
49341
  customDefs = fit.kept;
49120
49342
  defs = [...withPhaseDelegation(requested, customDefs, agentForDefs), ...customDefs];
49121
49343
  if (defs.length === 0) return [];
49122
- writeFileSync13(file, fit.json, { mode: 384 });
49344
+ writeFileSync14(file, fit.json, { mode: 384 });
49123
49345
  } catch (error) {
49124
49346
  if (phaseDefs.length === 0) throw error;
49125
49347
  return [error instanceof Error ? error.message : String(error)];
@@ -49199,8 +49421,8 @@ function createSession(projectId, cwd, opts2 = {}) {
49199
49421
  const sessionPrompt = bornPrompt(orchestrated);
49200
49422
  if (!opts2.command && sessionPrompt) {
49201
49423
  promptFile = promptFilePath(id2);
49202
- mkdirSync11(dirname10(promptFile), { recursive: true, mode: 448 });
49203
- writeFileSync13(promptFile, sessionPrompt + rosterBlock, { mode: 384 });
49424
+ mkdirSync12(dirname11(promptFile), { recursive: true, mode: 448 });
49425
+ writeFileSync14(promptFile, sessionPrompt + rosterBlock, { mode: 384 });
49204
49426
  promptArg = `"$(cat ${sq(promptFile)})"`;
49205
49427
  }
49206
49428
  const agent = opts2.agent ?? "claude";
@@ -49211,8 +49433,8 @@ function createSession(projectId, cwd, opts2 = {}) {
49211
49433
  let goalGate;
49212
49434
  if (agent === "codex" && opts2.goal && opts2.flow && opts2.specId) {
49213
49435
  goalGate = goalGatePath(id2);
49214
- mkdirSync11(dirname10(goalGate), { recursive: true, mode: 448 });
49215
- writeFileSync13(goalGate, codexGoalScript({
49436
+ mkdirSync12(dirname11(goalGate), { recursive: true, mode: 448 });
49437
+ writeFileSync14(goalGate, codexGoalScript({
49216
49438
  flow: opts2.flow,
49217
49439
  specId: opts2.specId,
49218
49440
  condition: opts2.goal,
@@ -49259,7 +49481,7 @@ function createSession(projectId, cwd, opts2 = {}) {
49259
49481
  for (const [k, v] of Object.entries(sessionEventEnv(id2))) envPairs.push(`${k}=${sq(v)}`);
49260
49482
  }
49261
49483
  if (opts2.phaseFile) {
49262
- mkdirSync11(dirname10(opts2.phaseFile), { recursive: true });
49484
+ mkdirSync12(dirname11(opts2.phaseFile), { recursive: true });
49263
49485
  envPairs.push(`HANOMAN_PHASE_FILE=${sq(opts2.phaseFile)}`);
49264
49486
  }
49265
49487
  if (!opts2.command) {
@@ -49288,7 +49510,7 @@ function createSession(projectId, cwd, opts2 = {}) {
49288
49510
  });
49289
49511
  cmd = wrapped;
49290
49512
  }
49291
- if (opts2.decisionFile) mkdirSync11(dirname10(opts2.decisionFile), { recursive: true });
49513
+ if (opts2.decisionFile) mkdirSync12(dirname11(opts2.decisionFile), { recursive: true });
49292
49514
  const bootstrap = runtime === "antigravity" ? createAntigravityEnvBootstrap(cmd, agentEnvironment(runtime, { ...process.env, ...opts2.env })) : void 0;
49293
49515
  if (bootstrap) cmd = bootstrap.command;
49294
49516
  try {
@@ -49695,11 +49917,11 @@ function killSession(id2) {
49695
49917
  cleanupAntigravityAgents({ cwd: p3.cwd, sessionId: id2 });
49696
49918
  cleanupInjectedSkills(agentTempDir(id2));
49697
49919
  try {
49698
- rmSync6(agentTempDir(id2), { recursive: true, force: true });
49920
+ rmSync7(agentTempDir(id2), { recursive: true, force: true });
49699
49921
  } catch {
49700
49922
  }
49701
49923
  try {
49702
- rmSync6(sessionEventDir(id2), { recursive: true, force: true });
49924
+ rmSync7(sessionEventDir(id2), { recursive: true, force: true });
49703
49925
  } catch {
49704
49926
  }
49705
49927
  emitDeath({ sessionId: id2, exitCode: p3.exited ? p3.code : null, transcript });
@@ -49758,7 +49980,7 @@ var init_pty = __esm({
49758
49980
  POLL_MS = 500;
49759
49981
  markerFilled = (f) => {
49760
49982
  try {
49761
- return statSync3(f).size > 0;
49983
+ return statSync4(f).size > 0;
49762
49984
  } catch {
49763
49985
  return false;
49764
49986
  }
@@ -49772,7 +49994,7 @@ var init_pty = __esm({
49772
49994
  };
49773
49995
  clearMarker = (f) => {
49774
49996
  try {
49775
- writeFileSync13(f, "");
49997
+ writeFileSync14(f, "");
49776
49998
  } catch {
49777
49999
  }
49778
50000
  };
@@ -49782,7 +50004,7 @@ var init_pty = __esm({
49782
50004
  markerOnset = (f) => {
49783
50005
  let raw;
49784
50006
  try {
49785
- raw = readFileSync13(f, "utf8");
50007
+ raw = readFileSync14(f, "utf8");
49786
50008
  } catch {
49787
50009
  return 0;
49788
50010
  }
@@ -50036,7 +50258,7 @@ var init_redact_known = __esm({
50036
50258
  // src/services/transcript-store.ts
50037
50259
  import { randomUUID as randomUUID5 } from "node:crypto";
50038
50260
  import { mkdir as mkdir2, writeFile as writeFile2, readFile as readFile3, unlink as unlink2, readdir as readdir2, stat as stat3 } from "node:fs/promises";
50039
- import { join as join20, resolve as resolve10, basename as basename6 } from "node:path";
50261
+ import { join as join22, resolve as resolve10, basename as basename6 } from "node:path";
50040
50262
  function transcriptDir() {
50041
50263
  return resolve10(effectiveStr("HANOMAN_TRANSCRIPT_DIR")?.trim() || resolveDataDirs().transcripts);
50042
50264
  }
@@ -50056,13 +50278,13 @@ async function saveTranscript(text2) {
50056
50278
  const dir2 = transcriptDir();
50057
50279
  await mkdir2(dir2, { recursive: true, mode: 448 });
50058
50280
  const key = `${randomUUID5()}.log`;
50059
- await writeFile2(join20(dir2, key), body, { encoding: "utf8", mode: 384 });
50281
+ await writeFile2(join22(dir2, key), body, { encoding: "utf8", mode: 384 });
50060
50282
  return { key, bytes: Buffer.byteLength(body, "utf8"), truncated };
50061
50283
  }
50062
50284
  async function readTranscript(key) {
50063
50285
  if (!key) return null;
50064
50286
  try {
50065
- return await readFile3(join20(transcriptDir(), basename6(key)), "utf8");
50287
+ return await readFile3(join22(transcriptDir(), basename6(key)), "utf8");
50066
50288
  } catch {
50067
50289
  return null;
50068
50290
  }
@@ -50070,7 +50292,7 @@ async function readTranscript(key) {
50070
50292
  async function deleteTranscript(key) {
50071
50293
  if (!key) return;
50072
50294
  try {
50073
- await unlink2(join20(transcriptDir(), basename6(key)));
50295
+ await unlink2(join22(transcriptDir(), basename6(key)));
50074
50296
  } catch (e) {
50075
50297
  if (e.code !== "ENOENT") throw e;
50076
50298
  }
@@ -50088,7 +50310,7 @@ async function listTranscripts() {
50088
50310
  for (const name2 of names) {
50089
50311
  if (!name2.endsWith(".log")) continue;
50090
50312
  try {
50091
- rows.push({ key: name2, mtimeMs: (await stat3(join20(dir2, name2))).mtimeMs });
50313
+ rows.push({ key: name2, mtimeMs: (await stat3(join22(dir2, name2))).mtimeMs });
50092
50314
  } catch {
50093
50315
  }
50094
50316
  }
@@ -55381,13 +55603,13 @@ var init_tombstone = __esm({
55381
55603
  });
55382
55604
 
55383
55605
  // src/services/qa-attachment-sync.ts
55384
- import { createHash as createHash8, randomUUID as randomUUID7 } from "node:crypto";
55606
+ import { createHash as createHash9, randomUUID as randomUUID7 } from "node:crypto";
55385
55607
  import { chmod, mkdir as mkdir4, rename as rename2, stat as stat4, writeFile as writeFile4 } from "node:fs/promises";
55386
- import { join as join22 } from "node:path";
55608
+ import { join as join24 } from "node:path";
55387
55609
  async function qaBytesPresent(storageKey) {
55388
55610
  if (!QA_STORAGE_KEY.test(storageKey)) return false;
55389
55611
  try {
55390
- return (await stat4(join22(uploadDir(), storageKey))).isFile();
55612
+ return (await stat4(join24(uploadDir(), storageKey))).isFile();
55391
55613
  } catch {
55392
55614
  return false;
55393
55615
  }
@@ -55402,7 +55624,7 @@ async function settleNewQaAttachment(id2) {
55402
55624
  }
55403
55625
  async function verifyQaBytes(meta, buf) {
55404
55626
  if (buf.length !== meta.size || buf.length === 0 || buf.length > QA_SYNC_MAX_BYTES) return { ok: false, status: 400, error: "size" };
55405
- if (createHash8("sha256").update(buf).digest("hex") !== meta.sha256) return { ok: false, status: 400, error: "sha256" };
55627
+ if (createHash9("sha256").update(buf).digest("hex") !== meta.sha256) return { ok: false, status: 400, error: "sha256" };
55406
55628
  if (SNIFFABLE2.has(meta.mimeType)) {
55407
55629
  if ((await fileTypeFromBuffer(buf))?.mime !== meta.mimeType) return { ok: false, status: 415, error: "type" };
55408
55630
  } else if (!isUtf8Text2(buf)) return { ok: false, status: 415, error: "type" };
@@ -55412,10 +55634,10 @@ async function storeQaBytes(storageKey, buf) {
55412
55634
  if (!QA_STORAGE_KEY.test(storageKey)) throw new Error("storageKey tak sah");
55413
55635
  const dir2 = uploadDir();
55414
55636
  await mkdir4(dir2, { recursive: true, mode: 448 });
55415
- const tmp = join22(dir2, `${storageKey}.tmp-${randomUUID7()}`);
55637
+ const tmp = join24(dir2, `${storageKey}.tmp-${randomUUID7()}`);
55416
55638
  await writeFile4(tmp, buf, { mode: 384 });
55417
- await rename2(tmp, join22(dir2, storageKey));
55418
- await chmod(join22(dir2, storageKey), 384).catch(() => {
55639
+ await rename2(tmp, join24(dir2, storageKey));
55640
+ await chmod(join24(dir2, storageKey), 384).catch(() => {
55419
55641
  });
55420
55642
  }
55421
55643
  var QA_STORAGE_KEY, QA_SYNC_MAX_BYTES, SNIFFABLE2, isUtf8Text2;
@@ -55659,9 +55881,9 @@ async function pull(sinceCursor, limit = 500, maxBytes = PULL_MAX_BYTES, opts2 =
55659
55881
  async function bootstrapSnapshot(after, maxBytes = PULL_MAX_BYTES, opts2 = {}) {
55660
55882
  const tip = await prisma.syncLog.findFirst({ orderBy: { seq: "desc" }, select: { seq: true } });
55661
55883
  const cursor = tip ? String(tip.seq) : "0";
55662
- const sep9 = after ? after.indexOf(":") : -1;
55663
- const afterEntity = sep9 > 0 ? after.slice(0, sep9) : null;
55664
- const afterId = sep9 > 0 ? after.slice(sep9 + 1) : null;
55884
+ const sep11 = after ? after.indexOf(":") : -1;
55885
+ const afterEntity = sep11 > 0 ? after.slice(0, sep11) : null;
55886
+ const afterId = sep11 > 0 ? after.slice(sep11 + 1) : null;
55665
55887
  const startAt = afterEntity ? BOOTSTRAP_ORDER.indexOf(afterEntity) : 0;
55666
55888
  if (startAt < 0) throw new Error("bootstrap cursor tak dikenal");
55667
55889
  const records = [];
@@ -56066,9 +56288,9 @@ var init_sync_notify = __esm({
56066
56288
  // src/services/safe-repo-path.ts
56067
56289
  import { constants as constants3 } from "node:fs";
56068
56290
  import { lstat, mkdir as mkdir5, open as open2, realpath, rename as rename3, unlink as unlink4 } from "node:fs/promises";
56069
- import { isAbsolute as isAbsolute10, join as join23, relative as relative5, resolve as resolve13, sep as sep6 } from "node:path";
56291
+ import { isAbsolute as isAbsolute10, join as join25, relative as relative6, resolve as resolve13, sep as sep7 } from "node:path";
56070
56292
  import { randomUUID as randomUUID8 } from "node:crypto";
56071
- import { closeSync as closeSync3, fstatSync as fstatSync2, lstatSync as lstatSync9, mkdirSync as mkdirSync12, openSync as openSync3, readFileSync as readFileSync14, realpathSync as realpathSync7, renameSync as renameSync5, unlinkSync as unlinkSync4, writeFileSync as writeFileSync14 } from "node:fs";
56293
+ import { closeSync as closeSync3, fstatSync as fstatSync2, lstatSync as lstatSync9, mkdirSync as mkdirSync13, openSync as openSync3, readFileSync as readFileSync15, realpathSync as realpathSync7, renameSync as renameSync6, unlinkSync as unlinkSync4, writeFileSync as writeFileSync15 } from "node:fs";
56072
56294
  function components(rel) {
56073
56295
  if (!rel || isAbsolute10(rel) || rel.includes("\0")) denied("path invalid");
56074
56296
  const parts = rel.replace(/\\/g, "/").split("/");
@@ -56082,15 +56304,15 @@ async function canonicalRoot(root) {
56082
56304
  return out4;
56083
56305
  }
56084
56306
  function beneath(root, candidate) {
56085
- const rel = relative5(root, candidate);
56086
- return rel === "" || !rel.startsWith(`..${sep6}`) && rel !== ".." && !isAbsolute10(rel);
56307
+ const rel = relative6(root, candidate);
56308
+ return rel === "" || !rel.startsWith(`..${sep7}`) && rel !== ".." && !isAbsolute10(rel);
56087
56309
  }
56088
56310
  async function ensureRepoParents(root, rel) {
56089
56311
  const base3 = await canonicalRoot(root);
56090
56312
  const parts = components(rel).slice(0, -1);
56091
56313
  let current = base3;
56092
56314
  for (const part of parts) {
56093
- current = join23(current, part);
56315
+ current = join25(current, part);
56094
56316
  try {
56095
56317
  await mkdir5(current, { mode: 448 });
56096
56318
  } catch (error) {
@@ -56107,9 +56329,9 @@ function ensureRepoParentsSync(root, rel) {
56107
56329
  const parts = components(rel).slice(0, -1);
56108
56330
  let current = base3;
56109
56331
  for (const part of parts) {
56110
- current = join23(current, part);
56332
+ current = join25(current, part);
56111
56333
  try {
56112
- mkdirSync12(current, { mode: 448 });
56334
+ mkdirSync13(current, { mode: 448 });
56113
56335
  } catch (error) {
56114
56336
  if (error.code !== "EEXIST") throw error;
56115
56337
  }
@@ -56123,7 +56345,7 @@ async function resolveRepoEntry(root, rel, opts2 = {}) {
56123
56345
  const parts = components(rel);
56124
56346
  let current = base3;
56125
56347
  for (let i = 0; i < parts.length; i++) {
56126
- current = join23(current, parts[i]);
56348
+ current = join25(current, parts[i]);
56127
56349
  const stat8 = await lstat(current).catch((error) => {
56128
56350
  if (error.code === "ENOENT" && opts2.allowMissingTail) return null;
56129
56351
  if (error.code === "ENOENT" && opts2.allowMissingFinal && i === parts.length - 1) return null;
@@ -56144,7 +56366,7 @@ function assertSafeRepoPathSync(root, rel, allowMissingFinal = false, allowMissi
56144
56366
  const parts = components(rel);
56145
56367
  let current = base3;
56146
56368
  for (let i = 0; i < parts.length; i++) {
56147
- current = join23(current, parts[i]);
56369
+ current = join25(current, parts[i]);
56148
56370
  try {
56149
56371
  const stat8 = lstatSync9(current);
56150
56372
  if (stat8.isSymbolicLink()) denied("symlink");
@@ -56163,7 +56385,7 @@ function readRepoFileSync(root, rel) {
56163
56385
  const fd = openSync3(path, constants3.O_RDONLY | (constants3.O_NOFOLLOW ?? 0));
56164
56386
  try {
56165
56387
  if (!fstatSync2(fd).isFile()) denied("bukan file regular");
56166
- return readFileSync14(fd);
56388
+ return readFileSync15(fd);
56167
56389
  } finally {
56168
56390
  closeSync3(fd);
56169
56391
  }
@@ -56172,12 +56394,12 @@ function writeRepoFileAtomicSync(root, rel, data) {
56172
56394
  ensureRepoParentsSync(root, rel);
56173
56395
  const path = assertSafeRepoPathSync(root, rel, true);
56174
56396
  const parent = resolve13(path, "..");
56175
- const parentRel = relative5(realpathSync7(root), parent);
56397
+ const parentRel = relative6(realpathSync7(root), parent);
56176
56398
  if (parentRel) assertSafeRepoPathSync(root, parentRel);
56177
- const temp = join23(parent, `.hanoman-${randomUUID8()}.tmp`);
56399
+ const temp = join25(parent, `.hanoman-${randomUUID8()}.tmp`);
56178
56400
  const fd = openSync3(temp, constants3.O_WRONLY | constants3.O_CREAT | constants3.O_EXCL | (constants3.O_NOFOLLOW ?? 0), 384);
56179
56401
  try {
56180
- writeFileSync14(fd, data);
56402
+ writeFileSync15(fd, data);
56181
56403
  } finally {
56182
56404
  closeSync3(fd);
56183
56405
  }
@@ -56190,7 +56412,7 @@ function writeRepoFileAtomicSync(root, rel, data) {
56190
56412
  }
56191
56413
  })();
56192
56414
  if (final?.isSymbolicLink() || final && !final.isFile()) denied("target berubah");
56193
- renameSync5(temp, path);
56415
+ renameSync6(temp, path);
56194
56416
  } catch (error) {
56195
56417
  try {
56196
56418
  unlinkSync4(temp);
@@ -56221,7 +56443,7 @@ async function writeRepoFileAtomic(root, rel, data) {
56221
56443
  await mkdir5(entry2.parent, { recursive: false, mode: 448 }).catch((error) => {
56222
56444
  if (error.code !== "EEXIST") throw error;
56223
56445
  });
56224
- const temp = join23(entry2.parent, `.hanoman-${randomUUID8()}.tmp`);
56446
+ const temp = join25(entry2.parent, `.hanoman-${randomUUID8()}.tmp`);
56225
56447
  const handle = await open2(temp, constants3.O_WRONLY | constants3.O_CREAT | constants3.O_EXCL | (constants3.O_NOFOLLOW ?? 0), 384);
56226
56448
  try {
56227
56449
  await handle.writeFile(data);
@@ -56257,8 +56479,8 @@ var init_safe_repo_path = __esm({
56257
56479
  // src/services/integrate.ts
56258
56480
  import { execFile as execFile5 } from "node:child_process";
56259
56481
  import { promisify as promisify4 } from "node:util";
56260
- import { rmSync as rmSync8 } from "node:fs";
56261
- import { join as join24 } from "node:path";
56482
+ import { rmSync as rmSync9 } from "node:fs";
56483
+ import { join as join26 } from "node:path";
56262
56484
  async function worktreeForBranch(repoDir, branch) {
56263
56485
  let path = null;
56264
56486
  for (const line of (await out(repoDir, ["worktree", "list", "--porcelain"])).split("\n")) {
@@ -56283,7 +56505,7 @@ async function resolveTarget(repoDir, target3) {
56283
56505
  async function reclaim(repoDir, wt) {
56284
56506
  await sh(repoDir, ["worktree", "remove", "--force", wt]);
56285
56507
  await sh(repoDir, ["worktree", "prune"]);
56286
- rmSync8(wt, { recursive: true, force: true });
56508
+ rmSync9(wt, { recursive: true, force: true });
56287
56509
  }
56288
56510
  async function integrateBranch(repoDir, src, op, target3) {
56289
56511
  const source = await resolveSource(repoDir, src.branch);
@@ -56291,7 +56513,7 @@ async function integrateBranch(repoDir, src, op, target3) {
56291
56513
  const tgt = await resolveTarget(repoDir, target3);
56292
56514
  if (!tgt) return { status: "error", code: 400, error: `target "${target3}" tidak dikenal` };
56293
56515
  await sh(repoDir, ["fetch", "origin"]);
56294
- const wt = join24(repoDir, ".worktrees", `merge-${sanitize(src.mergeId)}`);
56516
+ const wt = join26(repoDir, ".worktrees", `merge-${sanitize(src.mergeId)}`);
56295
56517
  await reclaim(repoDir, wt);
56296
56518
  const baseRef = op === "merge" ? tgt.ref : source;
56297
56519
  const baseSha = await out(repoDir, ["rev-parse", "--verify", "--end-of-options", `${baseRef}^{commit}`]);
@@ -56352,7 +56574,7 @@ async function mergeIntoCurrent(repoDir, source, opts2 = {}) {
56352
56574
  const src = await resolveGraphSource(repoDir, source);
56353
56575
  if (!src) return { status: "error", code: 400, error: `source "${source}" tak dikenal` };
56354
56576
  await sh(repoDir, ["fetch", "origin"]);
56355
- const wt = join24(repoDir, ".worktrees", `merge-${sanitize(current)}`);
56577
+ const wt = join26(repoDir, ".worktrees", `merge-${sanitize(current)}`);
56356
56578
  await reclaim(repoDir, wt);
56357
56579
  const baseSha = await out(repoDir, ["rev-parse", "--verify", "--end-of-options", `refs/heads/${current}^{commit}`]);
56358
56580
  if (!await ok(repoDir, ["worktree", "add", "--detach", "-q", wt, baseSha]))
@@ -56390,7 +56612,7 @@ async function replayCurrent(repoDir, source, cmd) {
56390
56612
  const current = await out(repoDir, ["rev-parse", "--abbrev-ref", "HEAD"]);
56391
56613
  if (!current || current === "HEAD")
56392
56614
  return { status: "error", code: 409, error: "HEAD detached \u2014 checkout sebuah branch dulu" };
56393
- const wt = join24(repoDir, ".worktrees", `merge-${sanitize(current)}`);
56615
+ const wt = join26(repoDir, ".worktrees", `merge-${sanitize(current)}`);
56394
56616
  await reclaim(repoDir, wt);
56395
56617
  const baseSha = await out(repoDir, ["rev-parse", "--verify", "--end-of-options", `refs/heads/${current}^{commit}`]);
56396
56618
  if (!await ok(repoDir, ["worktree", "add", "--detach", "-q", wt, baseSha]))
@@ -56450,11 +56672,11 @@ import { execFile as execFile6 } from "node:child_process";
56450
56672
  import { promisify as promisify5 } from "node:util";
56451
56673
  import { mkdtemp, copyFile, rm as rm2 } from "node:fs/promises";
56452
56674
  import { tmpdir as tmpdir5 } from "node:os";
56453
- import { join as join25, resolve as resolve15 } from "node:path";
56675
+ import { join as join27, resolve as resolve15 } from "node:path";
56454
56676
  async function withTempIndex(wt, fn) {
56455
56677
  const idx = (await exec4("git", ["rev-parse", "--git-path", "index"], { cwd: wt, ...GIT3 })).stdout.trim();
56456
- const dir2 = await mkdtemp(join25(tmpdir5(), "hanoman-idx-"));
56457
- const tmp = join25(dir2, "index");
56678
+ const dir2 = await mkdtemp(join27(tmpdir5(), "hanoman-idx-"));
56679
+ const tmp = join27(dir2, "index");
56458
56680
  await copyFile(resolve15(wt, idx), tmp);
56459
56681
  const env = { ...process.env, GIT_INDEX_FILE: tmp };
56460
56682
  try {
@@ -56600,7 +56822,7 @@ var init_spec_review2 = __esm({
56600
56822
  GIT3 = { maxBuffer: 1 << 24 };
56601
56823
  MAX = 256 * 1024;
56602
56824
  emptySpecReview = (reason) => ({ base: "", files: [], changed: [], empty: true, reason });
56603
- worktreeDir = (repoDir, specId) => join25(repoDir, ".worktrees", specId.toLowerCase().replace(/[^a-z0-9_-]/g, "_"));
56825
+ worktreeDir = (repoDir, specId) => join27(repoDir, ".worktrees", specId.toLowerCase().replace(/[^a-z0-9_-]/g, "_"));
56604
56826
  splitZ = (s2) => s2.split("\0").filter(Boolean);
56605
56827
  EMPTY_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
56606
56828
  }
@@ -56908,7 +57130,7 @@ var init_client = __esm({
56908
57130
  });
56909
57131
 
56910
57132
  // src/services/telegram/protocol.ts
56911
- import { createHash as createHash10 } from "node:crypto";
57133
+ import { createHash as createHash11 } from "node:crypto";
56912
57134
  function normalizeTelegramCommand(text2) {
56913
57135
  const clean3 = text2.trim();
56914
57136
  const match = clean3.match(/^\/([a-z0-9_]+)(?:@[a-z0-9_]+)?(?:\s+([\s\S]*))?$/i);
@@ -56997,7 +57219,7 @@ function parseTelegramUpdate(update2, allowlist, maxText = 12e3) {
56997
57219
  };
56998
57220
  }
56999
57221
  function inboundDigest(input) {
57000
- return createHash10("sha256").update(`${input.updateId}\0${input.chatId}\0${input.userId}\0${input.kind}\0${input.text}`, "utf8").digest("hex");
57222
+ return createHash11("sha256").update(`${input.updateId}\0${input.chatId}\0${input.userId}\0${input.kind}\0${input.text}`, "utf8").digest("hex");
57001
57223
  }
57002
57224
  function sanitizeTelegramOutput(text2, exactSecrets = []) {
57003
57225
  let clean3 = text2.replace(ANSI, "").replace(CONTROLS, "");
@@ -57300,7 +57522,7 @@ var init_notifications2 = __esm({
57300
57522
  });
57301
57523
 
57302
57524
  // src/services/codex-trust.ts
57303
- import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync13, readFileSync as readFileSync15, realpathSync as realpathSync9 } from "node:fs";
57525
+ import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync14, readFileSync as readFileSync16, realpathSync as realpathSync9 } from "node:fs";
57304
57526
  import { homedir as homedir5 } from "node:os";
57305
57527
  function ensureCodexTrust(repoDir, home3) {
57306
57528
  const path = codexConfigPath(home3);
@@ -57313,11 +57535,11 @@ function ensureCodexTrust(repoDir, home3) {
57313
57535
  try {
57314
57536
  let existing = "";
57315
57537
  try {
57316
- existing = readFileSync15(path, "utf8");
57538
+ existing = readFileSync16(path, "utf8");
57317
57539
  } catch {
57318
57540
  }
57319
57541
  if (existing.includes(header2)) return;
57320
- mkdirSync13(codexHome(home3), { recursive: true });
57542
+ mkdirSync14(codexHome(home3), { recursive: true });
57321
57543
  const lead = existing === "" || existing.endsWith("\n") ? "" : "\n";
57322
57544
  appendFileSync3(path, `${lead}
57323
57545
  ${header2}
@@ -57457,8 +57679,8 @@ var init_device_sockets = __esm({
57457
57679
 
57458
57680
  // src/services/exit-guard.ts
57459
57681
  import { spawn as spawn3 } from "node:child_process";
57460
- import { writeFileSync as writeFileSync15 } from "node:fs";
57461
- import { join as join29 } from "node:path";
57682
+ import { writeFileSync as writeFileSync16 } from "node:fs";
57683
+ import { join as join31 } from "node:path";
57462
57684
  function watchdogScript(pid, graceMs) {
57463
57685
  const secs = Math.max(1, Math.ceil(graceMs / 1e3));
57464
57686
  return `s=$(ps -o lstart= -p ${pid}); sleep ${secs}; [ -n "$s" ] && [ "$(ps -o lstart= -p ${pid})" = "$s" ] && kill -9 ${pid}`;
@@ -57474,8 +57696,8 @@ function launchWatchdog(script2, env = process.env) {
57474
57696
  function exitGuarded(code, d = {}) {
57475
57697
  const pid = d.pid ?? process.pid;
57476
57698
  try {
57477
- const write2 = d.writeIntent ?? ((p3, t) => writeFileSync15(p3, t, { mode: 384 }));
57478
- write2(join29(d.home ?? resolveHome(), EXIT_INTENT_FILE), formatExitIntent(pid, code));
57699
+ const write2 = d.writeIntent ?? ((p3, t) => writeFileSync16(p3, t, { mode: 384 }));
57700
+ write2(join31(d.home ?? resolveHome(), EXIT_INTENT_FILE), formatExitIntent(pid, code));
57479
57701
  } catch {
57480
57702
  }
57481
57703
  try {
@@ -57495,9 +57717,9 @@ var init_exit_guard = __esm({
57495
57717
  });
57496
57718
 
57497
57719
  // src/services/update.ts
57498
- import { readFileSync as readFileSync16 } from "node:fs";
57499
- import { resolve as resolve20, dirname as dirname13 } from "node:path";
57500
- import { fileURLToPath as fileURLToPath3 } from "node:url";
57720
+ import { readFileSync as readFileSync17 } from "node:fs";
57721
+ import { resolve as resolve20, dirname as dirname14 } from "node:path";
57722
+ import { fileURLToPath as fileURLToPath4 } from "node:url";
57501
57723
  function composeUpdate(x) {
57502
57724
  const available = x.registryStatus === "ok" && x.latestVersion != null && compareSemver(x.latestVersion, x.currentVersion) > 0;
57503
57725
  return {
@@ -57513,10 +57735,10 @@ function composeUpdate(x) {
57513
57735
  };
57514
57736
  }
57515
57737
  function runningVersion() {
57516
- const here = dirname13(fileURLToPath3(import.meta.url));
57738
+ const here = dirname14(fileURLToPath4(import.meta.url));
57517
57739
  for (const p3 of [resolve20(here, "build-info.json"), resolve20(here, "../package.json")]) {
57518
57740
  try {
57519
- const v = JSON.parse(readFileSync16(p3, "utf8")).version;
57741
+ const v = JSON.parse(readFileSync17(p3, "utf8")).version;
57520
57742
  if (typeof v === "string" && v) return v;
57521
57743
  } catch {
57522
57744
  }
@@ -57952,7 +58174,7 @@ var init_remote_session = __esm({
57952
58174
 
57953
58175
  // src/services/spec-attachment-dir.ts
57954
58176
  import { mkdir as mkdir6, readdir as readdir5, readFile as readFile6, rm as rm4, writeFile as writeFile5 } from "node:fs/promises";
57955
- import { join as join30 } from "node:path";
58177
+ import { join as join32 } from "node:path";
57956
58178
  function uniqueName(taken, filename) {
57957
58179
  if (!taken.has(filename)) {
57958
58180
  taken.add(filename);
@@ -58000,19 +58222,19 @@ async function syncSpecAttachmentsDir(specId, projectId) {
58000
58222
  for (const a of rows) {
58001
58223
  let bytes;
58002
58224
  try {
58003
- bytes = await readFile6(join30(uploadDir(), a.storageKey));
58225
+ bytes = await readFile6(join32(uploadDir(), a.storageKey));
58004
58226
  } catch {
58005
58227
  continue;
58006
58228
  }
58007
58229
  const filename = uniqueName(taken, a.filename);
58008
- const path = join30(dir2, filename);
58230
+ const path = join32(dir2, filename);
58009
58231
  await writeFile5(path, bytes, { mode: 384 });
58010
58232
  items.push({ filename, mimeType: a.mimeType, size: a.size, path });
58011
58233
  }
58012
- await writeFile5(join30(dir2, INDEX), renderIndex(specId, items), { mode: 384 });
58234
+ await writeFile5(join32(dir2, INDEX), renderIndex(specId, items), { mode: 384 });
58013
58235
  const keep = /* @__PURE__ */ new Set([INDEX, ...items.map((a) => a.filename)]);
58014
58236
  for (const name2 of await readdir5(dir2)) {
58015
- if (!keep.has(name2)) await rm4(join30(dir2, name2), { recursive: true, force: true }).catch(() => {
58237
+ if (!keep.has(name2)) await rm4(join32(dir2, name2), { recursive: true, force: true }).catch(() => {
58016
58238
  });
58017
58239
  }
58018
58240
  return items;
@@ -58031,7 +58253,7 @@ var init_spec_attachment_dir = __esm({
58031
58253
  init_local_binding();
58032
58254
  init_session_id();
58033
58255
  init_uploads();
58034
- specAttachmentsDir = (repoDir, sessionId2) => join30(repoDir, ".worktrees", ".attachments", sessionId2);
58256
+ specAttachmentsDir = (repoDir, sessionId2) => join32(repoDir, ".worktrees", ".attachments", sessionId2);
58035
58257
  INDEX = "INDEX.md";
58036
58258
  humanSize = (n2) => n2 >= 1024 * 1024 ? `${(n2 / 1024 / 1024).toFixed(1)} MB` : n2 >= 1024 ? `${Math.round(n2 / 1024)} KB` : `${n2} B`;
58037
58259
  }
@@ -58426,7 +58648,7 @@ var init_runtime_defaults2 = __esm({
58426
58648
  });
58427
58649
 
58428
58650
  // src/services/agent-token.ts
58429
- import { randomBytes as randomBytes2, createHash as createHash12, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
58651
+ import { randomBytes as randomBytes2, createHash as createHash13, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
58430
58652
  function toAgentTokenView(t) {
58431
58653
  return {
58432
58654
  id: t.id,
@@ -58513,7 +58735,7 @@ var init_agent_token = __esm({
58513
58735
  "src/services/agent-token.ts"() {
58514
58736
  "use strict";
58515
58737
  init_db();
58516
- hash2 = (token) => createHash12("sha256").update(token).digest("hex");
58738
+ hash2 = (token) => createHash13("sha256").update(token).digest("hex");
58517
58739
  AGENT_CREATOR_PREFIX = "agent:";
58518
58740
  }
58519
58741
  });
@@ -58842,11 +59064,11 @@ var init_project_config = __esm({
58842
59064
 
58843
59065
  // src/services/limits.ts
58844
59066
  import { execFileSync as execFileSync6 } from "node:child_process";
58845
- import { readFileSync as readFileSync17 } from "node:fs";
59067
+ import { readFileSync as readFileSync18 } from "node:fs";
58846
59068
  import { homedir as homedir6, userInfo } from "node:os";
58847
- import { join as join31 } from "node:path";
59069
+ import { join as join34 } from "node:path";
58848
59070
  function credsFile() {
58849
- return join31(effectiveStr("CLAUDE_CONFIG_DIR") ?? join31(homedir6(), ".claude"), ".credentials.json");
59071
+ return join34(effectiveStr("CLAUDE_CONFIG_DIR") ?? join34(homedir6(), ".claude"), ".credentials.json");
58850
59072
  }
58851
59073
  function keychainAccessToken(run6 = securityRun, account = osAccount()) {
58852
59074
  const base3 = ["find-generic-password", "-s", "Claude Code-credentials"];
@@ -58865,7 +59087,7 @@ function readAccessToken() {
58865
59087
  if (tok) return tok;
58866
59088
  }
58867
59089
  try {
58868
- return JSON.parse(readFileSync17(credsFile(), "utf8"))?.claudeAiOauth?.accessToken ?? null;
59090
+ return JSON.parse(readFileSync18(credsFile(), "utf8"))?.claudeAiOauth?.accessToken ?? null;
58869
59091
  } catch {
58870
59092
  return null;
58871
59093
  }
@@ -58949,7 +59171,7 @@ var init_limits = __esm({
58949
59171
  // src/services/codex-limits.ts
58950
59172
  import { open as open3, readdir as readdir6, stat as stat6 } from "node:fs/promises";
58951
59173
  import { homedir as homedir7 } from "node:os";
58952
- import { join as join32 } from "node:path";
59174
+ import { join as join35 } from "node:path";
58953
59175
  function windowLabel(minutes) {
58954
59176
  if (minutes === 300) return "Sesi 5 jam";
58955
59177
  if (minutes === 10080) return "Mingguan";
@@ -58980,7 +59202,7 @@ async function recentRollouts(dir2) {
58980
59202
  return [];
58981
59203
  }
58982
59204
  const stamped = await Promise.all(entries4.map(async (rel) => {
58983
- const full = join32(dir2, rel);
59205
+ const full = join35(dir2, rel);
58984
59206
  try {
58985
59207
  return { full, mtime: (await stat6(full)).mtimeMs };
58986
59208
  } catch {
@@ -59060,7 +59282,7 @@ var init_codex_limits = __esm({
59060
59282
  MAX_FILES = 8;
59061
59283
  cache2 = null;
59062
59284
  freshUntil2 = 0;
59063
- codexSessionsDir = () => join32(process.env.CODEX_HOME ?? join32(homedir7(), ".codex"), "sessions");
59285
+ codexSessionsDir = () => join35(process.env.CODEX_HOME ?? join35(homedir7(), ".codex"), "sessions");
59064
59286
  UNAVAILABLE = { status: "unavailable", windows: [], fetchedAt: null, plan: null };
59065
59287
  severityOf = (pct3) => pct3 >= 90 ? "critical" : pct3 >= 70 ? "warning" : "normal";
59066
59288
  }
@@ -59068,9 +59290,9 @@ var init_codex_limits = __esm({
59068
59290
 
59069
59291
  // src/services/antigravity-limits.ts
59070
59292
  import { execFileSync as execFileSync7 } from "node:child_process";
59071
- import { readFileSync as readFileSync18 } from "node:fs";
59293
+ import { readFileSync as readFileSync19 } from "node:fs";
59072
59294
  import { homedir as homedir8 } from "node:os";
59073
- import { join as join33 } from "node:path";
59295
+ import { join as join36 } from "node:path";
59074
59296
  function readAgyToken(run6 = securityRun2) {
59075
59297
  try {
59076
59298
  let raw = run6(["find-generic-password", "-s", "gemini", "-a", "antigravity", "-w"]).trim();
@@ -59086,7 +59308,7 @@ function readAgyToken(run6 = securityRun2) {
59086
59308
  }
59087
59309
  function defaultProject() {
59088
59310
  try {
59089
- return readFileSync18(join33(agyHome(), "cache", "default_project_id.txt"), "utf8").trim() || null;
59311
+ return readFileSync19(join36(agyHome(), "cache", "default_project_id.txt"), "utf8").trim() || null;
59090
59312
  } catch {
59091
59313
  return null;
59092
59314
  }
@@ -59177,7 +59399,7 @@ var init_antigravity_limits = __esm({
59177
59399
  freshUntil3 = 0;
59178
59400
  UNAVAILABLE2 = { status: "unavailable", windows: [], fetchedAt: null };
59179
59401
  securityRun2 = (args) => execFileSync7("security", args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
59180
- agyHome = () => join33(homedir8(), ".gemini", "antigravity-cli");
59402
+ agyHome = () => join36(homedir8(), ".gemini", "antigravity-cli");
59181
59403
  severityOf2 = (pct3) => pct3 >= 90 ? "critical" : pct3 >= 70 ? "warning" : "normal";
59182
59404
  prodDeps = {
59183
59405
  platform: process.platform,
@@ -59276,7 +59498,7 @@ var init_budget = __esm({
59276
59498
  });
59277
59499
 
59278
59500
  // src/services/session-dialog.ts
59279
- import { createHash as createHash13 } from "node:crypto";
59501
+ import { createHash as createHash14 } from "node:crypto";
59280
59502
  function readSessionDialog(io) {
59281
59503
  const text2 = io.capture();
59282
59504
  const s2 = answerable(text2);
@@ -59332,7 +59554,7 @@ var init_session_dialog2 = __esm({
59332
59554
  init_tui_dialog();
59333
59555
  init_pty();
59334
59556
  DIALOG_CHUNK_MS = 50;
59335
- screenHashOf = (paneText2) => createHash13("sha256").update(dialogKey(paneText2)).digest("hex").slice(0, 16);
59557
+ screenHashOf = (paneText2) => createHash14("sha256").update(dialogKey(paneText2)).digest("hex").slice(0, 16);
59336
59558
  answerable = (paneText2) => {
59337
59559
  const s2 = readDialogScreen(paneText2);
59338
59560
  if (s2?.kind !== "question") return null;
@@ -59635,8 +59857,8 @@ var init_prompt2 = __esm({
59635
59857
  });
59636
59858
 
59637
59859
  // src/services/lead/verdict.ts
59638
- import { existsSync as existsSync15 } from "node:fs";
59639
- import { isAbsolute as isAbsolute11, resolve as resolvePath2, relative as relative6 } from "node:path";
59860
+ import { existsSync as existsSync18 } from "node:fs";
59861
+ import { isAbsolute as isAbsolute11, resolve as resolvePath2, relative as relative7 } from "node:path";
59640
59862
  function extractJsonBlock(raw) {
59641
59863
  const re = /```json\s*\n([\s\S]*?)```/g;
59642
59864
  let last2 = null;
@@ -59669,9 +59891,9 @@ function keepExistingRefs(refs2, repoDir) {
59669
59891
  if (!repoDir) continue;
59670
59892
  if (isAbsolute11(ref)) continue;
59671
59893
  const full = resolvePath2(repoDir, ref);
59672
- const rel = relative6(repoDir, full);
59894
+ const rel = relative7(repoDir, full);
59673
59895
  if (rel.startsWith("..")) continue;
59674
- if (existsSync15(full)) out4.push(ref);
59896
+ if (existsSync18(full)) out4.push(ref);
59675
59897
  }
59676
59898
  return [...new Set(out4)];
59677
59899
  }
@@ -60861,9 +61083,9 @@ var init_queue = __esm({
60861
61083
  });
60862
61084
 
60863
61085
  // src/services/git-auth.ts
60864
- import { chmodSync as chmodSync8, mkdirSync as mkdirSync14, writeFileSync as writeFileSync16 } from "node:fs";
61086
+ import { chmodSync as chmodSync9, mkdirSync as mkdirSync15, writeFileSync as writeFileSync17 } from "node:fs";
60865
61087
  import { tmpdir as tmpdir6 } from "node:os";
60866
- import { dirname as dirname14 } from "node:path";
61088
+ import { dirname as dirname15 } from "node:path";
60867
61089
  function httpsRemote(remote) {
60868
61090
  const scp = /^[\w.-]+@github\.com:(.+)$/.exec(remote);
60869
61091
  if (scp) return `https://github.com/${scp[1]}`;
@@ -60873,9 +61095,9 @@ function httpsRemote(remote) {
60873
61095
  }
60874
61096
  function gitAuthEnv(token) {
60875
61097
  const path = gitAskpassPath();
60876
- mkdirSync14(dirname14(path), { recursive: true, mode: 448 });
60877
- writeFileSync16(path, ASKPASS_TOKEN, { mode: 448 });
60878
- chmodSync8(path, 448);
61098
+ mkdirSync15(dirname15(path), { recursive: true, mode: 448 });
61099
+ writeFileSync17(path, ASKPASS_TOKEN, { mode: 448 });
61100
+ chmodSync9(path, 448);
60879
61101
  return { GIT_ASKPASS: path, GIT_TERMINAL_PROMPT: "0", HANOMAN_GIT_TOKEN: token };
60880
61102
  }
60881
61103
  var gitAskpassPath, ASKPASS_TOKEN, isGithubHttps;
@@ -63875,7 +64097,7 @@ var require_websocket2 = __commonJS({
63875
64097
  var http = __require("http");
63876
64098
  var net = __require("net");
63877
64099
  var tls = __require("tls");
63878
- var { randomBytes: randomBytes12, createHash: createHash23 } = __require("crypto");
64100
+ var { randomBytes: randomBytes12, createHash: createHash24 } = __require("crypto");
63879
64101
  var { Duplex, Readable } = __require("stream");
63880
64102
  var { URL: URL2 } = __require("url");
63881
64103
  var PerMessageDeflate2 = require_permessage_deflate2();
@@ -64543,7 +64765,7 @@ var require_websocket2 = __commonJS({
64543
64765
  abortHandshake(websocket2, socket2, "Invalid Upgrade header");
64544
64766
  return;
64545
64767
  }
64546
- const digest3 = createHash23("sha1").update(key + GUID).digest("base64");
64768
+ const digest3 = createHash24("sha1").update(key + GUID).digest("base64");
64547
64769
  if (res.headers["sec-websocket-accept"] !== digest3) {
64548
64770
  abortHandshake(websocket2, socket2, "Invalid Sec-WebSocket-Accept header");
64549
64771
  return;
@@ -64912,7 +65134,7 @@ var require_websocket_server = __commonJS({
64912
65134
  var EventEmitter = __require("events");
64913
65135
  var http = __require("http");
64914
65136
  var { Duplex } = __require("stream");
64915
- var { createHash: createHash23 } = __require("crypto");
65137
+ var { createHash: createHash24 } = __require("crypto");
64916
65138
  var extension2 = require_extension();
64917
65139
  var PerMessageDeflate2 = require_permessage_deflate2();
64918
65140
  var subprotocol2 = require_subprotocol();
@@ -65219,7 +65441,7 @@ var require_websocket_server = __commonJS({
65219
65441
  );
65220
65442
  }
65221
65443
  if (this._state > RUNNING) return abortHandshake(socket2, 503);
65222
- const digest3 = createHash23("sha1").update(key + GUID).digest("base64");
65444
+ const digest3 = createHash24("sha1").update(key + GUID).digest("base64");
65223
65445
  const headers = [
65224
65446
  "HTTP/1.1 101 Switching Protocols",
65225
65447
  "Upgrade: websocket",
@@ -65515,16 +65737,16 @@ var init_client2 = __esm({
65515
65737
  });
65516
65738
 
65517
65739
  // src/services/logs/spool.ts
65518
- import { appendFile, mkdir as mkdir7, readFile as readFile7, readdir as readdir7, rm as rm5, stat as stat7 } from "node:fs/promises";
65519
- import { join as join34 } from "node:path";
65740
+ import { appendFile, mkdir as mkdir7, readFile as readFile8, readdir as readdir7, rm as rm5, stat as stat7 } from "node:fs/promises";
65741
+ import { join as join37 } from "node:path";
65520
65742
  function spoolDir(lane) {
65521
- return join34(resolveDataDirs().home, "log-spool", lane);
65743
+ return join37(resolveDataDirs().home, "log-spool", lane);
65522
65744
  }
65523
65745
  async function currentSegment(lane) {
65524
65746
  const dir2 = spoolDir(lane);
65525
65747
  await mkdir7(dir2, { recursive: true, mode: 448 });
65526
65748
  if (currentFile && currentBytes < LOG_SPOOL_SEGMENT_BYTES) return currentFile;
65527
- currentFile = join34(dir2, `${Date.now()}-${++segCounter}.ndjson`);
65749
+ currentFile = join37(dir2, `${Date.now()}-${++segCounter}.ndjson`);
65528
65750
  currentBytes = 0;
65529
65751
  return currentFile;
65530
65752
  }
@@ -65546,8 +65768,8 @@ async function readSpoolSegments(lane) {
65546
65768
  const files2 = names.filter((n2) => n2.endsWith(".ndjson")).sort();
65547
65769
  const out4 = [];
65548
65770
  for (const name2 of files2) {
65549
- const file = join34(dir2, name2);
65550
- const raw = await readFile7(file, "utf8").catch(() => "");
65771
+ const file = join37(dir2, name2);
65772
+ const raw = await readFile8(file, "utf8").catch(() => "");
65551
65773
  const entries4 = raw.split("\n").filter(Boolean).map((l) => JSON.parse(l));
65552
65774
  out4.push({ file, entries: entries4 });
65553
65775
  }
@@ -66211,8 +66433,8 @@ var init_git_token = __esm({
66211
66433
 
66212
66434
  // src/services/project-checkout.ts
66213
66435
  import { execFile as execFile14, spawnSync as spawnSync4 } from "node:child_process";
66214
- import { existsSync as existsSync16, readdirSync as readdirSync8, rmSync as rmSync9 } from "node:fs";
66215
- import { join as join35 } from "node:path";
66436
+ import { existsSync as existsSync19, readdirSync as readdirSync10, rmSync as rmSync10 } from "node:fs";
66437
+ import { join as join38 } from "node:path";
66216
66438
  import { promisify as promisify13 } from "node:util";
66217
66439
  async function runGit(args, o) {
66218
66440
  const full = o.token ? ["-c", "credential.helper=", ...args] : args;
@@ -66234,8 +66456,8 @@ async function doClone(projectId, opts2) {
66234
66456
  if (!project) throw new CheckoutError(`project "${projectId}" tidak ada`, "not-found");
66235
66457
  if (!project.gitRemote) throw new CheckoutError(`project "${projectId}" tidak punya gitRemote untuk di-clone`, "no-remote");
66236
66458
  const remote = project.gitRemote;
66237
- const dest = opts2.dir ?? join35(checkoutRoot(), slug(project.id));
66238
- if (existsSync16(dest) && !isEmptyDir(dest)) {
66459
+ const dest = opts2.dir ?? join38(checkoutRoot(), slug(project.id));
66460
+ if (existsSync19(dest) && !isEmptyDir(dest)) {
66239
66461
  const origin = originOf2(dest);
66240
66462
  if (origin && sameRemote(origin, remote)) {
66241
66463
  await setBinding(projectId, dest);
@@ -66251,7 +66473,7 @@ async function doClone(projectId, opts2) {
66251
66473
  timeout: CLONE_TIMEOUT_MS
66252
66474
  });
66253
66475
  if (!res.ok) {
66254
- rmSync9(dest, { recursive: true, force: true });
66476
+ rmSync10(dest, { recursive: true, force: true });
66255
66477
  const auth = AUTH_RE.test(res.stderr);
66256
66478
  throw new CheckoutError(
66257
66479
  auth ? useAuth ? `token GitHub ditolak atau tanpa akses ke ${remote}` : `akses ke ${remote} ditolak: isi GITHUB_TOKEN di Settings atau hubungkan instance ini ke hub` : `git clone ${remote} gagal`,
@@ -66298,7 +66520,7 @@ var init_project_checkout = __esm({
66298
66520
  CLONE_TIMEOUT_MS = 10 * 6e4;
66299
66521
  FETCH_TIMEOUT_MS = 2 * 6e4;
66300
66522
  AUTH_RE = /Authentication failed|could not read (Username|Password)|Invalid username or password|HTTP 40[13]|returned error: 40[13]|Repository not found|Permission denied \(publickey\)/i;
66301
- checkoutRoot = () => join35(resolveHome(), "projects");
66523
+ checkoutRoot = () => join38(resolveHome(), "projects");
66302
66524
  slug = (id2) => id2.toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || "project";
66303
66525
  sameRemote = (a, b) => {
66304
66526
  const norm5 = (u) => httpsRemote(u).replace(/\.git$/, "").replace(/\/+$/, "").toLowerCase();
@@ -66307,7 +66529,7 @@ var init_project_checkout = __esm({
66307
66529
  redact = (text2, token) => token ? text2.split(token).join("***") : text2;
66308
66530
  isEmptyDir = (dir2) => {
66309
66531
  try {
66310
- return readdirSync8(dir2).length === 0;
66532
+ return readdirSync10(dir2).length === 0;
66311
66533
  } catch {
66312
66534
  return false;
66313
66535
  }
@@ -67076,7 +67298,7 @@ var init_approvals = __esm({
67076
67298
  });
67077
67299
 
67078
67300
  // src/services/telegram/gateway.ts
67079
- import { createHash as createHash14 } from "node:crypto";
67301
+ import { createHash as createHash15 } from "node:crypto";
67080
67302
  var digestRaw, TelegramGateway;
67081
67303
  var init_gateway = __esm({
67082
67304
  "src/services/telegram/gateway.ts"() {
@@ -67085,7 +67307,7 @@ var init_gateway = __esm({
67085
67307
  init_protocol();
67086
67308
  init_runtime();
67087
67309
  init_typing();
67088
- digestRaw = (update2) => createHash14("sha256").update(JSON.stringify(update2)).digest("hex");
67310
+ digestRaw = (update2) => createHash15("sha256").update(JSON.stringify(update2)).digest("hex");
67089
67311
  TelegramGateway = class {
67090
67312
  constructor(deps) {
67091
67313
  this.deps = deps;
@@ -67409,14 +67631,14 @@ var init_engine_command = __esm({
67409
67631
  });
67410
67632
 
67411
67633
  // src/services/telegram/session.ts
67412
- import { createHash as createHash15 } from "node:crypto";
67634
+ import { createHash as createHash16 } from "node:crypto";
67413
67635
  var chatHash, telegramOperatorSessionId, formatTelegramTurn, TelegramSessionCoordinator;
67414
67636
  var init_session = __esm({
67415
67637
  "src/services/telegram/session.ts"() {
67416
67638
  "use strict";
67417
67639
  init_src2();
67418
67640
  init_engine_command();
67419
- chatHash = (chatId) => createHash15("sha256").update(chatId).digest("hex").slice(0, 16);
67641
+ chatHash = (chatId) => createHash16("sha256").update(chatId).digest("hex").slice(0, 16);
67420
67642
  telegramOperatorSessionId = (chatId) => `telegram-${chatHash(chatId)}`;
67421
67643
  formatTelegramTurn = (input) => `[Telegram update ${input.updateId} \xB7 chat ${input.chatId} \xB7 kind ${input.kind}]
67422
67644
  ${input.text}`;
@@ -67813,7 +68035,7 @@ __export(bootstrap_exports, {
67813
68035
  reloadTelegramGateway: () => reloadTelegramGateway,
67814
68036
  telegramSessionDeps: () => telegramSessionDeps
67815
68037
  });
67816
- import { mkdirSync as mkdirSync15 } from "node:fs";
68038
+ import { mkdirSync as mkdirSync16 } from "node:fs";
67817
68039
  function parseTelegramAllowedUserIds(raw) {
67818
68040
  const ids = raw.split(/[\s,]+/).filter(Boolean);
67819
68041
  if (!ids.length) throw new Error("Telegram allowlist wajib berisi numeric user id");
@@ -67838,7 +68060,7 @@ function telegramSessionDeps(input) {
67838
68060
  home: resolveHome(),
67839
68061
  apiBase: input.apiBase,
67840
68062
  agentToken: input.agentToken,
67841
- ensureDir: (path) => mkdirSync15(path, { recursive: true })
68063
+ ensureDir: (path) => mkdirSync16(path, { recursive: true })
67842
68064
  };
67843
68065
  }
67844
68066
  async function productionFactory(input) {
@@ -68060,17 +68282,17 @@ var init_codex_version = __esm({
68060
68282
  });
68061
68283
 
68062
68284
  // src/services/agent-tool-catalog.ts
68063
- import { readFileSync as readFileSync26 } from "node:fs";
68285
+ import { readFileSync as readFileSync27 } from "node:fs";
68064
68286
  import { homedir as homedir13 } from "node:os";
68065
- import { join as join49 } from "node:path";
68287
+ import { join as join51 } from "node:path";
68066
68288
  function mcpServerNames(repoDir) {
68067
68289
  const names = [];
68068
- const claudeJson = readJson2(join49(home(), ".claude.json"));
68290
+ const claudeJson = readJson2(join51(home(), ".claude.json"));
68069
68291
  names.push(...serversOf(claudeJson));
68070
68292
  if (repoDir) {
68071
68293
  const projects = claudeJson?.projects;
68072
68294
  if (projects && typeof projects === "object") names.push(...serversOf(projects[repoDir]));
68073
- names.push(...serversOf(readJson2(join49(repoDir, ".mcp.json"))));
68295
+ names.push(...serversOf(readJson2(join51(repoDir, ".mcp.json"))));
68074
68296
  }
68075
68297
  names.push(...codexServers());
68076
68298
  return [...new Set(names.filter(Boolean))].sort((a, b) => a.localeCompare(b));
@@ -68086,7 +68308,7 @@ var init_agent_tool_catalog = __esm({
68086
68308
  home = () => process.env.HOME || homedir13();
68087
68309
  readJson2 = (path) => {
68088
68310
  try {
68089
- return JSON.parse(readFileSync26(path, "utf8"));
68311
+ return JSON.parse(readFileSync27(path, "utf8"));
68090
68312
  } catch {
68091
68313
  return null;
68092
68314
  }
@@ -68099,7 +68321,7 @@ var init_agent_tool_catalog = __esm({
68099
68321
  codexServers = () => {
68100
68322
  let text2;
68101
68323
  try {
68102
- text2 = readFileSync26(join49(home(), ".codex", "config.toml"), "utf8");
68324
+ text2 = readFileSync27(join51(home(), ".codex", "config.toml"), "utf8");
68103
68325
  } catch {
68104
68326
  return [];
68105
68327
  }
@@ -68115,7 +68337,7 @@ var init_agent_tool_catalog = __esm({
68115
68337
  });
68116
68338
 
68117
68339
  // src/services/builtin-agents.ts
68118
- import { createHash as createHash19 } from "node:crypto";
68340
+ import { createHash as createHash20 } from "node:crypto";
68119
68341
  async function seedBuiltinAgents() {
68120
68342
  try {
68121
68343
  const setting = await getSetting();
@@ -68217,7 +68439,7 @@ var init_builtin_agents2 = __esm({
68217
68439
  init_settings3();
68218
68440
  init_tombstone();
68219
68441
  init_sync_notify();
68220
- digest2 = (parts) => createHash19("sha256").update(parts.join(" ")).digest("hex").slice(0, 16);
68442
+ digest2 = (parts) => createHash20("sha256").update(parts.join(" ")).digest("hex").slice(0, 16);
68221
68443
  legacyFingerprint = (name2, description, instructions, tools) => digest2([name2, description, instructions, [...tools].join(",")]);
68222
68444
  mentionPart = (v) => {
68223
68445
  const m = mentionsOf(v);
@@ -71717,9 +71939,9 @@ import Fastify from "fastify";
71717
71939
  import fastifyStatic from "@fastify/static";
71718
71940
  import websocket from "@fastify/websocket";
71719
71941
  import cookie from "@fastify/cookie";
71720
- import { fileURLToPath as fileURLToPath5 } from "node:url";
71942
+ import { fileURLToPath as fileURLToPath6 } from "node:url";
71721
71943
  import { dirname as dirname19 } from "node:path";
71722
- import { existsSync as existsSync23 } from "node:fs";
71944
+ import { existsSync as existsSync25 } from "node:fs";
71723
71945
 
71724
71946
  // src/web-dir.ts
71725
71947
  import { resolve as resolve11 } from "node:path";
@@ -71773,7 +71995,7 @@ async function agent_doc_default(app3, opts2) {
71773
71995
  init_config2();
71774
71996
  init_db();
71775
71997
  init_uploads();
71776
- import { createHash as createHash9 } from "node:crypto";
71998
+ import { createHash as createHash10 } from "node:crypto";
71777
71999
 
71778
72000
  // src/services/upload-pipeline.ts
71779
72001
  init_file_type();
@@ -71783,7 +72005,7 @@ init_uploads();
71783
72005
  import { randomUUID as randomUUID6 } from "node:crypto";
71784
72006
  import { spawn as spawn2 } from "node:child_process";
71785
72007
  import { mkdir as mkdir3, rename, unlink as unlink3, writeFile as writeFile3 } from "node:fs/promises";
71786
- import { basename as basename7, isAbsolute as isAbsolute9, join as join21 } from "node:path";
72008
+ import { basename as basename7, isAbsolute as isAbsolute9, join as join23 } from "node:path";
71787
72009
  import sharp from "sharp";
71788
72010
  var UPLOAD_LIMITS = {
71789
72011
  fileBytes: 5 * 1024 * 1024,
@@ -71857,16 +72079,16 @@ function safeFilename2(input, extension2) {
71857
72079
  }
71858
72080
  async function commitToStorage(buffer, extension2, deps, beforePromote) {
71859
72081
  const storageDir = deps.storageDir ?? uploadDir();
71860
- const quarantineDir = join21(storageDir, ".quarantine");
72082
+ const quarantineDir = join23(storageDir, ".quarantine");
71861
72083
  await mkdir3(quarantineDir, { recursive: true, mode: 448 });
71862
72084
  await mkdir3(storageDir, { recursive: true, mode: 448 });
71863
- const quarantine = join21(quarantineDir, `${randomUUID6()}.upload`);
72085
+ const quarantine = join23(quarantineDir, `${randomUUID6()}.upload`);
71864
72086
  const storageKey = `${randomUUID6()}${extension2}`;
71865
72087
  await writeFile3(quarantine, buffer, { mode: 384, flag: "wx" });
71866
72088
  try {
71867
72089
  await timeout((deps.scanner ?? scannerFromEnv)(quarantine), UPLOAD_LIMITS.scanMs, "UPLOAD_SCAN");
71868
72090
  await beforePromote?.();
71869
- await rename(quarantine, join21(storageDir, storageKey));
72091
+ await rename(quarantine, join23(storageDir, storageKey));
71870
72092
  } catch (error) {
71871
72093
  await unlink3(quarantine).catch(() => {
71872
72094
  });
@@ -72268,7 +72490,7 @@ async function addQaAttachments(report, owner, files2) {
72268
72490
  continue;
72269
72491
  }
72270
72492
  try {
72271
- const sha256 = createHash9("sha256").update(await readUpload(safe3.storageKey)).digest("hex");
72493
+ const sha256 = createHash10("sha256").update(await readUpload(safe3.storageKey)).digest("hex");
72272
72494
  const row = await prisma.qaAttachment.create({ data: {
72273
72495
  reportId: report.id,
72274
72496
  projectId: report.projectId,
@@ -72322,7 +72544,7 @@ init_src();
72322
72544
  init_safe_repo_path();
72323
72545
  import { execFile as execFile3 } from "node:child_process";
72324
72546
  import { promisify as promisify2 } from "node:util";
72325
- import { existsSync as existsSync11, rmSync as rmSync7 } from "node:fs";
72547
+ import { existsSync as existsSync13, rmSync as rmSync8 } from "node:fs";
72326
72548
  import { resolve as resolve14 } from "node:path";
72327
72549
  var exec = promisify2(execFile3);
72328
72550
  async function listRepoDocs(repoDir) {
@@ -72347,12 +72569,12 @@ function docsDirOf(repoDir) {
72347
72569
  }
72348
72570
  function resolveIndex(repoDir, docsDir) {
72349
72571
  const rel = `${docsDir}/README.md`;
72350
- return existsSync11(resolve14(repoDir, rel)) ? rel : "";
72572
+ return existsSync13(resolve14(repoDir, rel)) ? rel : "";
72351
72573
  }
72352
72574
  var catOf = (rel) => rel.includes("/") ? rel.slice(0, rel.lastIndexOf("/")) : ".";
72353
72575
  var nameOf = (rel) => rel.includes("/") ? rel.slice(rel.lastIndexOf("/") + 1) : rel;
72354
72576
  async function scanRepoDocs(repoDir) {
72355
- if (!repoDir || !existsSync11(repoDir)) return { coverage: 0, tree: [] };
72577
+ if (!repoDir || !existsSync13(repoDir)) return { coverage: 0, tree: [] };
72356
72578
  const files2 = await listRepoDocs(repoDir);
72357
72579
  const docsDir = docsDirOf(repoDir);
72358
72580
  const index = resolveIndex(repoDir, docsDir);
@@ -72400,8 +72622,8 @@ function writeDocFile(repoDir, rel, content) {
72400
72622
  }
72401
72623
  function deleteDocFile(repoDir, rel) {
72402
72624
  const abs = docAbsPath(repoDir, rel);
72403
- if (!existsSync11(abs)) return false;
72404
- rmSync7(abs);
72625
+ if (!existsSync13(abs)) return false;
72626
+ rmSync8(abs);
72405
72627
  return true;
72406
72628
  }
72407
72629
 
@@ -72719,7 +72941,7 @@ async function projects_default(app3) {
72719
72941
  init_src();
72720
72942
  init_src2();
72721
72943
  init_integrate();
72722
- import { existsSync as existsSync14 } from "node:fs";
72944
+ import { existsSync as existsSync17 } from "node:fs";
72723
72945
  import { basename as basename9 } from "node:path";
72724
72946
 
72725
72947
  // src/services/worktree-delete.ts
@@ -72738,7 +72960,7 @@ init_spec_review2();
72738
72960
  init_safe_repo_path();
72739
72961
  import { execFile as execFile7 } from "node:child_process";
72740
72962
  import { promisify as promisify6 } from "node:util";
72741
- import { existsSync as existsSync12 } from "node:fs";
72963
+ import { existsSync as existsSync14 } from "node:fs";
72742
72964
  import { readdir as readdir3 } from "node:fs/promises";
72743
72965
  import { resolve as resolve16 } from "node:path";
72744
72966
  var exec5 = promisify6(execFile7);
@@ -72751,7 +72973,7 @@ function repoAbsPath(repoDir, rel, allowMissingFinal = false) {
72751
72973
  return resolve16(repoDir, rel);
72752
72974
  }
72753
72975
  async function listRepoTree(repoDir, ref = "") {
72754
- if (!repoDir || !existsSync12(repoDir)) return [];
72976
+ if (!repoDir || !existsSync14(repoDir)) return [];
72755
72977
  try {
72756
72978
  const { stdout } = ref ? await exec5("git", ["ls-tree", "-r", "--name-only", "-z", ref], { cwd: repoDir, ...GIT4 }) : await exec5("git", ["ls-files", "--cached", "--others", "--exclude-standard", "-z"], { cwd: repoDir, ...GIT4 });
72757
72979
  return [...new Set(splitZ2(stdout))].sort();
@@ -72761,7 +72983,7 @@ async function listRepoTree(repoDir, ref = "") {
72761
72983
  }
72762
72984
  async function listIgnoredEntries(repoDir) {
72763
72985
  const empty = { files: [], dirs: [] };
72764
- if (!repoDir || !existsSync12(repoDir)) return empty;
72986
+ if (!repoDir || !existsSync14(repoDir)) return empty;
72765
72987
  try {
72766
72988
  const { stdout } = await exec5(
72767
72989
  "git",
@@ -72814,7 +73036,7 @@ async function currentBranch(repoDir) {
72814
73036
  }
72815
73037
  async function repoStatus(repoDir) {
72816
73038
  const empty = { branch: "", ahead: 0, behind: 0, staged: [], unstaged: [], untracked: [], clean: true };
72817
- if (!repoDir || !existsSync12(repoDir)) return empty;
73039
+ if (!repoDir || !existsSync14(repoDir)) return empty;
72818
73040
  try {
72819
73041
  const { stdout } = await exec5("git", ["status", "--porcelain=v1", "-z", "-b"], { cwd: repoDir, ...GIT4 });
72820
73042
  const parts = stdout.split("\0");
@@ -72847,7 +73069,7 @@ async function repoStatus(repoDir) {
72847
73069
  }
72848
73070
  }
72849
73071
  async function listStashes(repoDir) {
72850
- if (!repoDir || !existsSync12(repoDir)) return [];
73072
+ if (!repoDir || !existsSync14(repoDir)) return [];
72851
73073
  try {
72852
73074
  const { stdout } = await exec5("git", ["stash", "list", `--format=%gd${US}%s${US}%aI`], { cwd: repoDir, ...GIT4 });
72853
73075
  return stdout.split("\n").filter(Boolean).map((line) => {
@@ -72859,7 +73081,7 @@ async function listStashes(repoDir) {
72859
73081
  }
72860
73082
  }
72861
73083
  async function listGraph(repoDir, limit = 200, opts2 = {}) {
72862
- if (!repoDir || !existsSync12(repoDir)) return { commits: [], current: "", total: 0 };
73084
+ if (!repoDir || !existsSync14(repoDir)) return { commits: [], current: "", total: 0 };
72863
73085
  try {
72864
73086
  const fmt = ["%H", "%P", "%an", "%aI", "%s", "%D"].join(US);
72865
73087
  const refArgs = opts2.branches?.length ? ["--end-of-options", ...opts2.branches] : [
@@ -72929,7 +73151,7 @@ async function changedOf(repoDir, sha2) {
72929
73151
  }
72930
73152
  async function compareCommits(repoDir, from, to) {
72931
73153
  const empty = { from, to, changed: [] };
72932
- if (!repoDir || !existsSync12(repoDir)) return empty;
73154
+ if (!repoDir || !existsSync14(repoDir)) return empty;
72933
73155
  try {
72934
73156
  const [num, name2] = await Promise.all([
72935
73157
  exec5("git", ["diff", "--numstat", "-z", "--no-renames", "--end-of-options", from, to], { cwd: repoDir, ...GIT4 }),
@@ -72941,7 +73163,7 @@ async function compareCommits(repoDir, from, to) {
72941
73163
  }
72942
73164
  }
72943
73165
  async function searchCommits(repoDir, q2, by = "all") {
72944
- if (!repoDir || !existsSync12(repoDir) || !q2) return [];
73166
+ if (!repoDir || !existsSync14(repoDir) || !q2) return [];
72945
73167
  const run6 = async (extra) => {
72946
73168
  try {
72947
73169
  return (await exec5("git", ["log", "--all", "--format=%H", ...extra], { cwd: repoDir, ...GIT4 })).stdout.split("\n").filter(Boolean);
@@ -73188,7 +73410,7 @@ async function runGitOp(repoDir, op) {
73188
73410
  }
73189
73411
  }
73190
73412
  async function workingStatus(repoDir) {
73191
- if (!repoDir || !existsSync12(repoDir)) return { branch: "", staged: [], unstaged: [] };
73413
+ if (!repoDir || !existsSync14(repoDir)) return { branch: "", staged: [], unstaged: [] };
73192
73414
  const [branch, staged, unstaged] = await Promise.all([
73193
73415
  currentBranch(repoDir),
73194
73416
  changedFiles(repoDir, ["--cached"]),
@@ -73197,7 +73419,7 @@ async function workingStatus(repoDir) {
73197
73419
  return { branch, staged, unstaged };
73198
73420
  }
73199
73421
  async function workingFileDiff(repoDir, path, staged) {
73200
- if (!repoDir || !existsSync12(repoDir)) return null;
73422
+ if (!repoDir || !existsSync14(repoDir)) return null;
73201
73423
  repoAbsPath(repoDir, path, true);
73202
73424
  const changed = staged ? await changedFiles(repoDir, ["--cached"]) : await withTempIndex(repoDir, (env) => changedFiles(repoDir, [], env));
73203
73425
  const cf = changed.find((c) => c.path === path);
@@ -73406,7 +73628,7 @@ async function deleteBranches(repoDir, names, opts2) {
73406
73628
  import { execFile as execFile9 } from "node:child_process";
73407
73629
  import { realpathSync as realpathSync8 } from "node:fs";
73408
73630
  import { stat as stat5 } from "node:fs/promises";
73409
- import { basename as basename8, dirname as dirname12, join as join26, resolve as resolve17, sep as sep7 } from "node:path";
73631
+ import { basename as basename8, dirname as dirname13, join as join28, resolve as resolve17, sep as sep8 } from "node:path";
73410
73632
  import { promisify as promisify8 } from "node:util";
73411
73633
  var exec7 = promisify8(execFile9);
73412
73634
  var GIT6 = { timeout: 6e4, maxBuffer: 1 << 24, encoding: "utf8" };
@@ -73447,20 +73669,20 @@ var real = (p3) => {
73447
73669
  return realpathSync8(p3);
73448
73670
  } catch {
73449
73671
  const path = resolve17(p3);
73450
- const parent = dirname12(path);
73451
- return parent === path ? path : join26(real(parent), basename8(path));
73672
+ const parent = dirname13(path);
73673
+ return parent === path ? path : join28(real(parent), basename8(path));
73452
73674
  }
73453
73675
  };
73454
73676
  function hasWorktreeSession(path, sessionId2, sessions) {
73455
73677
  const target3 = real(path);
73456
73678
  return sessions.some((s2) => {
73457
73679
  const current = real(s2.cwd);
73458
- return s2.id === sessionId2 || current === target3 || current.startsWith(target3 + sep7);
73680
+ return s2.id === sessionId2 || current === target3 || current.startsWith(target3 + sep8);
73459
73681
  });
73460
73682
  }
73461
73683
  async function bornAt(path) {
73462
73684
  try {
73463
- const st2 = await stat5(join26(path, ".git"));
73685
+ const st2 = await stat5(join28(path, ".git"));
73464
73686
  const ms = st2.birthtimeMs > 0 ? st2.birthtimeMs : st2.mtimeMs;
73465
73687
  return new Date(ms).toISOString();
73466
73688
  } catch {
@@ -73485,7 +73707,7 @@ async function listWorktrees(repoDir, inputs) {
73485
73707
  const rows = [];
73486
73708
  for (const w of raw) {
73487
73709
  const path = resolve17(w.path);
73488
- if (path === trash || path.startsWith(trash + sep7)) continue;
73710
+ if (path === trash || path.startsWith(trash + sep8)) continue;
73489
73711
  const name2 = basename8(path);
73490
73712
  const deletable = path !== baseReal && path !== mainPath;
73491
73713
  const spec = inputs.specs.get(name2);
@@ -73672,12 +73894,12 @@ async function recordHeadSha(specId, worktree, read2 = readHead) {
73672
73894
  init_local_binding();
73673
73895
 
73674
73896
  // src/services/session-worktree.ts
73675
- import { resolve as resolve18, sep as sep8 } from "node:path";
73897
+ import { resolve as resolve18, sep as sep9 } from "node:path";
73676
73898
  function ownsWorktree(repoDir, cwd) {
73677
73899
  const base3 = resolve18(repoDir);
73678
73900
  const dir2 = resolve18(cwd);
73679
73901
  const container = resolve18(base3, ".worktrees");
73680
- return dir2 !== base3 && dir2 !== container && dir2.startsWith(container + sep8);
73902
+ return dir2 !== base3 && dir2 !== container && dir2.startsWith(container + sep9);
73681
73903
  }
73682
73904
 
73683
73905
  // src/services/worktree-reaper.ts
@@ -73688,7 +73910,7 @@ init_notifications2();
73688
73910
  import { execFile as execFile11 } from "node:child_process";
73689
73911
  import { readdir as readdir4, rm as rm3 } from "node:fs/promises";
73690
73912
  import { promisify as promisify10 } from "node:util";
73691
- import { join as join27, resolve as resolve19 } from "node:path";
73913
+ import { join as join29, resolve as resolve19 } from "node:path";
73692
73914
  var TICK_MS = 6e4;
73693
73915
  var trashDirOf = (repoDir) => resolve19(repoDir, ".worktrees", ".trash");
73694
73916
  var exec8 = promisify10(execFile11);
@@ -73749,7 +73971,7 @@ async function sweepRepo(repoDir, projectId, deps = prodReaperDeps) {
73749
73971
  }
73750
73972
  let removed = 0;
73751
73973
  for (const entry2 of entries4) {
73752
- const path = join27(dir2, entry2);
73974
+ const path = join29(dir2, entry2);
73753
73975
  const known2 = pending.get(path);
73754
73976
  const row = known2 ?? { path, repoDir, projectId, entry: entry2, sessionId: sessionIdOf(entry2), since: Date.now() };
73755
73977
  pending.set(path, row);
@@ -74213,8 +74435,8 @@ function appendSourceHistory(current, entry2) {
74213
74435
 
74214
74436
  // src/services/spec-reset.ts
74215
74437
  import { execFile as execFile13 } from "node:child_process";
74216
- import { existsSync as existsSync13 } from "node:fs";
74217
- import { join as join28 } from "node:path";
74438
+ import { existsSync as existsSync15 } from "node:fs";
74439
+ import { join as join30 } from "node:path";
74218
74440
  import { promisify as promisify12 } from "node:util";
74219
74441
 
74220
74442
  // src/services/stage-artifacts.ts
@@ -74269,14 +74491,14 @@ async function planSpecReset(spec) {
74269
74491
  const repoDir = await resolveRepoDir(spec.projectId);
74270
74492
  if (!repoDir) return EMPTY3;
74271
74493
  const sid = sessionIdForSpec(spec.id);
74272
- const wt = join28(repoDir, ".worktrees", sid);
74494
+ const wt = join30(repoDir, ".worktrees", sid);
74273
74495
  const branch = `hanoman/${sid}`;
74274
74496
  const hasBranch = await shaResolvable(repoDir, `refs/heads/${branch}`);
74275
74497
  return {
74276
74498
  // Berkas fase seluruh stage yang ditinggalkan — rentang yang sama persis dengan revert stage
74277
74499
  // SPEC-167, karena tujuan akhirnya memang sama: item berdiri lagi di `brainstorming`.
74278
74500
  wouldDelete: await artifactsToRemove(spec.projectId, spec.id, "brainstorming", spec.stage),
74279
- worktree: existsSync13(wt) ? wt : null,
74501
+ worktree: existsSync15(wt) ? wt : null,
74280
74502
  branch: hasBranch ? branch : null
74281
74503
  };
74282
74504
  }
@@ -74353,6 +74575,61 @@ init_remote_session();
74353
74575
  init_registry();
74354
74576
  init_spec_attachment_dir();
74355
74577
  init_uploads();
74578
+
74579
+ // src/services/spec-mockups.ts
74580
+ init_local_binding();
74581
+ init_spec_review2();
74582
+ import { existsSync as existsSync16, readdirSync as readdirSync9, realpathSync as realpathSync10, statSync as statSync5 } from "node:fs";
74583
+ import { readFile as readFile7 } from "node:fs/promises";
74584
+ import { extname as extname2, join as join33, sep as sep10 } from "node:path";
74585
+ var MOCKUP_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]*\.(png|jpe?g|webp)$/i;
74586
+ var MIME = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".webp": "image/webp" };
74587
+ var isDir = (p3) => {
74588
+ try {
74589
+ return statSync5(p3).isDirectory();
74590
+ } catch {
74591
+ return false;
74592
+ }
74593
+ };
74594
+ async function resolveMockupDir(spec) {
74595
+ const repoDir = await resolveRepoDir(spec.projectId);
74596
+ if (!repoDir) return null;
74597
+ const rel = join33("docs", "mockups", spec.id.toLowerCase());
74598
+ const worktree = join33(worktreeDir(repoDir, spec.id), rel);
74599
+ if (isDir(worktree)) return { source: "worktree", dir: worktree };
74600
+ const repo = join33(repoDir, rel);
74601
+ return isDir(repo) ? { source: "repo", dir: repo } : null;
74602
+ }
74603
+ async function listMockups(spec) {
74604
+ const found = await resolveMockupDir(spec);
74605
+ if (!found) return { source: null, dir: null, items: [] };
74606
+ const items = readdirSync9(found.dir).filter((n2) => MOCKUP_NAME.test(n2)).sort().flatMap((name2) => {
74607
+ try {
74608
+ const st2 = statSync5(join33(found.dir, name2));
74609
+ return st2.isFile() ? [{ name: name2, size: st2.size, modifiedAt: st2.mtime.toISOString() }] : [];
74610
+ } catch {
74611
+ return [];
74612
+ }
74613
+ });
74614
+ return { source: found.source, dir: found.dir, items };
74615
+ }
74616
+ async function readMockup(spec, name2) {
74617
+ if (!MOCKUP_NAME.test(name2)) return null;
74618
+ const found = await resolveMockupDir(spec);
74619
+ if (!found) return null;
74620
+ const file = join33(found.dir, name2);
74621
+ if (!existsSync16(file)) return null;
74622
+ try {
74623
+ const root = realpathSync10(found.dir);
74624
+ const real2 = realpathSync10(file);
74625
+ if (!real2.startsWith(root + sep10) || !statSync5(real2).isFile()) return null;
74626
+ return { buf: await readFile7(real2), mime: MIME[extname2(real2).toLowerCase()] ?? "application/octet-stream" };
74627
+ } catch {
74628
+ return null;
74629
+ }
74630
+ }
74631
+
74632
+ // src/routes/specs.ts
74356
74633
  init_stage_machine();
74357
74634
 
74358
74635
  // src/services/spec-docs.ts
@@ -76834,7 +77111,7 @@ init_stage_machine();
76834
77111
  init_notifications2();
76835
77112
  init_sync_notify();
76836
77113
  init_spec_deps();
76837
- import { createHash as createHash11 } from "node:crypto";
77114
+ import { createHash as createHash12 } from "node:crypto";
76838
77115
  var specNum = (id2) => Number.parseInt(id2.match(/\d+/)?.[0] ?? "0", 10);
76839
77116
  var selectOf = (shape) => Object.fromEntries(
76840
77117
  Object.keys(shape).filter((k) => k !== "blockedBy").map((k) => [k, true])
@@ -76926,7 +77203,7 @@ async function liveSignature() {
76926
77203
  async function specsDigest() {
76927
77204
  const agg = await prisma.spec.aggregate({ _count: { _all: true }, _max: { updatedAt: true }, _sum: { version: true } });
76928
77205
  const db = `${agg._count._all}:${agg._max.updatedAt?.getTime() ?? 0}:${agg._sum.version ?? 0}`;
76929
- return createHash11("sha1").update(`${db}|${await liveSignature()}`).digest("hex");
77206
+ return createHash12("sha1").update(`${db}|${await liveSignature()}`).digest("hex");
76930
77207
  }
76931
77208
  async function liveSpecs(filter = {}) {
76932
77209
  await liveOverlayTick();
@@ -77254,6 +77531,24 @@ ${item.context}` : item.context;
77254
77531
  await syncSpecAttachmentsDir(spec.id, spec.projectId);
77255
77532
  return reply.code(201).send(result);
77256
77533
  });
77534
+ app3.get("/specs/:id/mockups", async (req, reply) => {
77535
+ const { id: id2 } = req.params;
77536
+ const spec = await prisma.spec.findUnique({ where: { id: id2 }, select: { id: true, projectId: true } });
77537
+ if (!spec) return reply.code(404).send({ error: "not found" });
77538
+ return listMockups(spec);
77539
+ });
77540
+ app3.get("/specs/:id/mockups/:name", async (req, reply) => {
77541
+ const { id: id2, name: name2 } = req.params;
77542
+ const spec = await prisma.spec.findUnique({ where: { id: id2 }, select: { id: true, projectId: true } });
77543
+ const file = spec ? await readMockup(spec, name2) : null;
77544
+ if (!file) return reply.code(404).send({ error: "not found" });
77545
+ reply.header("content-type", file.mime);
77546
+ reply.header("content-disposition", `inline; filename="${name2}"`);
77547
+ reply.header("x-content-type-options", "nosniff");
77548
+ reply.header("content-security-policy", "sandbox; default-src 'none'");
77549
+ reply.header("cache-control", "no-cache");
77550
+ return reply.send(file.buf);
77551
+ });
77257
77552
  app3.get("/specs/:id/attachments/:attId", async (req, reply) => {
77258
77553
  const { id: id2, attId } = req.params;
77259
77554
  const a = await prisma.specAttachment.findUnique({ where: { id: attId } });
@@ -77349,7 +77644,7 @@ ${item.context}` : item.context;
77349
77644
  });
77350
77645
  const specWithProject = (id2) => prisma.spec.findUnique({ where: { id: id2 }, include: { project: true } });
77351
77646
  const resolveReview = async (repoDir, spec) => {
77352
- if (existsSync14(worktreeDir(repoDir, spec.id))) return { wt: true };
77647
+ if (existsSync17(worktreeDir(repoDir, spec.id))) return { wt: true };
77353
77648
  if (spec.baseSha && spec.headSha && await shaResolvable(repoDir, spec.baseSha) && await shaResolvable(repoDir, spec.headSha))
77354
77649
  return { wt: false, base: spec.baseSha, head: spec.headSha };
77355
77650
  const r = await specCommitRange(repoDir, spec.id);
@@ -77645,7 +77940,7 @@ import { spawn as spawn5 } from "node:child_process";
77645
77940
  // src/services/git-remotes.ts
77646
77941
  import { execFile as execFile16 } from "node:child_process";
77647
77942
  import { promisify as promisify15 } from "node:util";
77648
- import { existsSync as existsSync17 } from "node:fs";
77943
+ import { existsSync as existsSync20 } from "node:fs";
77649
77944
  var exec10 = promisify15(execFile16);
77650
77945
  var GIT7 = { maxBuffer: 1 << 24, encoding: "utf8" };
77651
77946
  function parseRemote(url2) {
@@ -77664,7 +77959,7 @@ function prUrl(remoteUrl, branch, base3 = "main") {
77664
77959
  return null;
77665
77960
  }
77666
77961
  async function listRemotes(repoDir) {
77667
- if (!repoDir || !existsSync17(repoDir)) return [];
77962
+ if (!repoDir || !existsSync20(repoDir)) return [];
77668
77963
  try {
77669
77964
  const { stdout } = await exec10("git", ["remote", "-v"], { cwd: repoDir, ...GIT7 });
77670
77965
  const map = /* @__PURE__ */ new Map();
@@ -77712,7 +78007,7 @@ init_integrate();
77712
78007
  init_safe_repo_path();
77713
78008
  import { lstat as lstat2, rename as rename4, rm as rm6 } from "node:fs/promises";
77714
78009
  import { createWriteStream } from "node:fs";
77715
- import { join as join36 } from "node:path";
78010
+ import { join as join39 } from "node:path";
77716
78011
  import { randomUUID as randomUUID11 } from "node:crypto";
77717
78012
  import { pipeline } from "node:stream/promises";
77718
78013
  var EntryExistsError = class extends Error {
@@ -77773,7 +78068,7 @@ async function saveUpload2(repoDir, rel, source, opts2 = {}) {
77773
78068
  if (current && !opts2.overwrite) return { status: "exists" };
77774
78069
  if (current && !current.isFile())
77775
78070
  throw new PathContainmentError("repository path ditolak: target bukan file regular");
77776
- const temp = join36(entry2.parent, `.hanoman-${randomUUID11()}.tmp`);
78071
+ const temp = join39(entry2.parent, `.hanoman-${randomUUID11()}.tmp`);
77777
78072
  try {
77778
78073
  await pipeline(source, createWriteStream(temp, { flags: "wx", mode: 384 }));
77779
78074
  if (opts2.isTruncated?.()) {
@@ -78264,15 +78559,15 @@ async function finishGraphOp(reply, id2, repoDir, r, verb) {
78264
78559
  // src/routes/fs.ts
78265
78560
  import { readdir as readdir8 } from "node:fs/promises";
78266
78561
  import { homedir as homedir9 } from "node:os";
78267
- import { resolve as resolve22, dirname as dirname15, join as join37 } from "node:path";
78562
+ import { resolve as resolve22, dirname as dirname16, join as join40 } from "node:path";
78268
78563
  async function fs_default(app3) {
78269
78564
  app3.get("/fs/browse", async (req, reply) => {
78270
78565
  const q2 = req.query.path;
78271
78566
  const dir2 = q2 && q2.trim() ? resolve22(q2.trim()) : homedir9();
78272
78567
  try {
78273
78568
  const ents = await readdir8(dir2, { withFileTypes: true });
78274
- const entries4 = ents.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => ({ name: e.name, path: join37(dir2, e.name) })).sort((a, b) => a.name.localeCompare(b.name));
78275
- const parent = dirname15(dir2);
78569
+ const entries4 = ents.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => ({ name: e.name, path: join40(dir2, e.name) })).sort((a, b) => a.name.localeCompare(b.name));
78570
+ const parent = dirname16(dir2);
78276
78571
  return { path: dir2, parent: parent === dir2 ? null : parent, entries: entries4 };
78277
78572
  } catch {
78278
78573
  return reply.code(400).send({ error: `tak bisa membaca folder "${dir2}"` });
@@ -78286,7 +78581,9 @@ init_src();
78286
78581
  init_src2();
78287
78582
  init_session_phases();
78288
78583
  init_spec_review2();
78289
- import { existsSync as existsSync18 } from "node:fs";
78584
+ import { existsSync as existsSync21 } from "node:fs";
78585
+ import { execFileSync as execFileSync9 } from "node:child_process";
78586
+ import { randomUUID as randomUUID13 } from "node:crypto";
78290
78587
  init_integrate();
78291
78588
  init_settings3();
78292
78589
  init_codex_trust();
@@ -78349,7 +78646,7 @@ function setBounded(map, key, value, maxKeys) {
78349
78646
  // src/services/auth.ts
78350
78647
  init_src2();
78351
78648
  init_db();
78352
- import { randomBytes as randomBytes4, scrypt as scryptCb, timingSafeEqual as timingSafeEqual4, createHash as createHash16 } from "node:crypto";
78649
+ import { randomBytes as randomBytes4, scrypt as scryptCb, timingSafeEqual as timingSafeEqual4, createHash as createHash17 } from "node:crypto";
78353
78650
  import { promisify as promisify16 } from "node:util";
78354
78651
  var scrypt = promisify16(scryptCb);
78355
78652
  var COOKIE_NAME = "hn_session";
@@ -78367,7 +78664,7 @@ async function verifyPassword(pw, stored) {
78367
78664
  return key.length === want.length && timingSafeEqual4(key, want);
78368
78665
  }
78369
78666
  var newSessionToken = () => randomBytes4(32).toString("base64url");
78370
- var sessionId = (token) => createHash16("sha256").update(token).digest("hex");
78667
+ var sessionId = (token) => createHash17("sha256").update(token).digest("hex");
78371
78668
  async function createSession2(userId) {
78372
78669
  const token = newSessionToken();
78373
78670
  await prisma.session.create({
@@ -78428,9 +78725,9 @@ function cookieOpts(req) {
78428
78725
 
78429
78726
  // src/services/device-token.ts
78430
78727
  init_db();
78431
- import { randomBytes as randomBytes5, createHash as createHash17 } from "node:crypto";
78728
+ import { randomBytes as randomBytes5, createHash as createHash18 } from "node:crypto";
78432
78729
  var newDeviceToken = () => randomBytes5(32).toString("base64url");
78433
- var tokenHash = (token) => createHash17("sha256").update(token).digest("hex");
78730
+ var tokenHash = (token) => createHash18("sha256").update(token).digest("hex");
78434
78731
  async function issueDeviceToken(userId, name2) {
78435
78732
  const token = newDeviceToken();
78436
78733
  const row = await prisma.deviceToken.create({ data: { userId, name: name2, tokenHash: tokenHash(token) } });
@@ -78643,14 +78940,14 @@ function installCommand(m, agent, shell = shellBin()) {
78643
78940
  init_local_binding();
78644
78941
 
78645
78942
  // src/services/terminal-diag.ts
78646
- import { appendFileSync as appendFileSync4, mkdirSync as mkdirSync16, statSync as statSync4, rmSync as rmSync10 } from "node:fs";
78647
- import { join as join38 } from "node:path";
78943
+ import { appendFileSync as appendFileSync4, mkdirSync as mkdirSync17, statSync as statSync6, rmSync as rmSync11 } from "node:fs";
78944
+ import { join as join41 } from "node:path";
78648
78945
  var DIAG_MAX_BYTES = 2 * 1024 * 1024;
78649
78946
  var KINDS = /* @__PURE__ */ new Set(["key", "comp", "data", "ack", "pred"]);
78650
78947
  var ID = /^[A-Za-z0-9_-]{1,64}$/;
78651
78948
  function diagFile(home3, sessionId2) {
78652
78949
  if (!ID.test(sessionId2)) throw new Error(`id sesi tak sah untuk diag: ${sessionId2}`);
78653
- return join38(home3, "diag", `${sessionId2}.jsonl`);
78950
+ return join41(home3, "diag", `${sessionId2}.jsonl`);
78654
78951
  }
78655
78952
  function usable(ev) {
78656
78953
  if (!ev || typeof ev !== "object") return false;
@@ -78661,9 +78958,9 @@ function appendDiag(home3, sessionId2, events) {
78661
78958
  const file = diagFile(home3, sessionId2);
78662
78959
  const rows = events.filter(usable);
78663
78960
  if (!rows.length) return;
78664
- mkdirSync16(join38(home3, "diag"), { recursive: true });
78961
+ mkdirSync17(join41(home3, "diag"), { recursive: true });
78665
78962
  try {
78666
- if (statSync4(file).size > DIAG_MAX_BYTES) rmSync10(file);
78963
+ if (statSync6(file).size > DIAG_MAX_BYTES) rmSync11(file);
78667
78964
  } catch {
78668
78965
  }
78669
78966
  appendFileSync4(file, rows.map((r) => JSON.stringify(r)).join("\n") + "\n");
@@ -78743,11 +79040,11 @@ init_uploads();
78743
79040
  // src/services/agent-invocations.ts
78744
79041
  init_src();
78745
79042
  init_db();
78746
- import { createHash as createHash18, randomUUID as randomUUID12 } from "node:crypto";
79043
+ import { createHash as createHash19, randomUUID as randomUUID12 } from "node:crypto";
78747
79044
  import { execFileSync as execFileSync8 } from "node:child_process";
78748
79045
  import { homedir as homedir10 } from "node:os";
78749
- import { readFileSync as readFileSync19, realpathSync as realpathSync10, statSync as statSync5 } from "node:fs";
78750
- import { isAbsolute as isAbsolute12, relative as relative7, resolve as resolve23 } from "node:path";
79046
+ import { readFileSync as readFileSync20, realpathSync as realpathSync11, statSync as statSync7 } from "node:fs";
79047
+ import { isAbsolute as isAbsolute12, relative as relative8, resolve as resolve23 } from "node:path";
78751
79048
 
78752
79049
  // src/services/session-event-relay.ts
78753
79050
  init_session_event_token();
@@ -78755,7 +79052,7 @@ init_session_event_spool();
78755
79052
  init_ingress_policy();
78756
79053
  import { constants as constants4 } from "node:fs";
78757
79054
  import { mkdir as mkdir8, open as open4, readdir as readdir9, rm as rm7 } from "node:fs/promises";
78758
- import { join as join39 } from "node:path";
79055
+ import { join as join42 } from "node:path";
78759
79056
  var MAX_EVENT_BYTES = 1e6;
78760
79057
  var MAX_FILES_PER_DRAIN = 1e3;
78761
79058
  var SESSION_ID_RE = /^[a-z0-9_-]+$/;
@@ -78806,7 +79103,7 @@ async function drainSpool(app3, root, observation, host2) {
78806
79103
  let readBuffer;
78807
79104
  for (const session of await readdir9(root, { withFileTypes: true })) {
78808
79105
  if (!session.isDirectory() || !SESSION_ID_RE.test(session.name)) continue;
78809
- const dir2 = join39(root, session.name);
79106
+ const dir2 = join42(root, session.name);
78810
79107
  let entries4;
78811
79108
  try {
78812
79109
  entries4 = await readdir9(dir2, { withFileTypes: true });
@@ -78818,7 +79115,7 @@ async function drainSpool(app3, root, observation, host2) {
78818
79115
  if (!entry2.isFile() || !entry2.name.endsWith(".json")) continue;
78819
79116
  const eventAt = /^(\d{1,15})-/.exec(entry2.name)?.[1];
78820
79117
  if (++examined > MAX_FILES_PER_DRAIN) return delivered;
78821
- const path = join39(dir2, entry2.name);
79118
+ const path = join42(dir2, entry2.name);
78822
79119
  let payload;
78823
79120
  let handle;
78824
79121
  try {
@@ -78905,7 +79202,7 @@ var MAX_TRANSCRIPT_BYTES2 = 10 * 1024 * 1024;
78905
79202
  var ANSI2 = /[\u001b\u009b](?:\][^\u0007]*(?:\u0007|\u001b\\)|\[[0-?]*[ -/]*[@-~]|[@-_])/g;
78906
79203
  var snapshotHashes = /* @__PURE__ */ new Map();
78907
79204
  var keyOf = (x) => `${x.sessionId}\0${x.runtimeInvocationId}`;
78908
- var hash3 = (value) => createHash18("sha256").update(value).digest("hex");
79205
+ var hash3 = (value) => createHash19("sha256").update(value).digest("hex");
78909
79206
  var defaultGitStatus = (cwd) => {
78910
79207
  try {
78911
79208
  return execFileSync8("git", ["-C", cwd, "status", "--porcelain=v1", "-z"], {
@@ -78940,7 +79237,7 @@ var transcriptRoots = () => [
78940
79237
  resolve23(process.env.CODEX_HOME ?? `${homedir10()}/.codex`)
78941
79238
  ];
78942
79239
  var inside = (path, root) => {
78943
- const rel = relative7(root, path);
79240
+ const rel = relative8(root, path);
78944
79241
  return rel === "" || !rel.startsWith("..") && !isAbsolute12(rel);
78945
79242
  };
78946
79243
  var EMPTY_USAGE = { inputTokens: null, outputTokens: null, cachedTokens: null };
@@ -79013,12 +79310,12 @@ function usageFromText(text2) {
79013
79310
  function transcriptUsage(path, roots = transcriptRoots()) {
79014
79311
  if (!path) return EMPTY_USAGE;
79015
79312
  try {
79016
- const real2 = realpathSync10(path);
79017
- const safeRoots = roots.map((root) => realpathSync10(root));
79313
+ const real2 = realpathSync11(path);
79314
+ const safeRoots = roots.map((root) => realpathSync11(root));
79018
79315
  if (!safeRoots.some((root) => inside(real2, root))) return EMPTY_USAGE;
79019
- const info = statSync5(real2);
79316
+ const info = statSync7(real2);
79020
79317
  if (!info.isFile() || info.size > MAX_TRANSCRIPT_BYTES2) return EMPTY_USAGE;
79021
- return usageFromText(readFileSync19(real2, "utf8"));
79318
+ return usageFromText(readFileSync20(real2, "utf8"));
79022
79319
  } catch {
79023
79320
  return EMPTY_USAGE;
79024
79321
  }
@@ -79303,25 +79600,25 @@ init_ask();
79303
79600
  // src/services/decision-guard.ts
79304
79601
  init_src2();
79305
79602
  init_db();
79306
- import { mkdirSync as mkdirSync17, readFileSync as readFileSync20, readdirSync as readdirSync9, rmSync as rmSync11, statSync as statSync6, writeFileSync as writeFileSync17 } from "node:fs";
79307
- import { join as join40 } from "node:path";
79603
+ import { mkdirSync as mkdirSync18, readFileSync as readFileSync21, readdirSync as readdirSync11, rmSync as rmSync12, statSync as statSync8, writeFileSync as writeFileSync18 } from "node:fs";
79604
+ import { join as join43 } from "node:path";
79308
79605
  var SESSION_HEADER = "x-hanoman-session";
79309
79606
  var SAFE_ID = /^[A-Za-z0-9._-]{1,200}$/;
79310
79607
  var launchers = /* @__PURE__ */ new Map();
79311
79608
  var STALE_MS = 30 * 24 * 60 * 6e4;
79312
- var launcherDir = (env = process.env) => join40(resolveHome(env), "session-launchers");
79609
+ var launcherDir = (env = process.env) => join43(resolveHome(env), "session-launchers");
79313
79610
  function recordLauncher(sessionId2, agentId, env = process.env) {
79314
79611
  if (!SAFE_ID.test(sessionId2)) return;
79315
79612
  const known2 = launchersFor(sessionId2, env);
79316
79613
  known2.add(agentId);
79317
79614
  try {
79318
79615
  const dir2 = launcherDir(env);
79319
- mkdirSync17(dir2, { recursive: true, mode: 448 });
79320
- writeFileSync17(join40(dir2, sessionId2), [...known2].join("\n"), { mode: 384 });
79616
+ mkdirSync18(dir2, { recursive: true, mode: 448 });
79617
+ writeFileSync18(join43(dir2, sessionId2), [...known2].join("\n"), { mode: 384 });
79321
79618
  const now = Date.now();
79322
- for (const f of readdirSync9(dir2)) {
79619
+ for (const f of readdirSync11(dir2)) {
79323
79620
  try {
79324
- if (now - statSync6(join40(dir2, f)).mtimeMs > STALE_MS) rmSync11(join40(dir2, f), { force: true });
79621
+ if (now - statSync8(join43(dir2, f)).mtimeMs > STALE_MS) rmSync12(join43(dir2, f), { force: true });
79325
79622
  } catch {
79326
79623
  }
79327
79624
  }
@@ -79333,7 +79630,7 @@ function launchersFor(sessionId2, env) {
79333
79630
  if (hot) return hot;
79334
79631
  const ids = /* @__PURE__ */ new Set();
79335
79632
  try {
79336
- for (const l of readFileSync20(join40(launcherDir(env), sessionId2), "utf8").split("\n")) if (l.trim()) ids.add(l.trim());
79633
+ for (const l of readFileSync21(join43(launcherDir(env), sessionId2), "utf8").split("\n")) if (l.trim()) ids.add(l.trim());
79337
79634
  } catch {
79338
79635
  }
79339
79636
  launchers.set(sessionId2, ids);
@@ -79647,6 +79944,26 @@ async function terminal_default(app3, opts2) {
79647
79944
  }
79648
79945
  const { agent, model, effort: effort2 } = await terminalAgentDefaults(parsed.data);
79649
79946
  if (agent === "codex") ensureCodexTrust(repoDir);
79947
+ if (parsed.data.worktree) {
79948
+ const id2 = `term-${randomUUID13().slice(0, 8)}`;
79949
+ const branch = `term/${id2.slice("term-".length)}`;
79950
+ const from = parsed.data.branchFrom ?? "HEAD";
79951
+ const result = await withSessionAdmission({ id: id2, exempt: false, force: parsed.data.force }, async () => {
79952
+ const wt = `${repoDir}/.worktrees/${id2}`;
79953
+ try {
79954
+ realGit.addWorktree(repoDir, wt, from);
79955
+ execFileSync9("git", ["switch", "-q", "-c", branch], { cwd: wt, stdio: "pipe" });
79956
+ } catch (e) {
79957
+ if (ownsWorktree(repoDir, wt)) realGit.removeWorktree(repoDir, wt);
79958
+ return { code: 422, body: { error: `gagal membuat worktree dari "${from}": ${e.message}` } };
79959
+ }
79960
+ const s3 = createSession(project.id, wt, { id: id2, agent, model, effort: effort2, branch });
79961
+ return { code: 201, body: { id: s3.id } };
79962
+ }, (pane) => ({ code: 201, body: { id: pane.id } }));
79963
+ return reply.code(result.code).send(result.body);
79964
+ }
79965
+ if (parsed.data.branchFrom)
79966
+ return reply.code(400).send({ error: "branchFrom hanya berlaku bersama worktree:true" });
79650
79967
  const s2 = await createOperatorSession(
79651
79968
  project.id,
79652
79969
  repoDir,
@@ -79765,7 +80082,7 @@ async function terminal_default(app3, opts2) {
79765
80082
  if (!repoDir) return { ok: false, code: 409, msg: "project belum punya repoDir" };
79766
80083
  if (!s2.cwd.includes("/.worktrees/"))
79767
80084
  return { ok: false, code: 409, msg: "belum ada worktree untuk di-review \u2014 jalankan/lanjutkan sesi dulu", empty: "no-worktree" };
79768
- if (!existsSync18(s2.cwd))
80085
+ if (!existsSync21(s2.cwd))
79769
80086
  return { ok: false, code: 409, msg: "belum ada worktree untuk di-review \u2014 jalankan/lanjutkan sesi dulu", empty: "no-commit" };
79770
80087
  return { ok: true, id: s2.id, repoDir };
79771
80088
  };
@@ -80028,7 +80345,7 @@ async function terminalWorkspaceRoutes(app3) {
80028
80345
  // src/routes/vps.ts
80029
80346
  init_db();
80030
80347
  init_src();
80031
- import { readFileSync as readFileSync25, existsSync as existsSync22 } from "node:fs";
80348
+ import { readFileSync as readFileSync26, existsSync as existsSync24 } from "node:fs";
80032
80349
  import { homedir as homedir12 } from "node:os";
80033
80350
 
80034
80351
  // src/vps/catalog/catalog.data.ts
@@ -80236,14 +80553,14 @@ function resolveComponents(ids, profile) {
80236
80553
  }
80237
80554
 
80238
80555
  // src/services/vps-provision.ts
80239
- import { readFileSync as readFileSync22 } from "node:fs";
80556
+ import { readFileSync as readFileSync23 } from "node:fs";
80240
80557
 
80241
80558
  // src/services/vps-ssh.ts
80242
80559
  init_config2();
80243
80560
  import { spawn as spawn6 } from "node:child_process";
80244
- import { mkdtempSync as mkdtempSync4, rmSync as rmSync12, writeFileSync as writeFileSync18 } from "node:fs";
80561
+ import { mkdtempSync as mkdtempSync4, rmSync as rmSync13, writeFileSync as writeFileSync19 } from "node:fs";
80245
80562
  import { tmpdir as tmpdir7 } from "node:os";
80246
- import { dirname as dirname16, join as join41 } from "node:path";
80563
+ import { dirname as dirname17, join as join44 } from "node:path";
80247
80564
  var sshBin = () => effectiveStr("HANOMAN_SSH_BIN") ?? "ssh";
80248
80565
  function consoleArgv(t) {
80249
80566
  return [
@@ -80258,9 +80575,9 @@ function consoleArgv(t) {
80258
80575
  ];
80259
80576
  }
80260
80577
  function askpassScript() {
80261
- const dir2 = mkdtempSync4(join41(tmpdir7(), "hanoman-askpass-"));
80262
- const path = join41(dir2, "askpass.sh");
80263
- writeFileSync18(path, `#!/bin/sh
80578
+ const dir2 = mkdtempSync4(join44(tmpdir7(), "hanoman-askpass-"));
80579
+ const path = join44(dir2, "askpass.sh");
80580
+ writeFileSync19(path, `#!/bin/sh
80264
80581
  printf '%s' "$HANOMAN_SSH_PASSWORD"
80265
80582
  `, { mode: 448 });
80266
80583
  return path;
@@ -80299,7 +80616,7 @@ function sshExec(t, remoteCmd, opts2 = {}) {
80299
80616
  const timer11 = setTimeout(() => p3.kill("SIGKILL"), opts2.timeoutMs ?? 6e4);
80300
80617
  const done = (r) => {
80301
80618
  clearTimeout(timer11);
80302
- if (askpass) rmSync12(dirname16(askpass), { recursive: true, force: true });
80619
+ if (askpass) rmSync13(dirname17(askpass), { recursive: true, force: true });
80303
80620
  resolve25(r);
80304
80621
  };
80305
80622
  p3.stdout.on("data", (d) => {
@@ -80319,26 +80636,10 @@ function sshExec(t, remoteCmd, opts2 = {}) {
80319
80636
 
80320
80637
  // src/services/vps-audit.ts
80321
80638
  init_db();
80322
- import { existsSync as existsSync20, readFileSync as readFileSync21 } from "node:fs";
80323
- import { join as join43 } from "node:path";
80324
- import { fileURLToPath as fileURLToPath4 } from "node:url";
80325
-
80326
- // src/runner/deps.ts
80327
- import { existsSync as existsSync19 } from "node:fs";
80328
- import { dirname as dirname17, join as join42 } from "node:path";
80329
- function repoRootFrom(startDir) {
80330
- let dir2 = startDir;
80331
- for (let i = 0; i < 8; i++) {
80332
- if (existsSync19(join42(dir2, "pnpm-workspace.yaml"))) return dir2;
80333
- const parent = dirname17(dir2);
80334
- if (parent === dir2) break;
80335
- dir2 = parent;
80336
- }
80337
- return startDir;
80338
- }
80339
- var repoRoot = (startDir = process.cwd()) => repoRootFrom(startDir);
80340
-
80341
- // src/services/vps-audit.ts
80639
+ init_deps();
80640
+ import { existsSync as existsSync22, readFileSync as readFileSync22 } from "node:fs";
80641
+ import { join as join45 } from "node:path";
80642
+ import { fileURLToPath as fileURLToPath5 } from "node:url";
80342
80643
  init_sync_notify();
80343
80644
 
80344
80645
  // src/vps/scoring.ts
@@ -80438,11 +80739,11 @@ function parseHealth(out4) {
80438
80739
  return { uptime: h2.uptime ?? "", disk: h2.disk ?? "", mem: h2.mem ?? "", load: h2.load ?? "" };
80439
80740
  }
80440
80741
  var target2 = (v) => ({ host: v.host, port: v.port, user: v.user, keyPath: v.keyPath });
80441
- var packagedScript = (f) => fileURLToPath4(new URL(`../scripts/vps/${f}`, import.meta.url));
80442
- var moduleDir = () => fileURLToPath4(new URL(".", import.meta.url));
80742
+ var packagedScript = (f) => fileURLToPath5(new URL(`../scripts/vps/${f}`, import.meta.url));
80743
+ var moduleDir2 = () => fileURLToPath5(new URL(".", import.meta.url));
80443
80744
  var scriptPath = (f) => {
80444
80745
  const packed = packagedScript(f);
80445
- return existsSync20(packed) ? packed : join43(repoRoot(moduleDir()), "server", "scripts", "vps", f);
80746
+ return existsSync22(packed) ? packed : join45(repoRoot(moduleDir2()), "server", "scripts", "vps", f);
80446
80747
  };
80447
80748
  async function itemStatesOf(vpsId) {
80448
80749
  const rows = await prisma.vpsItemState.findMany({ where: { vpsId } });
@@ -80452,7 +80753,7 @@ async function runAudit(v) {
80452
80753
  const r = await sshExec(
80453
80754
  target2(v),
80454
80755
  "sudo -n bash -s",
80455
- { stdin: readFileSync21(scriptPath("audit.sh"), "utf8"), timeoutMs: 6e4 }
80756
+ { stdin: readFileSync22(scriptPath("audit.sh"), "utf8"), timeoutMs: 6e4 }
80456
80757
  );
80457
80758
  const audit = parseAudit(r.out);
80458
80759
  if (r.code !== 0 || audit.length === 0) return { ok: false, out: r.out };
@@ -80532,7 +80833,7 @@ function parseProvisionSteps(out4) {
80532
80833
  return m ? [{ item: m[1], status: m[2], detail: (m[3] ?? "").trim() }] : [];
80533
80834
  });
80534
80835
  }
80535
- var script = () => readFileSync22(scriptPath("provision.sh"), "utf8");
80836
+ var script = () => readFileSync23(scriptPath("provision.sh"), "utf8");
80536
80837
  async function probeComponents(v) {
80537
80838
  const r = await sshExec(v, "env MODE=probe bash -s", { stdin: script(), timeoutMs: PROBE_TIMEOUT_MS });
80538
80839
  const components2 = parseComponents(r.out);
@@ -80559,7 +80860,7 @@ async function readSetupToken(v, home3 = "/var/lib/hanoman") {
80559
80860
  }
80560
80861
 
80561
80862
  // src/services/vps-remediate.ts
80562
- import { readFileSync as readFileSync23 } from "node:fs";
80863
+ import { readFileSync as readFileSync24 } from "node:fs";
80563
80864
  function parseSteps2(out4) {
80564
80865
  return out4.split("\n").flatMap((line) => {
80565
80866
  const m = line.match(/^STEP (\S+) (would|ok|fail)(?: (.*))?$/);
@@ -80570,7 +80871,7 @@ async function remediate(v, items, dryRun) {
80570
80871
  const r = await sshExec(
80571
80872
  v,
80572
80873
  `sudo -n env ITEMS=${items.join(",")} SSH_PORT=${v.port} DRY_RUN=${dryRun ? "1" : ""} bash -s`,
80573
- { stdin: readFileSync23(scriptPath("remediate.sh"), "utf8"), timeoutMs: 3e5 }
80874
+ { stdin: readFileSync24(scriptPath("remediate.sh"), "utf8"), timeoutMs: 3e5 }
80574
80875
  );
80575
80876
  return { ok: r.code === 0, steps: parseSteps2(r.out), out: r.out };
80576
80877
  }
@@ -80686,48 +80987,48 @@ async function listVpsWithScore() {
80686
80987
  // src/services/vps-key.ts
80687
80988
  init_src2();
80688
80989
  init_config2();
80689
- import { execFileSync as execFileSync9 } from "node:child_process";
80690
- import { chmodSync as chmodSync9, copyFileSync, existsSync as existsSync21, mkdirSync as mkdirSync18, readFileSync as readFileSync24, unlinkSync as unlinkSync5 } from "node:fs";
80990
+ import { execFileSync as execFileSync10 } from "node:child_process";
80991
+ import { chmodSync as chmodSync10, copyFileSync, existsSync as existsSync23, mkdirSync as mkdirSync19, readFileSync as readFileSync25, unlinkSync as unlinkSync5 } from "node:fs";
80691
80992
  import { homedir as homedir11 } from "node:os";
80692
- import { join as join44 } from "node:path";
80993
+ import { join as join46 } from "node:path";
80693
80994
  var keyDir = () => effectiveStr("HANOMAN_SSH_KEY_DIR") ?? resolveDataDirs().sshKeys;
80694
80995
  var KEY_FILES = ["id_ed25519", "id_ed25519.pub"];
80695
80996
  function adoptLegacyKey(dir2) {
80696
80997
  if (effectiveStr("HANOMAN_SSH_KEY_DIR")) return;
80697
- const legacy = join44(homedir11(), ".hanoman");
80998
+ const legacy = join46(homedir11(), ".hanoman");
80698
80999
  if (legacy === dir2) return;
80699
- if (!KEY_FILES.every((f) => existsSync21(join44(legacy, f)))) return;
80700
- mkdirSync18(dir2, { recursive: true, mode: 448 });
81000
+ if (!KEY_FILES.every((f) => existsSync23(join46(legacy, f)))) return;
81001
+ mkdirSync19(dir2, { recursive: true, mode: 448 });
80701
81002
  for (const f of KEY_FILES) {
80702
- copyFileSync(join44(legacy, f), join44(dir2, f));
80703
- chmodSync9(join44(dir2, f), 384);
81003
+ copyFileSync(join46(legacy, f), join46(dir2, f));
81004
+ chmodSync10(join46(dir2, f), 384);
80704
81005
  }
80705
- for (const f of KEY_FILES) unlinkSync5(join44(legacy, f));
81006
+ for (const f of KEY_FILES) unlinkSync5(join46(legacy, f));
80706
81007
  console.log(`vps: key SSH dipindah dari ${legacy} ke ${dir2} (SPEC-846 \u2014 satu batas backup)`);
80707
81008
  }
80708
81009
  function ensureHanomanKey() {
80709
81010
  const dir2 = keyDir();
80710
- const privPath = join44(dir2, KEY_FILES[0]);
81011
+ const privPath = join46(dir2, KEY_FILES[0]);
80711
81012
  const pubPath = `${privPath}.pub`;
80712
- if (!existsSync21(privPath)) adoptLegacyKey(dir2);
80713
- if (!existsSync21(privPath)) {
80714
- mkdirSync18(dir2, { recursive: true, mode: 448 });
80715
- execFileSync9(
81013
+ if (!existsSync23(privPath)) adoptLegacyKey(dir2);
81014
+ if (!existsSync23(privPath)) {
81015
+ mkdirSync19(dir2, { recursive: true, mode: 448 });
81016
+ execFileSync10(
80716
81017
  "ssh-keygen",
80717
81018
  ["-t", "ed25519", "-N", "", "-C", "hanoman", "-f", privPath],
80718
81019
  { stdio: ["ignore", "ignore", "pipe"] }
80719
81020
  );
80720
81021
  }
80721
- return { privPath, pubPath, pub: readFileSync24(pubPath, "utf8").trim() };
81022
+ return { privPath, pubPath, pub: readFileSync25(pubPath, "utf8").trim() };
80722
81023
  }
80723
81024
 
80724
81025
  // src/services/vps-bootstrap.ts
80725
81026
  var INSTALL_CMD = `mkdir -p ~/.ssh && chmod 700 ~/.ssh && touch ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys && k=$(cat) && { grep -qxF "$k" ~/.ssh/authorized_keys || printf '%s\\n' "$k" >> ~/.ssh/authorized_keys; }`;
80726
81027
  async function bootstrapKey(t, password) {
80727
81028
  const { privPath, pub } = ensureHanomanKey();
80728
- const install = await sshExec(t, INSTALL_CMD, { stdin: `${pub}
81029
+ const install2 = await sshExec(t, INSTALL_CMD, { stdin: `${pub}
80729
81030
  `, password, timeoutMs: 6e4 });
80730
- if (install.code !== 0) return { ok: false, out: install.out };
81031
+ if (install2.code !== 0) return { ok: false, out: install2.out };
80731
81032
  const verify = await sshExec({ ...t, keyPath: privPath }, "true", { timeoutMs: 3e4 });
80732
81033
  if (verify.code !== 0) return { ok: false, out: verify.out };
80733
81034
  return { ok: true, keyPath: privPath };
@@ -80738,7 +81039,7 @@ init_session_launch_gate();
80738
81039
  init_settings3();
80739
81040
  init_sync_notify();
80740
81041
  function keyMissing(v) {
80741
- return !!v.keyPath && !existsSync22(v.keyPath);
81042
+ return !!v.keyPath && !existsSync24(v.keyPath);
80742
81043
  }
80743
81044
  async function vps_default(app3) {
80744
81045
  app3.get("/vps", async () => listVpsWithScore());
@@ -80966,7 +81267,7 @@ async function vps_default(app3) {
80966
81267
  const r = await sshExec(
80967
81268
  v,
80968
81269
  `sudo -n env SSH_PORT=${v.port} SSH_USER=${v.user} bash -s`,
80969
- { stdin: readFileSync25(scriptPath("harden.sh"), "utf8"), timeoutMs: 3e5 }
81270
+ { stdin: readFileSync26(scriptPath("harden.sh"), "utf8"), timeoutMs: 3e5 }
80970
81271
  );
80971
81272
  if (r.code !== 0) return reply.code(502).send({ error: "harden gagal lewat ssh", transcript: r.out });
80972
81273
  const verify = await sshExec(v, "true", { timeoutMs: 3e4 });
@@ -81114,9 +81415,9 @@ async function antigravity(app3) {
81114
81415
  // src/services/model-catalog.ts
81115
81416
  init_src2();
81116
81417
  init_src();
81117
- import { randomUUID as randomUUID13 } from "node:crypto";
81118
- import { readFile as readFile8, writeFile as writeFile6, rename as rename5, mkdir as mkdir9, rm as rm9 } from "node:fs/promises";
81119
- import { dirname as dirname18, join as join46 } from "node:path";
81418
+ import { randomUUID as randomUUID14 } from "node:crypto";
81419
+ import { readFile as readFile9, writeFile as writeFile6, rename as rename5, mkdir as mkdir9, rm as rm9 } from "node:fs/promises";
81420
+ import { dirname as dirname18, join as join48 } from "node:path";
81120
81421
 
81121
81422
  // src/services/model-catalog-parser.ts
81122
81423
  init_zod();
@@ -81223,7 +81524,7 @@ init_session_sandbox();
81223
81524
  import { spawn as spawn7 } from "node:child_process";
81224
81525
  import { mkdtemp as mkdtemp2, rm as rm8 } from "node:fs/promises";
81225
81526
  import { tmpdir as tmpdir8 } from "node:os";
81226
- import { join as join45 } from "node:path";
81527
+ import { join as join47 } from "node:path";
81227
81528
  var MAX_BYTES = 4 * 1024 * 1024;
81228
81529
  var TIMEOUT_MS = 2e4;
81229
81530
  var quote3 = (s2) => "'" + s2.replaceAll("'", "'\\''") + "'";
@@ -81322,7 +81623,7 @@ function runCatalogCommand(command, agent, cwd, env, timeoutMs = TIMEOUT_MS) {
81322
81623
  }
81323
81624
  async function probeModelCatalog(agent) {
81324
81625
  assertAntigravityLaunch(agent);
81325
- const cwd = await mkdtemp2(join45(tmpdir8(), "hanoman-models-"));
81626
+ const cwd = await mkdtemp2(join47(tmpdir8(), "hanoman-models-"));
81326
81627
  const env = agentEnvironment(agent, process.env);
81327
81628
  for (const key of ["HANOMAN_CLAUDE_BIN", "HANOMAN_CODEX_BIN", "HANOMAN_PODMAN_BIN", "HANOMAN_ANTIGRAVITY_BIN"]) {
81328
81629
  const value = effectiveStr(key);
@@ -81425,7 +81726,7 @@ function createModelCatalogService(deps) {
81425
81726
  async function writeModelCatalogCache(file, catalog, current = () => true) {
81426
81727
  const sanitized = parseCachedCatalog(catalog);
81427
81728
  await mkdir9(dirname18(file), { recursive: true });
81428
- const temp = `${file}.${process.pid}.${randomUUID13()}.tmp`;
81729
+ const temp = `${file}.${process.pid}.${randomUUID14()}.tmp`;
81429
81730
  try {
81430
81731
  await writeFile6(temp, JSON.stringify(sanitized), { mode: 384, flag: "wx" });
81431
81732
  if (current()) await rename5(temp, file);
@@ -81433,12 +81734,12 @@ async function writeModelCatalogCache(file, catalog, current = () => true) {
81433
81734
  await rm9(temp, { force: true });
81434
81735
  }
81435
81736
  }
81436
- var cacheFile = () => join46(resolveHome(), "model-catalog.json");
81737
+ var cacheFile = () => join48(resolveHome(), "model-catalog.json");
81437
81738
  var modelCatalogService = createModelCatalogService({
81438
81739
  probe: probeModelCatalog,
81439
81740
  key: catalogProbeKey,
81440
81741
  read: async () => {
81441
- const content = await readFile8(cacheFile(), "utf8");
81742
+ const content = await readFile9(cacheFile(), "utf8");
81442
81743
  if (Buffer.byteLength(content) > 4 * 1024 * 1024) throw new Error("oversized cache");
81443
81744
  return JSON.parse(content);
81444
81745
  },
@@ -82622,8 +82923,8 @@ init_db();
82622
82923
  init_pty();
82623
82924
  init_config5();
82624
82925
  init_inbox();
82625
- import { mkdirSync as mkdirSync19, writeFileSync as writeFileSync19 } from "node:fs";
82626
- import { join as join47 } from "node:path";
82926
+ import { mkdirSync as mkdirSync20, writeFileSync as writeFileSync20 } from "node:fs";
82927
+ import { join as join49 } from "node:path";
82627
82928
  init_human_input();
82628
82929
  var idle = /* @__PURE__ */ new Set();
82629
82930
  var sending = /* @__PURE__ */ new Set();
@@ -82634,7 +82935,7 @@ function isLeadIdle(id2) {
82634
82935
  return idle.has(id2);
82635
82936
  }
82636
82937
  var HUMAN_QUIET_MS = 12e4;
82637
- var batchDir = (projectId) => join47(resolveHome(), "lead-batches", projectId);
82938
+ var batchDir = (projectId) => join49(resolveHome(), "lead-batches", projectId);
82638
82939
  var briefFn = async () => "(situasi tak tersedia \u2014 panggil /brief)";
82639
82940
  function setBriefBuilder(fn) {
82640
82941
  briefFn = fn;
@@ -82654,9 +82955,9 @@ var prodCourierDeps = {
82654
82955
  brief: (p3) => briefFn(p3).catch(() => "(situasi tak tersedia \u2014 panggil /brief)"),
82655
82956
  write: (projectId, text2) => {
82656
82957
  const dir2 = batchDir(projectId);
82657
- mkdirSync19(dir2, { recursive: true });
82658
- const file = join47(dir2, `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.md`);
82659
- writeFileSync19(file, text2);
82958
+ mkdirSync20(dir2, { recursive: true });
82959
+ const file = join49(dir2, `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.md`);
82960
+ writeFileSync20(file, text2);
82660
82961
  return file;
82661
82962
  },
82662
82963
  send: (id2, line) => sendToPane(id2, line),
@@ -83454,7 +83755,7 @@ init_src();
83454
83755
  init_db();
83455
83756
  import { gunzipSync, gzipSync } from "node:zlib";
83456
83757
  import { mkdir as mkdir10, rename as rename6, writeFile as writeFile7 } from "node:fs/promises";
83457
- import { join as join48 } from "node:path";
83758
+ import { join as join50 } from "node:path";
83458
83759
 
83459
83760
  // src/services/device-auth.ts
83460
83761
  function bearerToken2(req) {
@@ -83712,9 +84013,9 @@ async function sync_default(app3) {
83712
84013
  const deviceId = req.device.id;
83713
84014
  const withTranscriptKey = await Promise.all(parsed.data.entries.map(async (e) => {
83714
84015
  if (!e.transcript) return e;
83715
- const dir2 = join48(resolveDataDirs().home, "remote-transcripts", deviceId);
84016
+ const dir2 = join50(resolveDataDirs().home, "remote-transcripts", deviceId);
83716
84017
  await mkdir10(dir2, { recursive: true, mode: 448 });
83717
- const final = join48(dir2, `${e.seq}.txt`);
84018
+ const final = join50(dir2, `${e.seq}.txt`);
83718
84019
  const tmp = `${final}.tmp`;
83719
84020
  await writeFile7(tmp, e.transcript, { encoding: "utf8", mode: 384 });
83720
84021
  await rename6(tmp, final);
@@ -84269,9 +84570,9 @@ init_db();
84269
84570
  init_db();
84270
84571
  init_uploads();
84271
84572
  init_src();
84272
- import { createHash as createHash20, randomBytes as randomBytes7 } from "node:crypto";
84573
+ import { createHash as createHash21, randomBytes as randomBytes7 } from "node:crypto";
84273
84574
  function hashAccessKey(key) {
84274
- return createHash20("sha256").update(key).digest("hex");
84575
+ return createHash21("sha256").update(key).digest("hex");
84275
84576
  }
84276
84577
  function generateAccessKey() {
84277
84578
  const key = "hnm_tkt_" + randomBytes7(24).toString("hex");
@@ -87117,9 +87418,9 @@ init_codex_trust();
87117
87418
  init_qa_attachment_transfer();
87118
87419
  init_uploads();
87119
87420
  init_ask_via2();
87120
- import { createHash as createHash21 } from "node:crypto";
87121
- import { execFileSync as execFileSync10 } from "node:child_process";
87122
- import { join as join50 } from "node:path";
87421
+ import { createHash as createHash22 } from "node:crypto";
87422
+ import { execFileSync as execFileSync11 } from "node:child_process";
87423
+ import { join as join52 } from "node:path";
87123
87424
  var QaSessionError = class extends Error {
87124
87425
  constructor(code, message, needsBind = false) {
87125
87426
  super(message);
@@ -87127,7 +87428,7 @@ var QaSessionError = class extends Error {
87127
87428
  this.needsBind = needsBind;
87128
87429
  }
87129
87430
  };
87130
- var qaSessionId = (findingId) => `qa-${createHash21("sha256").update(findingId).digest("hex").slice(0, 24)}`;
87431
+ var qaSessionId = (findingId) => `qa-${createHash22("sha256").update(findingId).digest("hex").slice(0, 24)}`;
87131
87432
  async function startQaFindingSession(d, fid) {
87132
87433
  const f = d.findings.find((x) => x.id === fid);
87133
87434
  if (!f) throw new QaSessionError(404, "temuan tidak ditemukan");
@@ -87146,14 +87447,14 @@ async function startQaFindingSession(d, fid) {
87146
87447
  const saved = await saveSessionUpload(id2, bytes, a.mimeType);
87147
87448
  files2.push(`${JSON.stringify(a.filename)} (${a.mimeType}): ${saved.path}`);
87148
87449
  }
87149
- const wt = join50(repoDir, ".worktrees", id2);
87450
+ const wt = join52(repoDir, ".worktrees", id2);
87150
87451
  const branch = `qa/${id2.slice(3)}`;
87151
87452
  const reused = realGit.worktreeAlive(wt);
87152
87453
  try {
87153
87454
  if (!reused) {
87154
87455
  const base3 = realGit.revParse(repoDir, branch) ? branch : "HEAD";
87155
87456
  realGit.addWorktree(repoDir, wt, base3);
87156
- execFileSync10("git", ["checkout", "-B", branch], { cwd: wt, encoding: "utf8" });
87457
+ execFileSync11("git", ["checkout", "-B", branch], { cwd: wt, encoding: "utf8" });
87157
87458
  }
87158
87459
  } catch (e) {
87159
87460
  throw new QaSessionError(422, `Gagal membuat worktree QA: ${e.message}`);
@@ -88689,9 +88990,9 @@ ${baru}`;
88689
88990
  // src/services/portal-chat/workspace.ts
88690
88991
  init_src();
88691
88992
  init_db();
88692
- import { mkdirSync as mkdirSync20, mkdtempSync as mkdtempSync5, rmSync as rmSync13, writeFileSync as writeFileSync20 } from "node:fs";
88993
+ import { mkdirSync as mkdirSync21, mkdtempSync as mkdtempSync5, rmSync as rmSync14, writeFileSync as writeFileSync21 } from "node:fs";
88693
88994
  import { tmpdir as tmpdir9 } from "node:os";
88694
- import { join as join51 } from "node:path";
88995
+ import { join as join53 } from "node:path";
88695
88996
  var STAGE_LABEL = {
88696
88997
  brainstorming: "Dirumuskan",
88697
88998
  objective: "Dirumuskan",
@@ -88734,7 +89035,7 @@ function renderTicketDoc(tickets2) {
88734
89035
  ${baris.join("\n")}`;
88735
89036
  }
88736
89037
  async function buildChatWorkspace(projectId) {
88737
- const dir2 = mkdtempSync5(join51(tmpdir9(), "hanoman-portal-chat-"));
89038
+ const dir2 = mkdtempSync5(join53(tmpdir9(), "hanoman-portal-chat-"));
88738
89039
  try {
88739
89040
  const project = await prisma.project.findUnique({
88740
89041
  where: { id: projectId },
@@ -88749,7 +89050,7 @@ async function buildChatWorkspace(projectId) {
88749
89050
  });
88750
89051
  const files2 = [];
88751
89052
  const tulis = (rel, isi) => {
88752
- writeFileSync20(join51(dir2, rel), isi, { mode: 384 });
89053
+ writeFileSync21(join53(dir2, rel), isi, { mode: 384 });
88753
89054
  files2.push(rel);
88754
89055
  };
88755
89056
  tulis("project.md", renderProjectDoc(project));
@@ -88757,16 +89058,16 @@ async function buildChatWorkspace(projectId) {
88757
89058
  tulis("laporan.md", renderTicketDoc(tickets2.map((t) => toPortalTicket(t, null))));
88758
89059
  const prds = await listPrds(projectId);
88759
89060
  if (prds.length) {
88760
- mkdirSync20(join51(dir2, "dokumen"), { mode: 448 });
89061
+ mkdirSync21(join53(dir2, "dokumen"), { mode: 448 });
88761
89062
  for (const prd of prds) {
88762
89063
  const isi = await readPrd(projectId, prd.path);
88763
89064
  if (!isi) continue;
88764
- tulis(join51("dokumen", `${prd.slug.replaceAll("/", "-")}.md`), isi);
89065
+ tulis(join53("dokumen", `${prd.slug.replaceAll("/", "-")}.md`), isi);
88765
89066
  }
88766
89067
  }
88767
- return { dir: dir2, files: files2, cleanup: () => rmSync13(dir2, { recursive: true, force: true }) };
89068
+ return { dir: dir2, files: files2, cleanup: () => rmSync14(dir2, { recursive: true, force: true }) };
88768
89069
  } catch (error) {
88769
- rmSync13(dir2, { recursive: true, force: true });
89070
+ rmSync14(dir2, { recursive: true, force: true });
88770
89071
  throw error;
88771
89072
  }
88772
89073
  }
@@ -89286,20 +89587,20 @@ init_src();
89286
89587
  init_db();
89287
89588
 
89288
89589
  // src/services/bootstrap.ts
89289
- import { createHash as createHash22, randomBytes as randomBytes11, timingSafeEqual as timingSafeEqual5 } from "node:crypto";
89290
- import { chmod as chmod2, lstat as lstat3, mkdir as mkdir11, readFile as readFile9, unlink as unlink5, writeFile as writeFile8 } from "node:fs/promises";
89291
- import { join as join52 } from "node:path";
89590
+ import { createHash as createHash23, randomBytes as randomBytes11, timingSafeEqual as timingSafeEqual5 } from "node:crypto";
89591
+ import { chmod as chmod2, lstat as lstat3, mkdir as mkdir11, readFile as readFile10, unlink as unlink5, writeFile as writeFile8 } from "node:fs/promises";
89592
+ import { join as join54 } from "node:path";
89292
89593
  var TTL_MS7 = 15 * 6e4;
89293
89594
  var SETUP_TOKEN_FILE = "setup.token";
89294
89595
  var BootstrapError = class extends Error {
89295
89596
  code = "BOOTSTRAP_PROOF";
89296
89597
  };
89297
- var tokenPath = (home3) => join52(home3, SETUP_TOKEN_FILE);
89598
+ var tokenPath = (home3) => join54(home3, SETUP_TOKEN_FILE);
89298
89599
  async function readStored(home3) {
89299
89600
  try {
89300
89601
  const info = await lstat3(tokenPath(home3));
89301
89602
  if (info.isSymbolicLink() || !info.isFile()) throw new BootstrapError("invalid setup proof");
89302
- const [token, expires] = (await readFile9(tokenPath(home3), "utf8")).trim().split("\n");
89603
+ const [token, expires] = (await readFile10(tokenPath(home3), "utf8")).trim().split("\n");
89303
89604
  const expiresAt = Date.parse(expires ?? "");
89304
89605
  if (!token || !Number.isFinite(expiresAt)) throw new BootstrapError("invalid setup proof");
89305
89606
  return { token, expiresAt };
@@ -89339,8 +89640,8 @@ ${new Date(expiresAt).toISOString()}
89339
89640
  }
89340
89641
  async function verifySetupToken(candidate, home3, now = Date.now()) {
89341
89642
  const stored = await readStored(home3);
89342
- const got = createHash22("sha256").update(candidate).digest();
89343
- const want = createHash22("sha256").update(stored.token).digest();
89643
+ const got = createHash23("sha256").update(candidate).digest();
89644
+ const want = createHash23("sha256").update(stored.token).digest();
89344
89645
  if (stored.expiresAt <= now || !timingSafeEqual5(got, want)) throw new BootstrapError("invalid setup proof");
89345
89646
  }
89346
89647
  async function consumeSetupToken(home3) {
@@ -89759,7 +90060,7 @@ init_event_log();
89759
90060
 
89760
90061
  // src/services/remote-access-request.ts
89761
90062
  init_src();
89762
- import { randomUUID as randomUUID14 } from "node:crypto";
90063
+ import { randomUUID as randomUUID15 } from "node:crypto";
89763
90064
  var REQUEST_TIER_LABEL = { lihat: "Lihat", kerja: "Kerja", tulis: "Tulis" };
89764
90065
  var entries3 = [];
89765
90066
  var accepted = [];
@@ -89783,7 +90084,7 @@ function createAccessRequest(input, now = Date.now()) {
89783
90084
  return { ok: false, reason: "rate-limited", retryAfterMs: accepted[0] + ACCESS_REQUEST_WINDOW_MS - now };
89784
90085
  accepted.push(now);
89785
90086
  const entry2 = {
89786
- id: randomUUID14(),
90087
+ id: randomUUID15(),
89787
90088
  tier: input.tier,
89788
90089
  state: "pending",
89789
90090
  requestedAt: now,
@@ -90131,8 +90432,8 @@ init_db();
90131
90432
  init_event_log();
90132
90433
  init_transcript_store();
90133
90434
  init_src2();
90134
- import { readFile as readFile10 } from "node:fs/promises";
90135
- import { join as join53 } from "node:path";
90435
+ import { readFile as readFile11 } from "node:fs/promises";
90436
+ import { join as join55 } from "node:path";
90136
90437
  function decodeCursor(c) {
90137
90438
  if (!c) return null;
90138
90439
  try {
@@ -90170,9 +90471,9 @@ async function readRemoteTranscript(id2) {
90170
90471
  const row = await prisma.logEntry.findUnique({ where: { id: id2 } });
90171
90472
  if (!row || row.lane !== "transcript" || !row.transcriptKey) return null;
90172
90473
  if (row.deviceId === "local") return readTranscript(row.transcriptKey);
90173
- const path = join53(resolveDataDirs().home, "remote-transcripts", row.transcriptKey);
90474
+ const path = join55(resolveDataDirs().home, "remote-transcripts", row.transcriptKey);
90174
90475
  try {
90175
- return await readFile10(path, "utf8");
90476
+ return await readFile11(path, "utf8");
90176
90477
  } catch {
90177
90478
  return null;
90178
90479
  }
@@ -90367,7 +90668,7 @@ var SETUP_PUBLIC = /* @__PURE__ */ new Set([
90367
90668
  "POST /api/setup"
90368
90669
  ]);
90369
90670
  function buildApp({ requireAuth = true, agentDocFile, env = process.env } = {}) {
90370
- const docFile = agentDocFile !== void 0 ? agentDocFile : pickGuideFile(dirname19(fileURLToPath5(import.meta.url)), env, existsSync23);
90671
+ const docFile = agentDocFile !== void 0 ? agentDocFile : pickGuideFile(dirname19(fileURLToPath6(import.meta.url)), env, existsSync25);
90371
90672
  const ingress = loadIngressPolicy(env);
90372
90673
  const app3 = Fastify({ logger: false, trustProxy: trustProxyFromEnv(env) });
90373
90674
  app3.setErrorHandler((error, _req, reply) => {
@@ -90516,7 +90817,7 @@ function buildApp({ requireAuth = true, agentDocFile, env = process.env } = {})
90516
90817
  await api.register(qaBacklog);
90517
90818
  }, { prefix: "/api" });
90518
90819
  if (shouldServeWeb(env)) {
90519
- const dist = pickWebDir(dirname19(fileURLToPath5(import.meta.url)), env, existsSync23);
90820
+ const dist = pickWebDir(dirname19(fileURLToPath6(import.meta.url)), env, existsSync25);
90520
90821
  if (dist) {
90521
90822
  app3.register(fastifyStatic, { root: dist });
90522
90823
  app3.setNotFoundHandler((req, reply) => req.url.startsWith("/api") ? reply.code(404).send({ error: "not found" }) : req.url.startsWith("/assets/") ? reply.code(404).type("text/plain").send("not found") : reply.sendFile("index.html"));
@@ -90961,6 +91262,7 @@ async function resumeReconciledSessions(cutoff, deps = prodDeps4) {
90961
91262
  init_event_log();
90962
91263
  init_settings3();
90963
91264
  init_custom_agents2();
91265
+ init_bundled_skills();
90964
91266
  init_pty();
90965
91267
  init_bootstrap();
90966
91268
 
@@ -90971,7 +91273,7 @@ init_tap();
90971
91273
  init_src();
90972
91274
  init_db();
90973
91275
  init_actor();
90974
- import { randomUUID as randomUUID15 } from "node:crypto";
91276
+ import { randomUUID as randomUUID16 } from "node:crypto";
90975
91277
  function skipped(def2, row) {
90976
91278
  if (!def2.skipWhen || !row) return false;
90977
91279
  return row[def2.skipWhen.field] === def2.skipWhen.equals;
@@ -91037,7 +91339,7 @@ async function emitWebhook(i) {
91037
91339
  const targets = matchingEndpoints(type, projectId);
91038
91340
  if (!targets.length) return;
91039
91341
  const name2 = projectId ? (await prisma.project.findUnique({ where: { id: projectId }, select: { name: true } }))?.name ?? null : null;
91040
- const env = buildEnvelope(i, name2, (/* @__PURE__ */ new Date()).toISOString(), `evt_${randomUUID15().replace(/-/g, "")}`);
91342
+ const env = buildEnvelope(i, name2, (/* @__PURE__ */ new Date()).toISOString(), `evt_${randomUUID16().replace(/-/g, "")}`);
91041
91343
  if (!env) return;
91042
91344
  await enqueueEnvelope(env, targets);
91043
91345
  } catch (e) {
@@ -91452,9 +91754,9 @@ async function pruneLogs(now, retention, opts2 = {}) {
91452
91754
  }
91453
91755
  async function reconcileRemoteTranscripts() {
91454
91756
  const { readdir: readdir10, unlink: unlink6, stat: stat8 } = await import("node:fs/promises");
91455
- const { join: join54 } = await import("node:path");
91757
+ const { join: join56 } = await import("node:path");
91456
91758
  const { resolveDataDirs: resolveDataDirs2 } = await Promise.resolve().then(() => (init_src2(), src_exports));
91457
- const root = join54(resolveDataDirs2().home, "remote-transcripts");
91759
+ const root = join56(resolveDataDirs2().home, "remote-transcripts");
91458
91760
  let deviceDirs;
91459
91761
  try {
91460
91762
  deviceDirs = await readdir10(root);
@@ -91468,16 +91770,16 @@ async function reconcileRemoteTranscripts() {
91468
91770
  for (const dev of deviceDirs) {
91469
91771
  let files2;
91470
91772
  try {
91471
- files2 = await readdir10(join54(root, dev));
91773
+ files2 = await readdir10(join56(root, dev));
91472
91774
  } catch {
91473
91775
  continue;
91474
91776
  }
91475
91777
  for (const f of files2) {
91476
91778
  const key = `${dev}/${f}`;
91477
91779
  if (referenced.has(key)) continue;
91478
- const st2 = await stat8(join54(root, dev, f)).catch(() => null);
91780
+ const st2 = await stat8(join56(root, dev, f)).catch(() => null);
91479
91781
  if (!st2 || Date.now() - st2.mtimeMs < gracePeriodMs) continue;
91480
- await unlink6(join54(root, dev, f)).catch(() => {
91782
+ await unlink6(join56(root, dev, f)).catch(() => {
91481
91783
  });
91482
91784
  orphans++;
91483
91785
  }
@@ -91712,6 +92014,9 @@ bootstrapReady.then(async () => {
91712
92014
  console.error("config runtime gagal dimuat \u2014 memakai env/default:", e);
91713
92015
  }
91714
92016
  await installCustomAgents();
92017
+ const skills2 = seedBundledSkills(home2);
92018
+ if (skills2.seeded.length || skills2.updated.length)
92019
+ console.log(`skill bundel: di-seed ${skills2.seeded.join(", ") || "-"} \xB7 diperbarui ${skills2.updated.join(", ") || "-"}`);
91715
92020
  return app2.listen({ port, host });
91716
92021
  }).then(async () => {
91717
92022
  console.log(`hanoman api ${host}:${port}`);