hanoman 0.9.29 → 0.9.30

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 (24) hide show
  1. package/dist/build-info.json +3 -3
  2. package/dist/cli.js +23 -0
  3. package/dist/server.js +266 -68
  4. package/package.json +1 -1
  5. package/web/assets/{DalangHanomanScreen-BMrikKnt.js → DalangHanomanScreen-Fd7_DcSP.js} +1 -1
  6. package/web/assets/{DocPreviewModal-5j31WUwI.js → DocPreviewModal-SMRN2K98.js} +1 -1
  7. package/web/assets/{DocsWorkspace-GkTrniF3.js → DocsWorkspace-pxj0N6QW.js} +1 -1
  8. package/web/assets/{IdeScreen-CAQfv1_e.js → IdeScreen-Cr9Vqdrc.js} +1 -1
  9. package/web/assets/{LeadScreen-Da5-1FLe.js → LeadScreen-CQXpJy0y.js} +1 -1
  10. package/web/assets/{ReviewScreen-B8KHzQpd.js → ReviewScreen-B7NNEjxk.js} +1 -1
  11. package/web/assets/SchedulerScreen-D3rPdS-F.js +1 -0
  12. package/web/assets/{SettingsScreen-BxQ3CH2D.js → SettingsScreen-Bx13PWKQ.js} +1 -1
  13. package/web/assets/{TeamScreen-P6vz88Pp.js → TeamScreen-BBAsYLpT.js} +1 -1
  14. package/web/assets/{TerminalScreen-B_cagBZd.js → TerminalScreen-DpVp4h1n.js} +1 -1
  15. package/web/assets/{TriageScreen-IYkxyhdm.js → TriageScreen-wAPnttRe.js} +1 -1
  16. package/web/assets/{VpsScreen-DFGT5S8Y.js → VpsScreen-DeQVx9Jy.js} +1 -1
  17. package/web/assets/{fields-CmVwp6QK.js → fields-qnBoIMR_.js} +1 -1
  18. package/web/assets/{index-C-iSWHn3.js → index-BQw6xQI4.js} +207 -207
  19. package/web/assets/{index-0T3s-I82.css → index-C3KwOJxI.css} +1 -1
  20. package/web/assets/{live-lSo-u7M6.js → live-CONO0GTu.js} +1 -1
  21. package/web/assets/{sched-lead-shared-BTz6aILx.js → sched-lead-shared-CDR0pOqs.js} +1 -1
  22. package/web/assets/{settings-rebase-CPawFVpp.js → settings-rebase-CpaFw0Tm.js} +1 -1
  23. package/web/index.html +2 -2
  24. package/web/assets/SchedulerScreen-CBd4O5kw.js +0 -1
package/dist/server.js CHANGED
@@ -5575,7 +5575,7 @@ function resolvePhasePlan(input) {
5575
5575
  if (!input.nativeAgents || cfg?.enabled === false) return null;
5576
5576
  const cells = cfg?.[input.runtime] ?? {};
5577
5577
  const coerce3 = input.runtime === "codex" ? coerceCodexEffort : coerceClaudeEffort;
5578
- const resolve25 = (key, fallback2) => {
5578
+ const resolve25 = (key, fallback3) => {
5579
5579
  const cell4 = cells[key];
5580
5580
  const override = input.phaseOverrides?.[key];
5581
5581
  if (input.runtime === "antigravity") {
@@ -5587,9 +5587,9 @@ function resolvePhasePlan(input) {
5587
5587
  }
5588
5588
  return { model: model2, effort: null };
5589
5589
  }
5590
- const picked = override?.model ?? cell4?.model ?? fallback2.model;
5590
+ const picked = override?.model ?? cell4?.model ?? fallback3.model;
5591
5591
  const model = input.runtime === "claude" && picked === CLAUDE_DEFAULT_ALIAS ? CLAUDE_SUBAGENT_INHERIT : picked;
5592
- return { model, effort: coerce3(model, override?.effort ?? cell4?.effort ?? fallback2.effort ?? "medium") };
5592
+ return { model, effort: coerce3(model, override?.effort ?? cell4?.effort ?? fallback3.effort ?? "medium") };
5593
5593
  };
5594
5594
  const reviewer = REVIEWER_FLOWS.has(input.flow) ? {
5595
5595
  agentName: phaseAgentName(REVIEWER_CELL),
@@ -9122,6 +9122,17 @@ var init_config_registry = __esm({
9122
9122
  // runtime
9123
9123
  { key: "HANOMAN_EVENTS_TICK_MS", group: "runtime", label: "Interval events (ms)", kind: "int", apply: "live", category: "knob", example: "1000", default: "1000", min: 100 },
9124
9124
  { key: "HANOMAN_UPDATE_FETCH", group: "runtime", label: "Deteksi update saat boot", kind: "bool", apply: "live", category: "knob", default: "1" },
9125
+ // ADR-0224 · limit Antigravity membaca token agy di Keychain dan memanggil Cloud Code API Google.
9126
+ {
9127
+ key: "HANOMAN_ANTIGRAVITY_LIMITS_FETCH",
9128
+ group: "runtime",
9129
+ label: "Baca limit Antigravity",
9130
+ kind: "bool",
9131
+ apply: "live",
9132
+ category: "knob",
9133
+ default: "1",
9134
+ help: "Membaca token agy di Keychain macOS lalu memanggil API kuota Google (endpoint internal, tak terdokumentasi). Matikan bila tak ingin hanoman menyentuh kredensial agy."
9135
+ },
9125
9136
  // SPEC-398 · ADR-0087 · deteksi update kini membandingkan semver dengan registry npm, bukan
9126
9137
  // menghitung commit di repo git (HANOMAN_REPO_ROOT dicabut bersama jalur git itu).
9127
9138
  {
@@ -14235,6 +14246,18 @@ var init_system = __esm({
14235
14246
  build: () => ({ method: "GET", path: "/limits/codex" }),
14236
14247
  shape: (raw) => raw
14237
14248
  },
14249
+ {
14250
+ name: "hanoman_limits_antigravity",
14251
+ title: "Sisa kuota Antigravity",
14252
+ description: "Sisa kuota Antigravity CLI (agy) per grup model (Gemini; Claude/GPT) untuk jendela mingguan dan 5 jam, berikut kapan resetnya. Dibaca live dari Cloud Code API Google memakai token agy yang sedang hidup dan di-cache 60 detik; token yang kedaluwarsa (agy tak berjalan) memberi `stale` atau `unavailable`, bukan angka karangan. Endpoint terpisah dari hanoman_limits karena sumbernya beda. Tak menuntut capability apa pun.",
14253
+ inputSchema: obj({ properties: {} }),
14254
+ mode: "read",
14255
+ capability: null,
14256
+ samplePath: "/limits/antigravity",
14257
+ sampleMethod: "GET",
14258
+ build: () => ({ method: "GET", path: "/limits/antigravity" }),
14259
+ shape: (raw) => raw
14260
+ },
14238
14261
  {
14239
14262
  name: "hanoman_overview_get",
14240
14263
  title: "Ringkasan kinerja hari ini (Overview)",
@@ -63404,7 +63427,7 @@ function parseCodexVersion(out4) {
63404
63427
  return m ? `${m[1]}.${m[2]}.${m[3]}` : null;
63405
63428
  }
63406
63429
  async function getCodexVersion(now = Date.now()) {
63407
- if (cache3 && now - cache3.at < TTL_MS4) return cache3.version;
63430
+ if (cache3 && now - cache3.at < TTL_MS5) return cache3.version;
63408
63431
  const version = await probeCodexVersion();
63409
63432
  cache3 = { at: now, version };
63410
63433
  return version;
@@ -63420,7 +63443,7 @@ async function codexVersionInfo() {
63420
63443
  function _resetCodexVersionCache() {
63421
63444
  cache3 = null;
63422
63445
  }
63423
- var run3, CODEX_MIN_CLIENT, TTL_MS4, cache3, codexBin2, runVersion;
63446
+ var run3, CODEX_MIN_CLIENT, TTL_MS5, cache3, codexBin2, runVersion;
63424
63447
  var init_codex_version = __esm({
63425
63448
  "src/services/codex-version.ts"() {
63426
63449
  "use strict";
@@ -63429,7 +63452,7 @@ var init_codex_version = __esm({
63429
63452
  init_config2();
63430
63453
  run3 = promisify16(execFile17);
63431
63454
  CODEX_MIN_CLIENT = "0.144.0";
63432
- TTL_MS4 = 5 * 6e4;
63455
+ TTL_MS5 = 5 * 6e4;
63433
63456
  cache3 = null;
63434
63457
  codexBin2 = () => effectiveStr("HANOMAN_CODEX_BIN") ?? "codex";
63435
63458
  runVersion = async (bin, args) => {
@@ -63440,17 +63463,17 @@ var init_codex_version = __esm({
63440
63463
  });
63441
63464
 
63442
63465
  // src/services/agent-tool-catalog.ts
63443
- import { readFileSync as readFileSync25 } from "node:fs";
63444
- import { homedir as homedir12 } from "node:os";
63445
- import { join as join47 } from "node:path";
63466
+ import { readFileSync as readFileSync26 } from "node:fs";
63467
+ import { homedir as homedir13 } from "node:os";
63468
+ import { join as join48 } from "node:path";
63446
63469
  function mcpServerNames(repoDir) {
63447
63470
  const names = [];
63448
- const claudeJson = readJson2(join47(home(), ".claude.json"));
63471
+ const claudeJson = readJson2(join48(home(), ".claude.json"));
63449
63472
  names.push(...serversOf(claudeJson));
63450
63473
  if (repoDir) {
63451
63474
  const projects = claudeJson?.projects;
63452
63475
  if (projects && typeof projects === "object") names.push(...serversOf(projects[repoDir]));
63453
- names.push(...serversOf(readJson2(join47(repoDir, ".mcp.json"))));
63476
+ names.push(...serversOf(readJson2(join48(repoDir, ".mcp.json"))));
63454
63477
  }
63455
63478
  names.push(...codexServers());
63456
63479
  return [...new Set(names.filter(Boolean))].sort((a, b) => a.localeCompare(b));
@@ -63463,10 +63486,10 @@ var init_agent_tool_catalog = __esm({
63463
63486
  "src/services/agent-tool-catalog.ts"() {
63464
63487
  "use strict";
63465
63488
  init_src();
63466
- home = () => process.env.HOME || homedir12();
63489
+ home = () => process.env.HOME || homedir13();
63467
63490
  readJson2 = (path) => {
63468
63491
  try {
63469
- return JSON.parse(readFileSync25(path, "utf8"));
63492
+ return JSON.parse(readFileSync26(path, "utf8"));
63470
63493
  } catch {
63471
63494
  return null;
63472
63495
  }
@@ -63479,7 +63502,7 @@ var init_agent_tool_catalog = __esm({
63479
63502
  codexServers = () => {
63480
63503
  let text2;
63481
63504
  try {
63482
- text2 = readFileSync25(join47(home(), ".codex", "config.toml"), "utf8");
63505
+ text2 = readFileSync26(join48(home(), ".codex", "config.toml"), "utf8");
63483
63506
  } catch {
63484
63507
  return [];
63485
63508
  }
@@ -76879,6 +76902,10 @@ async function fail(req, deps, reason, flowId = null, step = null) {
76879
76902
  // src/services/lead/detect.ts
76880
76903
  var answers = /* @__PURE__ */ new Map();
76881
76904
  var capped = /* @__PURE__ */ new Set();
76905
+ function askSignature(a) {
76906
+ const raw = a.questions.length ? a.questions.map((q2) => q2.question).join("\n") : a.message;
76907
+ return raw.toLowerCase().replace(/\s+/g, " ").trim().slice(0, 2e3);
76908
+ }
76882
76909
  var failures = /* @__PURE__ */ new Map();
76883
76910
  var failCapped = /* @__PURE__ */ new Set();
76884
76911
  var failedAt = /* @__PURE__ */ new Map();
@@ -76950,7 +76977,9 @@ async function admitAsk(s2, deps = prodDetectDeps) {
76950
76977
  if (!(await deps.optIn()).includes(s2.projectId)) return no("project tak opt-in lead");
76951
76978
  if (!leadActive(cfg, s2.projectId)) return no("lead dijeda untuk project ini");
76952
76979
  if (deps.exited(s2.id)) return no("pane mati");
76953
- if ((answers.get(s2.id) ?? 0) >= cfg.maxAutoAnswers) {
76980
+ const run6 = answers.get(s2.id);
76981
+ const looping = !!run6 && (s2.sig === void 0 || s2.sig === run6.sig);
76982
+ if (run6 && looping && run6.n >= cfg.maxAutoAnswers) {
76954
76983
  if (!capped.has(s2.id)) {
76955
76984
  capped.add(s2.id);
76956
76985
  const row = await recordDecision({
@@ -76959,9 +76988,9 @@ async function admitAsk(s2, deps = prodDetectDeps) {
76959
76988
  sessionId: s2.id,
76960
76989
  gate: "detected",
76961
76990
  kind: "quality",
76962
- question: `Sesi ${s2.id} sudah dijawab otomatis ${cfg.maxAutoAnswers}\xD7 berturut-turut.`,
76963
- answer: "Berhenti menjawab sesi ini; serahkan ke operator.",
76964
- reason: "Batas jawaban otomatis per sesi tercapai \u2014 pengulangan menandakan lead tak benar-benar membuka jalan buntunya (AC-11).",
76991
+ question: `Sesi ${s2.id} sudah dijawab otomatis ${cfg.maxAutoAnswers}\xD7 berturut-turut untuk pertanyaan yang sama.`,
76992
+ answer: "Berhenti menjawab pertanyaan ini; serahkan ke operator.",
76993
+ reason: "Pertanyaan yang sama terus kembali sesudah dijawab \u2014 pengulangan menandakan lead tak benar-benar membuka jalan buntunya (AC-11, ADR-0223).",
76965
76994
  refs: [],
76966
76995
  confidence: "tinggi",
76967
76996
  action: "none",
@@ -77017,7 +77046,13 @@ async function answerAsk(ask, s2, deps = prodDetectDeps) {
77017
77046
  const out4 = { at: chain.at, flowId: chain.flowId, step: chain.step };
77018
77047
  if (chain.acted && chain.done) {
77019
77048
  if (s2.decisionFile) deps.clearMarker(s2.decisionFile);
77020
- answers.set(ask.sessionId, (answers.get(ask.sessionId) ?? 0) + 1);
77049
+ const sig = askSignature(ask);
77050
+ const prev = answers.get(ask.sessionId);
77051
+ if (prev?.sig === sig) prev.n++;
77052
+ else {
77053
+ answers.set(ask.sessionId, { sig, n: 1 });
77054
+ capped.delete(ask.sessionId);
77055
+ }
77021
77056
  failures.delete(ask.sessionId);
77022
77057
  return { answered: true, reason: "", ...out4 };
77023
77058
  }
@@ -77025,7 +77060,7 @@ async function answerAsk(ask, s2, deps = prodDetectDeps) {
77025
77060
  failures.set(ask.sessionId, (failures.get(ask.sessionId) ?? 0) + 1);
77026
77061
  failedAt.set(ask.sessionId, deps.now());
77027
77062
  }
77028
- return { answered: false, reason: chain.reason, ...out4 };
77063
+ return { answered: false, reason: chain.reason, ...out4, ...chain.mismatch ? { mismatch: true } : {} };
77029
77064
  }
77030
77065
  async function runChain(ask, s2, deps) {
77031
77066
  const sid = ask.sessionId;
@@ -77043,6 +77078,7 @@ async function runChain(ask, s2, deps) {
77043
77078
  return { ...r, flowId, step };
77044
77079
  };
77045
77080
  const stop = (reason, failed = false) => ({ acted, done: false, failed, reason, at, flowId, step });
77081
+ const asked = ask.source === "ask-tool";
77046
77082
  if (ask.source === "turn-end") {
77047
77083
  const read2 = readCodexTurn(ask.message);
77048
77084
  if (!read2.asking) return stop(read2.reason);
@@ -77079,6 +77115,13 @@ async function runChain(ask, s2, deps) {
77079
77115
  // `tunggal` di tiap langkah — `chainSteps` kosong tepat di tempat yang paling membutuhkannya.
77080
77116
  chain: true
77081
77117
  };
77118
+ if (asked && q2) {
77119
+ const seen2 = await waitDialogFor(sid, q2, ask.questions, deps);
77120
+ if (seen2 === "none") return stop("dialog tak muncul di pane \u2014 tak ada yang diketik", true);
77121
+ if (seen2 === "other") {
77122
+ return i === 0 ? { ...stop("dialog di pane adalah pertanyaan lain \u2014 tak ada yang diketik"), mismatch: true } : stop("dialog di pane bukan pertanyaan berikutnya rantai ini \u2014 tak ada yang diketik");
77123
+ }
77124
+ }
77082
77125
  let row;
77083
77126
  try {
77084
77127
  row = await deps.decide({ ...req, flowId }, deps.decideDeps);
@@ -77102,9 +77145,11 @@ async function runChain(ask, s2, deps) {
77102
77145
  if (!row) return stop("lead tak menghasilkan keputusan yang berlaku");
77103
77146
  if (row.status !== "berlaku") return stop("lead tak menghasilkan keputusan yang berlaku", true);
77104
77147
  let before2 = "";
77105
- if (ask.source === "ask-tool") {
77106
- const text2 = await waitDialog(sid, deps);
77107
- if (text2 === null) return stop("dialog tak muncul di pane \u2014 tak ada yang diketik", true);
77148
+ if (asked && q2) {
77149
+ const text2 = deps.pane(sid);
77150
+ const screen = readDialogScreen(text2);
77151
+ if (!screen || !dialogFits(screen, q2, ask.questions))
77152
+ return stop("layar dialog berubah selagi lead memutuskan \u2014 tak ada yang diketik");
77108
77153
  before2 = dialogKey(text2);
77109
77154
  }
77110
77155
  if (isTakenOver(sid)) return stop("diambil alih operator");
@@ -77136,13 +77181,29 @@ async function afterLastAnswer(id2, deps) {
77136
77181
  }
77137
77182
  return "pending";
77138
77183
  }
77139
- async function waitDialog(id2, deps) {
77184
+ async function waitDialogFor(id2, q2, all, deps) {
77185
+ let sawDialog = false;
77140
77186
  for (let i = 0; i < DIALOG_WAIT_TRIES; i++) {
77141
- const text2 = deps.pane(id2);
77142
- if (readDialogScreen(text2)) return text2;
77187
+ const screen = readDialogScreen(deps.pane(id2));
77188
+ if (screen?.kind === "question") {
77189
+ if (dialogFits(screen, q2, all)) return "fit";
77190
+ sawDialog = true;
77191
+ }
77143
77192
  await deps.sleep(CHAIN_POLL_MS);
77144
77193
  }
77145
- return null;
77194
+ return sawDialog ? "other" : "none";
77195
+ }
77196
+ var bare = (s2) => s2.toLowerCase().replace(/[^\p{L}\p{N}]+/gu, "");
77197
+ function dialogFits(screen, q2, all) {
77198
+ if (screen.kind !== "question") return false;
77199
+ if (screen.tabs.length && screen.tabs.length !== all.length) return false;
77200
+ const title = bare(screen.title);
77201
+ const text2 = bare(q2.question);
77202
+ if (title.length >= 6 && text2 && !text2.startsWith(title) && !title.startsWith(text2)) return false;
77203
+ const labels = q2.options.map((o) => bare(o.label)).filter(Boolean);
77204
+ const shown = screen.options.map(bare).filter(Boolean);
77205
+ if (labels.length && shown.length && !shown.some((s2) => labels.some((l) => l.startsWith(s2) || s2.startsWith(l)))) return false;
77206
+ return true;
77146
77207
  }
77147
77208
  async function waitScreenChange(id2, before2, deps) {
77148
77209
  for (let i = 0; i < CHAIN_POLL_TRIES; i++) {
@@ -77157,6 +77218,8 @@ init_pty();
77157
77218
  var ASK_BUCKET = { capacity: 5, refillMs: 1e4 };
77158
77219
  var GLOBAL_BUCKET = { capacity: 20, refillMs: 500 };
77159
77220
  var SEEN_MAX = 512;
77221
+ var ASK_QUEUE_MAX = 8;
77222
+ var ASK_MISMATCH_MAX = 2;
77160
77223
  var entries = /* @__PURE__ */ new Map();
77161
77224
  var seen = [];
77162
77225
  var seenSet = /* @__PURE__ */ new Set();
@@ -77236,7 +77299,8 @@ async function intakeAsk(input, deps = prodAskDeps) {
77236
77299
  const verdict = await deps.admit({
77237
77300
  id: input.sessionId,
77238
77301
  specId: input.specId,
77239
- projectId: input.projectId
77302
+ projectId: input.projectId,
77303
+ sig: askSignature(input.event)
77240
77304
  });
77241
77305
  if (!verdict.ok) return { status: "rejected", reason: verdict.reason };
77242
77306
  seenSet.add(key);
@@ -77252,12 +77316,16 @@ async function intakeAsk(input, deps = prodAskDeps) {
77252
77316
  };
77253
77317
  const ask = toAsk({ sessionId: input.sessionId, agent: input.agent, event: input.event, now });
77254
77318
  const cur = entries.get(input.sessionId);
77255
- if (cur?.running) {
77256
- cur.pending = { ask, ctx };
77319
+ if (cur?.busy) {
77320
+ cur.queue.push({ ask, ctx, skips: 0 });
77321
+ if (cur.queue.length > ASK_QUEUE_MAX) {
77322
+ const dropped = cur.queue.shift();
77323
+ console.log(`lead ask ${input.sessionId}: antrean penuh \u2014 tanya ${dropped.ask.askId} dibuang`);
77324
+ }
77257
77325
  return { status: "accepted" };
77258
77326
  }
77259
77327
  clearTakeover(input.sessionId);
77260
- entries.set(input.sessionId, { ask, ctx, pending: null, running: false });
77328
+ entries.set(input.sessionId, { ask, ctx, skips: 0, queue: [], busy: true, running: false });
77261
77329
  void prune(deps);
77262
77330
  void run2(input.sessionId, deps);
77263
77331
  return { status: "accepted" };
@@ -77311,16 +77379,19 @@ async function run2(sessionId2, deps) {
77311
77379
  step: r.step,
77312
77380
  state: r.answered ? "answered" : "failed"
77313
77381
  };
77314
- if (!e.pending) break;
77315
- const next = e.pending;
77316
- e.pending = null;
77382
+ if (r.mismatch && e.queue.length && e.skips < ASK_MISMATCH_MAX)
77383
+ e.queue.push({ ask: { ...e.ask, state: "queued" }, ctx: e.ctx, skips: e.skips + 1 });
77384
+ const next = e.queue.shift();
77385
+ if (!next) break;
77317
77386
  e.ask = next.ask;
77318
77387
  e.ctx = next.ctx;
77388
+ e.skips = next.skips;
77319
77389
  }
77320
77390
  } finally {
77321
77391
  clearQueued(sessionId2);
77322
77392
  clearDeciding(sessionId2);
77323
77393
  e.running = false;
77394
+ e.busy = false;
77324
77395
  inFlight3--;
77325
77396
  const nextId = waiting.shift();
77326
77397
  if (nextId) void run2(nextId, deps);
@@ -79247,10 +79318,131 @@ async function getCodexLimits(dir2) {
79247
79318
  return dto;
79248
79319
  }
79249
79320
 
79321
+ // src/services/antigravity-limits.ts
79322
+ init_config2();
79323
+ import { execFileSync as execFileSync9 } from "node:child_process";
79324
+ import { readFileSync as readFileSync25 } from "node:fs";
79325
+ import { homedir as homedir12 } from "node:os";
79326
+ import { join as join44 } from "node:path";
79327
+ var QUOTA_URL = "https://cloudcode-pa.googleapis.com/v1internal:retrieveUserQuotaSummary";
79328
+ var USER_AGENT = "antigravity (hanoman)";
79329
+ var TTL_MS4 = 6e4;
79330
+ var EXPIRY_SKEW_MS = 3e4;
79331
+ var lastOk2 = null;
79332
+ var freshUntil3 = 0;
79333
+ var UNAVAILABLE2 = { status: "unavailable", windows: [], fetchedAt: null };
79334
+ var securityRun2 = (args) => execFileSync9("security", args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
79335
+ function readAgyToken(run6 = securityRun2) {
79336
+ try {
79337
+ let raw = run6(["find-generic-password", "-s", "gemini", "-a", "antigravity", "-w"]).trim();
79338
+ const prefix = "go-keyring-base64:";
79339
+ if (raw.startsWith(prefix)) raw = Buffer.from(raw.slice(prefix.length), "base64").toString("utf8");
79340
+ const tok = JSON.parse(raw)?.token;
79341
+ if (typeof tok?.access_token !== "string" || !tok.access_token) return null;
79342
+ const expiry = typeof tok.expiry === "string" ? Date.parse(tok.expiry) : NaN;
79343
+ return { accessToken: tok.access_token, expiry: Number.isFinite(expiry) ? expiry : null };
79344
+ } catch {
79345
+ return null;
79346
+ }
79347
+ }
79348
+ var agyHome = () => join44(homedir12(), ".gemini", "antigravity-cli");
79349
+ function defaultProject() {
79350
+ try {
79351
+ return readFileSync25(join44(agyHome(), "cache", "default_project_id.txt"), "utf8").trim() || null;
79352
+ } catch {
79353
+ return null;
79354
+ }
79355
+ }
79356
+ var severityOf2 = (pct3) => pct3 >= 90 ? "critical" : pct3 >= 70 ? "warning" : "normal";
79357
+ function windowLabel2(window2) {
79358
+ if (window2 === "5h") return "Sesi 5 jam";
79359
+ if (window2 === "weekly") return "Mingguan";
79360
+ return window2;
79361
+ }
79362
+ function groupLabel(name2) {
79363
+ if (/^gemini/i.test(name2)) return "Gemini";
79364
+ if (/claude/i.test(name2) && /gpt/i.test(name2)) return "Claude/GPT";
79365
+ return name2;
79366
+ }
79367
+ function mapQuota(json) {
79368
+ const groups = json?.groups;
79369
+ if (!Array.isArray(groups)) return [];
79370
+ return groups.flatMap((g) => {
79371
+ const group2 = groupLabel(g.displayName ?? "");
79372
+ return (Array.isArray(g.buckets) ? g.buckets : []).flatMap((b) => {
79373
+ if (typeof b.remainingFraction !== "number" || !b.bucketId) return [];
79374
+ const remaining = Math.max(0, Math.min(1, b.remainingFraction));
79375
+ const usedPct = Math.round((1 - remaining) * 100);
79376
+ const label2 = windowLabel2(b.window ?? "");
79377
+ return [{
79378
+ key: `antigravity:${b.bucketId}`,
79379
+ label: group2 ? `${group2} \xB7 ${label2}` : label2,
79380
+ usedPct,
79381
+ resetsAt: b.resetTime ?? null,
79382
+ severity: severityOf2(usedPct),
79383
+ // API tak punya padanan `is_active` claude. Yang paling dekat: bucket yang HABIS.
79384
+ isActive: remaining === 0
79385
+ }];
79386
+ });
79387
+ });
79388
+ }
79389
+ function fallback2() {
79390
+ return lastOk2 ? { ...lastOk2, status: "stale" } : UNAVAILABLE2;
79391
+ }
79392
+ var prodDeps = {
79393
+ platform: process.platform,
79394
+ // `HANOMAN_ANTIGRAVITY_LIMITS_FETCH=0` mematikan sumbernya sama sekali (cermin
79395
+ // `HANOMAN_UPDATE_FETCH`): suite test server berjalan di macOS operator, dan route ini tak boleh
79396
+ // membaca Keychain asli lalu memanggil Google dari dalam test.
79397
+ token: () => effectiveStr("HANOMAN_ANTIGRAVITY_LIMITS_FETCH") === "0" ? null : readAgyToken(),
79398
+ project: defaultProject,
79399
+ fetch: (...a) => fetch(...a),
79400
+ now: Date.now
79401
+ };
79402
+ async function fetchQuota(deps) {
79403
+ if (deps.platform !== "darwin") return fallback2();
79404
+ const tok = deps.token();
79405
+ if (!tok) return fallback2();
79406
+ if (tok.expiry !== null && tok.expiry - EXPIRY_SKEW_MS <= deps.now()) return fallback2();
79407
+ const project = deps.project();
79408
+ try {
79409
+ const res = await deps.fetch(QUOTA_URL, {
79410
+ method: "POST",
79411
+ headers: {
79412
+ authorization: `Bearer ${tok.accessToken}`,
79413
+ "content-type": "application/json",
79414
+ "user-agent": USER_AGENT
79415
+ },
79416
+ body: JSON.stringify(project ? { project } : {}),
79417
+ // Endpoint yang menggantung tak boleh menumpuk koneksi di bawah siar 30 dtk (SPEC-197).
79418
+ signal: AbortSignal.timeout(5e3)
79419
+ });
79420
+ if (!res.ok) return fallback2();
79421
+ const windows = mapQuota(await res.json());
79422
+ if (!windows.length) return fallback2();
79423
+ const dto = {
79424
+ status: "ok",
79425
+ windows,
79426
+ fetchedAt: new Date(deps.now()).toISOString()
79427
+ };
79428
+ lastOk2 = dto;
79429
+ return dto;
79430
+ } catch {
79431
+ return fallback2();
79432
+ }
79433
+ }
79434
+ async function getAntigravityLimits(deps = prodDeps) {
79435
+ if (lastOk2 && deps.now() < freshUntil3) return lastOk2;
79436
+ const dto = await fetchQuota(deps);
79437
+ if (dto.status === "ok") freshUntil3 = deps.now() + TTL_MS4;
79438
+ return dto;
79439
+ }
79440
+
79250
79441
  // src/routes/limits.ts
79251
79442
  async function limits(app3) {
79252
79443
  app3.get("/limits", async () => getLimits());
79253
79444
  app3.get("/limits/codex", async () => getCodexLimits());
79445
+ app3.get("/limits/antigravity", async () => getAntigravityLimits());
79254
79446
  }
79255
79447
 
79256
79448
  // src/routes/codex.ts
@@ -79266,7 +79458,7 @@ init_session_sandbox();
79266
79458
  import { execFile as execFile18 } from "node:child_process";
79267
79459
  import { promisify as promisify17 } from "node:util";
79268
79460
  var run4 = promisify17(execFile18);
79269
- var TTL_MS5 = 5 * 6e4;
79461
+ var TTL_MS6 = 5 * 6e4;
79270
79462
  var unavailable = (error) => ({ version: null, error });
79271
79463
  var CHANGED2 = "Konfigurasi runtime berubah; periksa versi kembali.";
79272
79464
  function parseAntigravityVersion(output) {
@@ -79318,7 +79510,7 @@ function createAntigravityVersionService(deps = {}) {
79318
79510
  cache6 = void 0;
79319
79511
  pending2 = void 0;
79320
79512
  }
79321
- if (cache6 && now() - cache6.at < TTL_MS5) return Promise.resolve(cache6.info);
79513
+ if (cache6 && now() - cache6.at < TTL_MS6) return Promise.resolve(cache6.info);
79322
79514
  if (pending2) return pending2;
79323
79515
  const startedGeneration = generation2;
79324
79516
  const request = probe().catch(() => unavailable("Versi Antigravity tidak dapat diperiksa.")).then((info) => {
@@ -79344,7 +79536,7 @@ init_src2();
79344
79536
  init_src();
79345
79537
  import { randomUUID as randomUUID14 } from "node:crypto";
79346
79538
  import { readFile as readFile8, writeFile as writeFile6, rename as rename5, mkdir as mkdir9, rm as rm9 } from "node:fs/promises";
79347
- import { dirname as dirname18, join as join45 } from "node:path";
79539
+ import { dirname as dirname18, join as join46 } from "node:path";
79348
79540
 
79349
79541
  // src/services/model-catalog-parser.ts
79350
79542
  init_zod();
@@ -79451,7 +79643,7 @@ init_session_sandbox();
79451
79643
  import { spawn as spawn5 } from "node:child_process";
79452
79644
  import { mkdtemp as mkdtemp2, rm as rm8 } from "node:fs/promises";
79453
79645
  import { tmpdir as tmpdir9 } from "node:os";
79454
- import { join as join44 } from "node:path";
79646
+ import { join as join45 } from "node:path";
79455
79647
  var MAX_BYTES = 4 * 1024 * 1024;
79456
79648
  var TIMEOUT_MS = 2e4;
79457
79649
  var quote3 = (s2) => "'" + s2.replaceAll("'", "'\\''") + "'";
@@ -79550,7 +79742,7 @@ function runCatalogCommand(command, agent, cwd, env, timeoutMs = TIMEOUT_MS) {
79550
79742
  }
79551
79743
  async function probeModelCatalog(agent) {
79552
79744
  assertAntigravityLaunch(agent);
79553
- const cwd = await mkdtemp2(join44(tmpdir9(), "hanoman-models-"));
79745
+ const cwd = await mkdtemp2(join45(tmpdir9(), "hanoman-models-"));
79554
79746
  const env = agentEnvironment(agent, process.env);
79555
79747
  for (const key of ["HANOMAN_CLAUDE_BIN", "HANOMAN_CODEX_BIN", "HANOMAN_PODMAN_BIN", "HANOMAN_ANTIGRAVITY_BIN"]) {
79556
79748
  const value = effectiveStr(key);
@@ -79661,7 +79853,7 @@ async function writeModelCatalogCache(file, catalog, current = () => true) {
79661
79853
  await rm9(temp, { force: true });
79662
79854
  }
79663
79855
  }
79664
- var cacheFile = () => join45(resolveHome(), "model-catalog.json");
79856
+ var cacheFile = () => join46(resolveHome(), "model-catalog.json");
79665
79857
  var modelCatalogService = createModelCatalogService({
79666
79858
  probe: probeModelCatalog,
79667
79859
  key: catalogProbeKey,
@@ -81158,6 +81350,9 @@ var GROUPS = [
81158
81350
  // ekor + cache 30s, jadi biayanya jauh di bawah panggilan jaringan claude. Frame lahir hanya saat
81159
81351
  // isinya berubah (dedup signature), dan snapshot codex baru bergerak saat ada sesi codex jalan.
81160
81352
  { everyTicks: 30, last: "", build: async () => ({ t: "codexLimits", limits: await getCodexLimits() }) },
81353
+ // ADR-0224 · limit Antigravity — grup TERPISAH. Panggilan jaringan ber-cache 60 dtk di service, dan
81354
+ // nol panggilan saat token agy kedaluwarsa; dedup signature membuat frame lahir saat isinya berubah.
81355
+ { everyTicks: 30, last: "", build: async () => ({ t: "antigravityLimits", limits: await getAntigravityLimits() }) },
81161
81356
  // SPEC-214 · deteksi update jarang berubah; recompute tiap 300 dtk, dedup signature → siar hanya
81162
81357
  // saat status berubah. getUpdateStatus cache 15s + fetch ter-gate (server.ts) → attach tak menahan.
81163
81358
  { everyTicks: 300, last: "", build: async () => ({ t: "update", update: await getUpdateStatus() }) },
@@ -81585,7 +81780,7 @@ init_src();
81585
81780
  init_db();
81586
81781
  import { gunzipSync, gzipSync } from "node:zlib";
81587
81782
  import { mkdir as mkdir10, rename as rename6, writeFile as writeFile7 } from "node:fs/promises";
81588
- import { join as join46 } from "node:path";
81783
+ import { join as join47 } from "node:path";
81589
81784
 
81590
81785
  // src/services/device-auth.ts
81591
81786
  function bearerToken2(req) {
@@ -81841,9 +82036,9 @@ async function sync_default(app3) {
81841
82036
  const deviceId = req.device.id;
81842
82037
  const withTranscriptKey = await Promise.all(parsed.data.entries.map(async (e) => {
81843
82038
  if (!e.transcript) return e;
81844
- const dir2 = join46(resolveDataDirs().home, "remote-transcripts", deviceId);
82039
+ const dir2 = join47(resolveDataDirs().home, "remote-transcripts", deviceId);
81845
82040
  await mkdir10(dir2, { recursive: true, mode: 448 });
81846
- const final = join46(dir2, `${e.seq}.txt`);
82041
+ const final = join47(dir2, `${e.seq}.txt`);
81847
82042
  const tmp = `${final}.tmp`;
81848
82043
  await writeFile7(tmp, e.transcript, { encoding: "utf8", mode: 384 });
81849
82044
  await rename6(tmp, final);
@@ -82238,7 +82433,10 @@ async function session_events_default(app3) {
82238
82433
  event
82239
82434
  });
82240
82435
  if (r.status === "duplicate") return reply.code(202).send({ duplicate: true });
82241
- if (r.status === "rate-limited") return reply.code(429).send({ error: "terlalu banyak event" });
82436
+ if (r.status === "rate-limited") {
82437
+ console.log(`lead ask ${sessionId2}: tanya ${event.askId} ditolak batas laju \u2014 menunggu manusia`);
82438
+ return reply.code(429).send({ error: "terlalu banyak event" });
82439
+ }
82242
82440
  if (r.status === "rejected") return reply.code(202).send({ rejected: true, reason: r.reason });
82243
82441
  return reply.code(202).send({ accepted: true });
82244
82442
  });
@@ -85126,8 +85324,8 @@ init_codex_trust();
85126
85324
  init_qa_attachment_transfer();
85127
85325
  init_uploads();
85128
85326
  import { createHash as createHash21 } from "node:crypto";
85129
- import { execFileSync as execFileSync9 } from "node:child_process";
85130
- import { join as join48 } from "node:path";
85327
+ import { execFileSync as execFileSync10 } from "node:child_process";
85328
+ import { join as join49 } from "node:path";
85131
85329
  var QaSessionError = class extends Error {
85132
85330
  constructor(code, message, needsBind = false) {
85133
85331
  super(message);
@@ -85154,14 +85352,14 @@ async function startQaFindingSession(d, fid) {
85154
85352
  const saved = await saveSessionUpload(id2, bytes, a.mimeType);
85155
85353
  files2.push(`${JSON.stringify(a.filename)} (${a.mimeType}): ${saved.path}`);
85156
85354
  }
85157
- const wt = join48(repoDir, ".worktrees", id2);
85355
+ const wt = join49(repoDir, ".worktrees", id2);
85158
85356
  const branch = `qa/${id2.slice(3)}`;
85159
85357
  const reused = realGit.worktreeAlive(wt);
85160
85358
  try {
85161
85359
  if (!reused) {
85162
85360
  const base3 = realGit.revParse(repoDir, branch) ? branch : "HEAD";
85163
85361
  realGit.addWorktree(repoDir, wt, base3);
85164
- execFileSync9("git", ["checkout", "-B", branch], { cwd: wt, encoding: "utf8" });
85362
+ execFileSync10("git", ["checkout", "-B", branch], { cwd: wt, encoding: "utf8" });
85165
85363
  }
85166
85364
  } catch (e) {
85167
85365
  throw new QaSessionError(422, `Gagal membuat worktree QA: ${e.message}`);
@@ -86695,7 +86893,7 @@ init_src();
86695
86893
  init_db();
86696
86894
  import { mkdirSync as mkdirSync20, mkdtempSync as mkdtempSync5, rmSync as rmSync14, writeFileSync as writeFileSync19 } from "node:fs";
86697
86895
  import { tmpdir as tmpdir10 } from "node:os";
86698
- import { join as join49 } from "node:path";
86896
+ import { join as join50 } from "node:path";
86699
86897
  var STAGE_LABEL = {
86700
86898
  brainstorming: "Dirumuskan",
86701
86899
  objective: "Dirumuskan",
@@ -86738,7 +86936,7 @@ function renderTicketDoc(tickets2) {
86738
86936
  ${baris.join("\n")}`;
86739
86937
  }
86740
86938
  async function buildChatWorkspace(projectId) {
86741
- const dir2 = mkdtempSync5(join49(tmpdir10(), "hanoman-portal-chat-"));
86939
+ const dir2 = mkdtempSync5(join50(tmpdir10(), "hanoman-portal-chat-"));
86742
86940
  try {
86743
86941
  const project = await prisma.project.findUnique({
86744
86942
  where: { id: projectId },
@@ -86753,7 +86951,7 @@ async function buildChatWorkspace(projectId) {
86753
86951
  });
86754
86952
  const files2 = [];
86755
86953
  const tulis = (rel, isi) => {
86756
- writeFileSync19(join49(dir2, rel), isi, { mode: 384 });
86954
+ writeFileSync19(join50(dir2, rel), isi, { mode: 384 });
86757
86955
  files2.push(rel);
86758
86956
  };
86759
86957
  tulis("project.md", renderProjectDoc(project));
@@ -86761,11 +86959,11 @@ async function buildChatWorkspace(projectId) {
86761
86959
  tulis("laporan.md", renderTicketDoc(tickets2.map((t) => toPortalTicket(t, null))));
86762
86960
  const prds = await listPrds(projectId);
86763
86961
  if (prds.length) {
86764
- mkdirSync20(join49(dir2, "dokumen"), { mode: 448 });
86962
+ mkdirSync20(join50(dir2, "dokumen"), { mode: 448 });
86765
86963
  for (const prd of prds) {
86766
86964
  const isi = await readPrd(projectId, prd.path);
86767
86965
  if (!isi) continue;
86768
- tulis(join49("dokumen", `${prd.slug.replaceAll("/", "-")}.md`), isi);
86966
+ tulis(join50("dokumen", `${prd.slug.replaceAll("/", "-")}.md`), isi);
86769
86967
  }
86770
86968
  }
86771
86969
  return { dir: dir2, files: files2, cleanup: () => rmSync14(dir2, { recursive: true, force: true }) };
@@ -87292,13 +87490,13 @@ init_db();
87292
87490
  // src/services/bootstrap.ts
87293
87491
  import { createHash as createHash22, randomBytes as randomBytes11, timingSafeEqual as timingSafeEqual5 } from "node:crypto";
87294
87492
  import { chmod as chmod2, lstat as lstat3, mkdir as mkdir11, readFile as readFile9, unlink as unlink5, writeFile as writeFile8 } from "node:fs/promises";
87295
- import { join as join50 } from "node:path";
87296
- var TTL_MS6 = 15 * 6e4;
87493
+ import { join as join51 } from "node:path";
87494
+ var TTL_MS7 = 15 * 6e4;
87297
87495
  var SETUP_TOKEN_FILE = "setup.token";
87298
87496
  var BootstrapError = class extends Error {
87299
87497
  code = "BOOTSTRAP_PROOF";
87300
87498
  };
87301
- var tokenPath = (home3) => join50(home3, SETUP_TOKEN_FILE);
87499
+ var tokenPath = (home3) => join51(home3, SETUP_TOKEN_FILE);
87302
87500
  async function readStored(home3) {
87303
87501
  try {
87304
87502
  const info = await lstat3(tokenPath(home3));
@@ -87330,7 +87528,7 @@ async function ensureSetupToken(home3, now = Date.now()) {
87330
87528
  });
87331
87529
  }
87332
87530
  const token = randomBytes11(32).toString("base64url");
87333
- const expiresAt = now + TTL_MS6;
87531
+ const expiresAt = now + TTL_MS7;
87334
87532
  try {
87335
87533
  await writeFile8(tokenPath(home3), `${token}
87336
87534
  ${new Date(expiresAt).toISOString()}
@@ -88136,7 +88334,7 @@ init_event_log();
88136
88334
  init_transcript_store();
88137
88335
  init_src2();
88138
88336
  import { readFile as readFile10 } from "node:fs/promises";
88139
- import { join as join51 } from "node:path";
88337
+ import { join as join52 } from "node:path";
88140
88338
  function decodeCursor(c) {
88141
88339
  if (!c) return null;
88142
88340
  try {
@@ -88174,7 +88372,7 @@ async function readRemoteTranscript(id2) {
88174
88372
  const row = await prisma.logEntry.findUnique({ where: { id: id2 } });
88175
88373
  if (!row || row.lane !== "transcript" || !row.transcriptKey) return null;
88176
88374
  if (row.deviceId === "local") return readTranscript(row.transcriptKey);
88177
- const path = join51(resolveDataDirs().home, "remote-transcripts", row.transcriptKey);
88375
+ const path = join52(resolveDataDirs().home, "remote-transcripts", row.transcriptKey);
88178
88376
  try {
88179
88377
  return await readFile10(path, "utf8");
88180
88378
  } catch {
@@ -88841,7 +89039,7 @@ async function tick4(now, deps, end2 = prodEnd) {
88841
89039
  console.error("scheduler drain:", e);
88842
89040
  }
88843
89041
  }
88844
- var prodDeps = {
89042
+ var prodDeps2 = {
88845
89043
  liveCount: async () => (await listSessionsAsync()).filter((s2) => !s2.exited).length,
88846
89044
  isLive: async (specId) => {
88847
89045
  const s2 = await getSessionAsync(sessionIdForSpec(specId));
@@ -88875,7 +89073,7 @@ var prodDeps = {
88875
89073
  };
88876
89074
  var TICK_MS3 = 1e4;
88877
89075
  var timer7;
88878
- function startScheduler(deps = prodDeps) {
89076
+ function startScheduler(deps = prodDeps2) {
88879
89077
  if (timer7) return;
88880
89078
  timer7 = setInterval(() => void tick4(Date.now(), deps), TICK_MS3);
88881
89079
  timer7.unref();
@@ -88910,7 +89108,7 @@ init_src();
88910
89108
  init_db();
88911
89109
  init_session_admission2();
88912
89110
  init_notifications2();
88913
- var prodDeps2 = {
89111
+ var prodDeps3 = {
88914
89112
  // ADR-0170 · mengamandemen ADR-0169 keputusan #4: `bypassCapacity` DICABUT. Auto-resume kini
88915
89113
  // tunduk cap/beban host seperti "Lanjutkan" manual — mesin 8 GB operator sudah dua kali kernel
88916
89114
  // panic akibat batch sesi paralel tepat SAAT boot (memori mac-mini-8gb-panic-agen-paralel),
@@ -88919,7 +89117,7 @@ var prodDeps2 = {
88919
89117
  recordFail: recordFailure,
88920
89118
  recordDeferred: recordFailure
88921
89119
  };
88922
- async function resumeReconciledSessions(cutoff, deps = prodDeps2) {
89120
+ async function resumeReconciledSessions(cutoff, deps = prodDeps3) {
88923
89121
  const report = { resumed: [], failed: [], deferred: [] };
88924
89122
  const specIds = await reconciledSpecIdsSince(cutoff);
88925
89123
  if (!specIds.length) return report;
@@ -89441,9 +89639,9 @@ async function pruneLogs(now, retention, opts2 = {}) {
89441
89639
  }
89442
89640
  async function reconcileRemoteTranscripts() {
89443
89641
  const { readdir: readdir10, unlink: unlink6, stat: stat8 } = await import("node:fs/promises");
89444
- const { join: join52 } = await import("node:path");
89642
+ const { join: join53 } = await import("node:path");
89445
89643
  const { resolveDataDirs: resolveDataDirs2 } = await Promise.resolve().then(() => (init_src2(), src_exports));
89446
- const root = join52(resolveDataDirs2().home, "remote-transcripts");
89644
+ const root = join53(resolveDataDirs2().home, "remote-transcripts");
89447
89645
  let deviceDirs;
89448
89646
  try {
89449
89647
  deviceDirs = await readdir10(root);
@@ -89457,16 +89655,16 @@ async function reconcileRemoteTranscripts() {
89457
89655
  for (const dev of deviceDirs) {
89458
89656
  let files2;
89459
89657
  try {
89460
- files2 = await readdir10(join52(root, dev));
89658
+ files2 = await readdir10(join53(root, dev));
89461
89659
  } catch {
89462
89660
  continue;
89463
89661
  }
89464
89662
  for (const f of files2) {
89465
89663
  const key = `${dev}/${f}`;
89466
89664
  if (referenced.has(key)) continue;
89467
- const st = await stat8(join52(root, dev, f)).catch(() => null);
89665
+ const st = await stat8(join53(root, dev, f)).catch(() => null);
89468
89666
  if (!st || Date.now() - st.mtimeMs < gracePeriodMs) continue;
89469
- await unlink6(join52(root, dev, f)).catch(() => {
89667
+ await unlink6(join53(root, dev, f)).catch(() => {
89470
89668
  });
89471
89669
  orphans++;
89472
89670
  }