@rubytech/create-maxy-code 0.1.475 → 0.1.477

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 (89) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/lib/storage-broker/dist/cf-exec.d.ts.map +1 -1
  3. package/payload/platform/lib/storage-broker/dist/cf-exec.js +3 -2
  4. package/payload/platform/lib/storage-broker/dist/cf-exec.js.map +1 -1
  5. package/payload/platform/lib/storage-broker/src/cf-exec.ts +3 -2
  6. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +3 -3
  7. package/payload/platform/plugins/cloudflare/skills/calendar-site/template/schema.sql +13 -3
  8. package/payload/platform/plugins/docs/references/calendar-booking.md +2 -2
  9. package/payload/platform/plugins/scheduling/PLUGIN.md +2 -2
  10. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-block-sqlite.test.d.ts +2 -0
  11. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-block-sqlite.test.d.ts.map +1 -0
  12. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-block-sqlite.test.js +150 -0
  13. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-block-sqlite.test.js.map +1 -0
  14. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-block.test.d.ts +2 -0
  15. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-block.test.d.ts.map +1 -0
  16. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-block.test.js +248 -0
  17. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-block.test.js.map +1 -0
  18. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-reconcile.test.js +158 -23
  19. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-reconcile.test.js.map +1 -1
  20. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-schema-migration.test.d.ts +2 -0
  21. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-schema-migration.test.d.ts.map +1 -0
  22. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-schema-migration.test.js +200 -0
  23. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-schema-migration.test.js.map +1 -0
  24. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/connector-sync-provision.test.d.ts +2 -0
  25. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/connector-sync-provision.test.d.ts.map +1 -0
  26. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/connector-sync-provision.test.js +309 -0
  27. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/connector-sync-provision.test.js.map +1 -0
  28. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/d1-command.test.js +219 -19
  29. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/d1-command.test.js.map +1 -1
  30. package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.d.ts +184 -11
  31. package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.d.ts.map +1 -1
  32. package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.js +211 -22
  33. package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.js.map +1 -1
  34. package/payload/platform/plugins/scheduling/mcp/dist/lib/connector-sync-provision.d.ts +109 -0
  35. package/payload/platform/plugins/scheduling/mcp/dist/lib/connector-sync-provision.d.ts.map +1 -0
  36. package/payload/platform/plugins/scheduling/mcp/dist/lib/connector-sync-provision.js +213 -0
  37. package/payload/platform/plugins/scheduling/mcp/dist/lib/connector-sync-provision.js.map +1 -0
  38. package/payload/platform/plugins/scheduling/mcp/dist/lib/d1-command.d.ts +80 -8
  39. package/payload/platform/plugins/scheduling/mcp/dist/lib/d1-command.d.ts.map +1 -1
  40. package/payload/platform/plugins/scheduling/mcp/dist/lib/d1-command.js +202 -8
  41. package/payload/platform/plugins/scheduling/mcp/dist/lib/d1-command.js.map +1 -1
  42. package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.d.ts +6 -4
  43. package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.d.ts.map +1 -1
  44. package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js +411 -141
  45. package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js.map +1 -1
  46. package/payload/platform/scripts/cpu-triage.sh +313 -0
  47. package/payload/server/{chunk-Y6V45R6M.js → chunk-JECAP3Z2.js} +296 -63
  48. package/payload/server/maxy-edge.js +1 -1
  49. package/payload/server/public/activity.html +6 -6
  50. package/payload/server/public/assets/{AdminLoginScreens-B44KbEuC.js → AdminLoginScreens-mEKP4pDi.js} +1 -1
  51. package/payload/server/public/assets/{AdminShell-BjxV2qkb.js → AdminShell-BwSaZ88d.js} +1 -1
  52. package/payload/server/public/assets/{Checkbox-DSeZNGNA.js → Checkbox-CN4uQ80w.js} +1 -1
  53. package/payload/server/public/assets/activity-CIQ8ozNZ.js +1 -0
  54. package/payload/server/public/assets/{admin-B4LpTCur.js → admin-BVhgfgs1.js} +1 -1
  55. package/payload/server/public/assets/{browser-CEFvaWwI.js → browser-D-HMmHcM.js} +1 -1
  56. package/payload/server/public/assets/{calendar-B3zo6V34.js → calendar-tGWvFyFn.js} +1 -1
  57. package/payload/server/public/assets/chat-DkttVxAz.js +1 -0
  58. package/payload/server/public/assets/chevron-left-CZ4ez9G5.js +1 -0
  59. package/payload/server/public/assets/data-P-mcmnNi.js +1 -0
  60. package/payload/server/public/assets/{graph-labels-GlTEIYJj.js → graph-labels-qnBleOE6.js} +1 -1
  61. package/payload/server/public/assets/{graph-RaOrnSf2.js → graph-zEw610xK.js} +1 -1
  62. package/payload/server/public/assets/{maximize-2-CHDa8p5h.js → maximize-2-BlTjXT_Y.js} +1 -1
  63. package/payload/server/public/assets/{operator-DtqHY4jV.js → operator-C7oIw2PG.js} +1 -1
  64. package/payload/server/public/assets/{page-DxnWgPtd.js → page-CTP7OFZa.js} +1 -1
  65. package/payload/server/public/assets/{page-CkhFj8GQ.js → page-DL6Zsdvk.js} +1 -1
  66. package/payload/server/public/assets/{public-D5TUfQ3I.js → public-r1A9dqR_.js} +1 -1
  67. package/payload/server/public/assets/{rotate-ccw-BJjVAQQY.js → rotate-ccw-CzkkKx4-.js} +1 -1
  68. package/payload/server/public/assets/{tasks-CM9pYYG1.js → tasks-fIJwYFWG.js} +1 -1
  69. package/payload/server/public/assets/{time-entry-format-C5RYEYjM.js → time-entry-format-j669DgXj.js} +1 -1
  70. package/payload/server/public/assets/{triangle-alert-DLfIsuo_.js → triangle-alert-BPgCgIef.js} +1 -1
  71. package/payload/server/public/assets/{useCopyFeedback-7yKeiAzV.js → useCopyFeedback-BmLa2aes.js} +1 -1
  72. package/payload/server/public/assets/{useSelectionMode-CWlh6AEq.js → useSelectionMode-DnnM7A8-.js} +1 -1
  73. package/payload/server/public/assets/{useSubAccountSwitcher-B2n9QPVI.css → useSubAccountSwitcher-C_E8h07P.css} +1 -1
  74. package/payload/server/public/assets/{useVoiceRecorder-j3ZArZ2H.js → useVoiceRecorder-u2dwZfen.js} +1 -1
  75. package/payload/server/public/browser.html +5 -5
  76. package/payload/server/public/calendar.html +6 -6
  77. package/payload/server/public/chat.html +12 -12
  78. package/payload/server/public/data.html +11 -11
  79. package/payload/server/public/graph.html +11 -11
  80. package/payload/server/public/index.html +14 -14
  81. package/payload/server/public/operator.html +14 -14
  82. package/payload/server/public/public.html +12 -12
  83. package/payload/server/public/tasks.html +5 -5
  84. package/payload/server/server.js +358 -189
  85. package/payload/server/public/assets/activity-Mz1BhgB1.js +0 -1
  86. package/payload/server/public/assets/chat-5tJdo9i_.js +0 -1
  87. package/payload/server/public/assets/chevron-left-BJllrPBj.js +0 -1
  88. package/payload/server/public/assets/data-Du79EgR-.js +0 -1
  89. /package/payload/server/public/assets/{useSubAccountSwitcher-CBlKgwms.js → useSubAccountSwitcher-CFa6ZAT1.js} +0 -0
@@ -28,6 +28,7 @@ import {
28
28
  VNC_DISPLAY,
29
29
  WEBSOCKIFY_PORT,
30
30
  accessPasswordCollides,
31
+ armedCount,
31
32
  autoDeliverPremiumPlugins,
32
33
  canAccessAdmin,
33
34
  checkRateLimit,
@@ -71,6 +72,7 @@ import {
71
72
  isValidAccountId,
72
73
  linkConversationTranscript,
73
74
  listAdminSessions,
75
+ listLoops,
74
76
  listValidAccounts,
75
77
  loadAdminUserName,
76
78
  loadPersonNameByElementId,
@@ -81,6 +83,7 @@ import {
81
83
  projectAgent,
82
84
  reconcileEnabledPluginsForAccounts,
83
85
  recordFailedAttempt,
86
+ registerLoop,
84
87
  registerSession,
85
88
  renameConversation,
86
89
  renderLoginPage,
@@ -94,6 +97,8 @@ import {
94
97
  runAdminUserSelfHeal,
95
98
  runUserProfileReconcile,
96
99
  safeJson,
100
+ sampleCpu,
101
+ sampleProcessCpu,
97
102
  sanitizeClientCorrId,
98
103
  serve,
99
104
  setAccessPassword,
@@ -112,7 +117,7 @@ import {
112
117
  vncLog,
113
118
  walkPremiumBundles,
114
119
  writeAdminUserAndPerson
115
- } from "./chunk-Y6V45R6M.js";
120
+ } from "./chunk-JECAP3Z2.js";
116
121
  import {
117
122
  D1_MAX_SQL_STATEMENT_BYTES,
118
123
  D1_QUERY_MAX_BODY_BYTES,
@@ -2391,12 +2396,12 @@ function startAuthHealthHeartbeat(intervalMs = 5 * 60 * 1e3) {
2391
2396
  ticking = false;
2392
2397
  }
2393
2398
  };
2394
- void tick();
2395
- const handle = setInterval(() => {
2396
- void tick();
2397
- }, intervalMs);
2398
- if (typeof handle.unref === "function") handle.unref();
2399
- return () => clearInterval(handle);
2399
+ return registerLoop({
2400
+ name: "auth-health",
2401
+ intervalMs,
2402
+ firstRunDelayMs: 0,
2403
+ run: tick
2404
+ });
2400
2405
  }
2401
2406
  async function ensureAuth() {
2402
2407
  const health = assessAuthHealth();
@@ -2809,12 +2814,12 @@ function createBaileysLogger(bindings = {}, authDir) {
2809
2814
  var credsSaveQueue = Promise.resolve();
2810
2815
  async function drainCredsSaveQueue(timeoutMs = 5e3) {
2811
2816
  console.error(`${TAG6} draining credential save queue\u2026`);
2812
- const timer2 = new Promise(
2817
+ const timer = new Promise(
2813
2818
  (resolve37) => setTimeout(() => resolve37("timeout"), timeoutMs)
2814
2819
  );
2815
2820
  const result = await Promise.race([
2816
2821
  credsSaveQueue.then(() => "drained"),
2817
- timer2
2822
+ timer
2818
2823
  ]);
2819
2824
  if (result === "timeout") {
2820
2825
  console.error(`${TAG6} credential save queue drain timed out after ${timeoutMs}ms`);
@@ -3100,19 +3105,19 @@ function isTimeoutError(err) {
3100
3105
  }
3101
3106
  function withTimeout(label, promise, timeoutMs) {
3102
3107
  return new Promise((resolve37, reject) => {
3103
- const timer2 = setTimeout(() => {
3108
+ const timer = setTimeout(() => {
3104
3109
  const err = new Error(`${label} timed out after ${timeoutMs}ms`);
3105
3110
  err.__maxyTimeout = true;
3106
3111
  reject(err);
3107
3112
  }, timeoutMs);
3108
- timer2.unref?.();
3113
+ timer.unref?.();
3109
3114
  promise.then(
3110
3115
  (value) => {
3111
- clearTimeout(timer2);
3116
+ clearTimeout(timer);
3112
3117
  resolve37(value);
3113
3118
  },
3114
3119
  (err) => {
3115
- clearTimeout(timer2);
3120
+ clearTimeout(timer);
3116
3121
  reject(err);
3117
3122
  }
3118
3123
  );
@@ -5197,10 +5202,10 @@ function createInboundDebouncer(opts) {
5197
5202
  flushKey(key).catch(onError);
5198
5203
  }, debounceMs);
5199
5204
  } else {
5200
- const timer2 = setTimeout(() => {
5205
+ const timer = setTimeout(() => {
5201
5206
  flushKey(key).catch(onError);
5202
5207
  }, debounceMs);
5203
- pending.set(key, { entries: [msg], timer: timer2 });
5208
+ pending.set(key, { entries: [msg], timer });
5204
5209
  }
5205
5210
  }
5206
5211
  function registerPending(key, promise) {
@@ -5855,9 +5860,9 @@ async function connectWithReconnect(conn) {
5855
5860
  `${TAG17} reconnecting account=${conn.accountId} in ${delay}ms (attempt ${decision.nextAttempts}/${maxAttempts})`
5856
5861
  );
5857
5862
  await new Promise((resolve37) => {
5858
- const timer2 = setTimeout(resolve37, delay);
5863
+ const timer = setTimeout(resolve37, delay);
5859
5864
  conn.abortController.signal.addEventListener("abort", () => {
5860
- clearTimeout(timer2);
5865
+ clearTimeout(timer);
5861
5866
  resolve37();
5862
5867
  }, { once: true });
5863
5868
  });
@@ -9633,36 +9638,43 @@ app3.post("/r2/object/delete", envelopeBodyLimit("write"), async (c) => {
9633
9638
  var storage_broker_default = app3;
9634
9639
  async function runStorageAudit(root) {
9635
9640
  const { reconcileStorage } = await import("./src-JYPKMWJR.js");
9636
- const session = getSession();
9637
- try {
9638
- const cf = await makeHouseCfExec(root);
9639
- const accountsDir = resolve11(root, "..", "data", "accounts");
9640
- const strays = (await reconcileStorage({
9641
- cfD1: async () => (await cf.d1List()).map((d) => d.name),
9642
- cfR2: async () => (await cf.r2List()).map((b) => b.name),
9643
- registeredNames: async (kind) => {
9641
+ const cf = await makeHouseCfExec(root);
9642
+ const accountsDir = resolve11(root, "..", "data", "accounts");
9643
+ const strays = (await reconcileStorage({
9644
+ cfD1: async () => (await cf.d1List()).map((d) => d.name),
9645
+ cfR2: async () => (await cf.r2List()).map((b) => b.name),
9646
+ // Its own session per invocation, never one captured from the enclosing
9647
+ // scope. reconcileStorage issues the "d1" and "r2" reads through the same
9648
+ // Promise.all, and a Neo4j session admits one query at a time — sharing one
9649
+ // made the second reject, which left this audit reporting nothing for 802
9650
+ // consecutive ticks on maxy-code and 295 on sitedesk-code. Sessions come
9651
+ // from the driver pool, so one per read is cheap; the alternative,
9652
+ // serialising the library's fan-out, would bend it around this caller.
9653
+ registeredNames: async (kind) => {
9654
+ const session = getSession();
9655
+ try {
9644
9656
  const res = await session.run(
9645
9657
  "MATCH (r:StorageResource {kind: $kind}) RETURN r.name AS name",
9646
9658
  { kind }
9647
9659
  );
9648
9660
  return res.records.map((rec) => rec.get("name"));
9649
- },
9650
- accountSecretsFiles: async () => {
9651
- if (!existsSync9(accountsDir)) return [];
9652
- const files = [];
9653
- for (const accountId of readdirSync8(accountsDir)) {
9654
- const p = join13(accountsDir, accountId, "secrets", "cloudflare.env");
9655
- if (existsSync9(p)) files.push({ accountId, path: p, contents: readFileSync15(p, "utf8") });
9656
- }
9657
- return files;
9661
+ } finally {
9662
+ await session.close();
9658
9663
  }
9659
- })).strays;
9660
- console.error(
9661
- `[storage-audit] strays=${strays.length}${strays.map((s) => ` ${s.kind}:${s.detail}`).join("")}`
9662
- );
9663
- } finally {
9664
- await session.close();
9665
- }
9664
+ },
9665
+ accountSecretsFiles: async () => {
9666
+ if (!existsSync9(accountsDir)) return [];
9667
+ const files = [];
9668
+ for (const accountId of readdirSync8(accountsDir)) {
9669
+ const p = join13(accountsDir, accountId, "secrets", "cloudflare.env");
9670
+ if (existsSync9(p)) files.push({ accountId, path: p, contents: readFileSync15(p, "utf8") });
9671
+ }
9672
+ return files;
9673
+ }
9674
+ })).strays;
9675
+ console.error(
9676
+ `[storage-audit] strays=${strays.length}${strays.map((s) => ` ${s.kind}:${s.detail}`).join("")}`
9677
+ );
9666
9678
  }
9667
9679
  async function runPagesAudit(root) {
9668
9680
  const { reconcilePages } = await import("./src-JYPKMWJR.js");
@@ -10445,9 +10457,9 @@ function isModelGated(turns) {
10445
10457
  return turns.some((t) => t.kind === "agent-error" && t.code === "model_unavailable");
10446
10458
  }
10447
10459
  function renderOrSuppressChannelInbound(u, ts, out, queuedPendingSuppress) {
10448
- const armed = queuedPendingSuppress.get(u.text) ?? 0;
10449
- if (armed > 0) {
10450
- queuedPendingSuppress.set(u.text, armed - 1);
10460
+ const armed2 = queuedPendingSuppress.get(u.text) ?? 0;
10461
+ if (armed2 > 0) {
10462
+ queuedPendingSuppress.set(u.text, armed2 - 1);
10451
10463
  } else {
10452
10464
  out.push(operatorInboundTurn(u, ts));
10453
10465
  }
@@ -10656,8 +10668,8 @@ function pickActivity(entries, grown, nowMs, concurrencyWindowMs) {
10656
10668
 
10657
10669
  // server/routes/stream-lifecycle.ts
10658
10670
  var GAUGE_INTERVAL_MS = 6e4;
10659
- function isArmed(timer2) {
10660
- return timer2._destroyed !== true;
10671
+ function isArmed(timer) {
10672
+ return timer._destroyed !== true;
10661
10673
  }
10662
10674
  var Registration = class {
10663
10675
  constructor(connId, channel, startedAt) {
@@ -10667,9 +10679,9 @@ var Registration = class {
10667
10679
  }
10668
10680
  timers = [];
10669
10681
  closed = false;
10670
- arm(timer2) {
10671
- this.timers.push(timer2);
10672
- return timer2;
10682
+ arm(timer) {
10683
+ this.timers.push(timer);
10684
+ return timer;
10673
10685
  }
10674
10686
  teardown(reason) {
10675
10687
  if (this.closed) return false;
@@ -10703,11 +10715,11 @@ function emitGauge() {
10703
10715
  counts[reg.channel]++;
10704
10716
  oldestMs = Math.max(oldestMs, now - reg.startedAt);
10705
10717
  }
10706
- const armed = counts.reader + counts.store;
10718
+ const armed2 = counts.reader + counts.store;
10707
10719
  console.log(
10708
- `[wa-stream] op=gauge armed=${armed} reader=${counts.reader} store=${counts.store} oldestSec=${Math.floor(oldestMs / 1e3)} stale=${stale}`
10720
+ `[wa-stream] op=gauge armed=${armed2} reader=${counts.reader} store=${counts.store} oldestSec=${Math.floor(oldestMs / 1e3)} stale=${stale}`
10709
10721
  );
10710
- if (armed === 0) stopGauge();
10722
+ if (armed2 === 0) stopGauge();
10711
10723
  }
10712
10724
  function startGauge() {
10713
10725
  if (gaugeTimer) return;
@@ -13902,12 +13914,21 @@ function checkRateLimit2(ip, cls) {
13902
13914
  }
13903
13915
  return { allow: true, blocked: 0, shouldLog: false };
13904
13916
  }
13905
- setInterval(() => {
13906
- const now = Date.now();
13907
- for (const [ip, rec] of rateHits) {
13908
- if (now - rec.windowStart >= WINDOW_MS * 2) rateHits.delete(ip);
13917
+ registerLoop({
13918
+ name: "client-error-rate-cleanup",
13919
+ intervalMs: WINDOW_MS,
13920
+ run: () => {
13921
+ const now = Date.now();
13922
+ let purged = 0;
13923
+ for (const [key, rec] of rateHits) {
13924
+ if (now - rec.windowStart >= WINDOW_MS * 2) {
13925
+ rateHits.delete(key);
13926
+ purged += 1;
13927
+ }
13928
+ }
13929
+ return `purged ${purged}, ${rateHits.size} live`;
13909
13930
  }
13910
- }, WINDOW_MS).unref();
13931
+ });
13911
13932
  function truncate(value, max) {
13912
13933
  if (typeof value !== "string") return "";
13913
13934
  return value.length > max ? value.slice(0, max) + "\u2026" : value;
@@ -20701,18 +20722,18 @@ async function probeConversationDb() {
20701
20722
  } catch (err) {
20702
20723
  return { state: "error", reason: err instanceof Error ? err.message : String(err) };
20703
20724
  }
20704
- let timer2;
20725
+ let timer;
20705
20726
  try {
20706
20727
  const probe = session.run("RETURN 1");
20707
20728
  const timeout = new Promise((_, reject) => {
20708
- timer2 = setTimeout(() => reject(new Error("timeout")), PROBE_TIMEOUT_MS);
20729
+ timer = setTimeout(() => reject(new Error("timeout")), PROBE_TIMEOUT_MS);
20709
20730
  });
20710
20731
  await Promise.race([probe, timeout]);
20711
20732
  return { state: "ok" };
20712
20733
  } catch (err) {
20713
20734
  return { state: "error", reason: err instanceof Error ? err.message : String(err) };
20714
20735
  } finally {
20715
- if (timer2) clearTimeout(timer2);
20736
+ if (timer) clearTimeout(timer);
20716
20737
  session.close().catch(() => {
20717
20738
  });
20718
20739
  }
@@ -22383,10 +22404,32 @@ app52.get("/", requireAdminSession, async (c) => {
22383
22404
  if (name) names[a.accountId] = name;
22384
22405
  })
22385
22406
  );
22407
+ const sampledAt = Date.now();
22408
+ sampleCpu(sampledAt);
22409
+ const cpu = sampleProcessCpu(sampledAt);
22410
+ const loopRows = listLoops();
22411
+ const armed2 = armedCount();
22412
+ const byState = loopRows.reduce((acc, r) => {
22413
+ acc[r.state] = (acc[r.state] ?? 0) + 1;
22414
+ return acc;
22415
+ }, {});
22416
+ console.log(
22417
+ `${TAG42} op=loops req=${req} armed=${armed2} rendered=${loopRows.length} procPct=${cpu.processPct?.toFixed(1) ?? "null"} attributedPct=${cpu.attributedPct?.toFixed(1) ?? "null"} ` + Object.entries(byState).map(([s, n]) => `${s}=${n}`).join(" ")
22418
+ );
22386
22419
  return c.json({
22387
22420
  rows,
22388
22421
  census,
22389
22422
  censusAgeMs,
22423
+ loops: {
22424
+ armed: armed2,
22425
+ rows: loopRows,
22426
+ // The server's own CPU beside what the registry can explain. The GAP is
22427
+ // the point: loops summing to 2% against a process at 60% says the cost
22428
+ // is somewhere else, which small numbers alone would never tell you.
22429
+ processPct: cpu.processPct,
22430
+ attributedPct: cpu.attributedPct,
22431
+ unattributedPct: cpu.unattributedPct
22432
+ },
22390
22433
  accounts: {
22391
22434
  onDisk,
22392
22435
  valid: valid.length,
@@ -23130,12 +23173,21 @@ function rateLimit(ip, now = Date.now()) {
23130
23173
  if (b.count > RATE_LIMIT) return { ok: false, resetIn: b.resetAt - now };
23131
23174
  return { ok: true, resetIn: b.resetAt - now };
23132
23175
  }
23133
- setInterval(() => {
23134
- const now = Date.now();
23135
- for (const [ip, b] of buckets) {
23136
- if (now >= b.resetAt) buckets.delete(ip);
23176
+ registerLoop({
23177
+ name: "visitor-event-rate-cleanup",
23178
+ intervalMs: 5 * RATE_WINDOW_MS,
23179
+ run: () => {
23180
+ const now = Date.now();
23181
+ let purged = 0;
23182
+ for (const [ip, b] of buckets) {
23183
+ if (now >= b.resetAt) {
23184
+ buckets.delete(ip);
23185
+ purged += 1;
23186
+ }
23187
+ }
23188
+ return `purged ${purged}, ${buckets.size} live`;
23137
23189
  }
23138
- }, 5 * RATE_WINDOW_MS).unref?.();
23190
+ });
23139
23191
  var VALID_TYPES = /* @__PURE__ */ new Set(["pageview", "click", "scroll", "dwell"]);
23140
23192
  var ID_RE = /^[A-Za-z0-9_-]{6,128}$/;
23141
23193
  function parseEvent(body) {
@@ -23801,14 +23853,12 @@ function startTimeEntryCensus(openSession, opts = {}) {
23801
23853
  }
23802
23854
  }
23803
23855
  };
23804
- void tick();
23805
- const id = setInterval(() => {
23806
- void tick();
23807
- }, intervalMs);
23808
- if (typeof id.unref === "function") {
23809
- id.unref();
23810
- }
23811
- return () => clearInterval(id);
23856
+ return registerLoop({
23857
+ name: "timeentry-census",
23858
+ intervalMs,
23859
+ firstRunDelayMs: 0,
23860
+ run: tick
23861
+ });
23812
23862
  }
23813
23863
 
23814
23864
  // app/lib/ledger-census.ts
@@ -23962,15 +24012,12 @@ function startLedgerCensus(openSession, opts = {}) {
23962
24012
  }
23963
24013
  }
23964
24014
  };
23965
- void tick();
23966
- const id = setInterval(() => {
23967
- void tick();
23968
- }, intervalMs);
23969
- if (typeof id.unref === "function") {
23970
- ;
23971
- id.unref();
23972
- }
23973
- return () => clearInterval(id);
24015
+ return registerLoop({
24016
+ name: "ledger-census",
24017
+ intervalMs,
24018
+ firstRunDelayMs: 0,
24019
+ run: tick
24020
+ });
23974
24021
  }
23975
24022
 
23976
24023
  // app/lib/conversation-audit.ts
@@ -24022,7 +24069,7 @@ function renderLabelTop(rows) {
24022
24069
  return `${labels}:${c}`;
24023
24070
  }).join(",");
24024
24071
  }
24025
- var timer = null;
24072
+ var armed = false;
24026
24073
  async function runGraphHealthTick() {
24027
24074
  const session = getSession();
24028
24075
  try {
@@ -24117,16 +24164,16 @@ async function verifyUserProfileConstraint() {
24117
24164
  }
24118
24165
  }
24119
24166
  function startGraphHealthTimer() {
24120
- if (timer) return;
24167
+ if (armed) return;
24121
24168
  verifyUserProfileConstraint().catch(() => {
24122
24169
  });
24123
- runGraphHealthTick().catch(() => {
24170
+ registerLoop({
24171
+ name: "graph-health",
24172
+ intervalMs: HOUR_MS,
24173
+ firstRunDelayMs: 0,
24174
+ run: () => runGraphHealthTick()
24124
24175
  });
24125
- timer = setInterval(() => {
24126
- runGraphHealthTick().catch(() => {
24127
- });
24128
- }, HOUR_MS);
24129
- if (typeof timer.unref === "function") timer.unref();
24176
+ armed = true;
24130
24177
  }
24131
24178
 
24132
24179
  // app/lib/file-watcher.ts
@@ -24968,10 +25015,28 @@ var WaGateway = class {
24968
25015
  }
24969
25016
  return events;
24970
25017
  }
24971
- /** Start the periodic reconciliation sweep in the long-lived UI process.
24972
- * Returns the interval handle. */
25018
+ /** Arm the periodic reconciliation sweep in the long-lived UI process.
25019
+ *
25020
+ * Task 1814 — armed through the registry, so it appears on /activity. This
25021
+ * gateway is constructed once at module scope in server/index.ts and reached
25022
+ * everywhere else through the singleton accessor in ./instance, so the sweep
25023
+ * is per-install, not per-instance. Returns the registry's disarm function
25024
+ * rather than the interval handle; no caller reads it.
25025
+ *
25026
+ * Two semantics move with it. The timer is now unref'd, where the bare
25027
+ * setInterval was not: a loop is a passenger of the process, never what
25028
+ * keeps it alive. And a throwing sweep is recorded as lastError instead of
25029
+ * surfacing as an uncaught timer exception, so a sweep that fails every
25030
+ * tick now reads as a failing row on /activity rather than a crash. */
24973
25031
  startSweeper(intervalMs = SWEEP_INTERVAL_MS) {
24974
- return setInterval(() => this.reconcile(Date.now()), intervalMs);
25032
+ return registerLoop({
25033
+ name: "whatsapp-native-sweep",
25034
+ intervalMs,
25035
+ run: () => {
25036
+ const events = this.reconcile(Date.now());
25037
+ return events.length > 0 ? `${events.length} terminal events` : void 0;
25038
+ }
25039
+ });
24975
25040
  }
24976
25041
  /** Handle one inbound admin WhatsApp message. */
24977
25042
  async handleInbound(input) {
@@ -25446,13 +25511,13 @@ var WebchatGateway = class _WebchatGateway {
25446
25511
  * response, preserving the pre-756 POST→single-blob browser contract. */
25447
25512
  awaitReply(key, timeoutMs) {
25448
25513
  return new Promise((resolve37) => {
25449
- const timer2 = setTimeout(() => {
25514
+ const timer = setTimeout(() => {
25450
25515
  this.replyAwaiters.delete(key);
25451
25516
  resolve37({ timeout: true });
25452
25517
  }, timeoutMs);
25453
- if (timer2.unref) timer2.unref();
25518
+ if (timer.unref) timer.unref();
25454
25519
  this.replyAwaiters.set(key, (r) => {
25455
- clearTimeout(timer2);
25520
+ clearTimeout(timer);
25456
25521
  this.replyAwaiters.delete(key);
25457
25522
  resolve37(r);
25458
25523
  });
@@ -25467,16 +25532,16 @@ var WebchatGateway = class _WebchatGateway {
25467
25532
  const k = _WebchatGateway.promptKey(p.key, p.requestId);
25468
25533
  return new Promise((resolve37) => {
25469
25534
  this.pendingPrompts.get(k)?.resolve({ timeout: true });
25470
- const timer2 = setTimeout(() => {
25535
+ const timer = setTimeout(() => {
25471
25536
  this.pendingPrompts.delete(k);
25472
25537
  resolve37({ timeout: true });
25473
25538
  }, timeoutMs);
25474
- if (timer2.unref) timer2.unref();
25539
+ if (timer.unref) timer.unref();
25475
25540
  this.pendingPrompts.set(k, {
25476
25541
  ...p,
25477
25542
  at: Date.now(),
25478
25543
  resolve: (r) => {
25479
- clearTimeout(timer2);
25544
+ clearTimeout(timer);
25480
25545
  this.pendingPrompts.delete(k);
25481
25546
  resolve37(r);
25482
25547
  }
@@ -25544,11 +25609,22 @@ var WebchatGateway = class _WebchatGateway {
25544
25609
  void this.deps.deleteSession?.(rec.sessionId);
25545
25610
  }
25546
25611
  }
25547
- /** Start the periodic public idle reaper in the long-lived UI process. */
25612
+ /** Arm the periodic public idle reaper in the long-lived UI process.
25613
+ *
25614
+ * Task 1814 — armed through the registry, so it appears on /activity. Like
25615
+ * the sweeper below, this is per-install: one gateway per server, reached
25616
+ * through the singleton accessor in ./instance. Returns the registry's
25617
+ * disarm function rather than the interval handle; no caller reads it.
25618
+ *
25619
+ * This one already unref'd its handle, so only the error semantics move: a
25620
+ * throwing reap is recorded as lastError rather than surfacing as an
25621
+ * uncaught timer exception. */
25548
25622
  startPublicReaper(intervalMs = 6e4) {
25549
- const handle = setInterval(() => this.reapPublic(Date.now()), intervalMs);
25550
- if (handle.unref) handle.unref();
25551
- return handle;
25623
+ return registerLoop({
25624
+ name: "webchat-public-reaper",
25625
+ intervalMs,
25626
+ run: () => this.reapPublic(Date.now())
25627
+ });
25552
25628
  }
25553
25629
  /** A gated public PTY exited explicitly on the manager side (operator stop,
25554
25630
  * claude crash, manager DELETE), not via the idle reaper. The manager POSTs
@@ -25630,15 +25706,36 @@ var WebchatGateway = class _WebchatGateway {
25630
25706
  }
25631
25707
  return events;
25632
25708
  }
25633
- /** Start the periodic reconciliation sweep in the long-lived UI process. Each
25709
+ /** Arm the periodic reconciliation sweep in the long-lived UI process. Each
25634
25710
  * tick runs the sync sweep (drain-/inbound-undelivered) AND the async
25635
- * recovery pass for queued-no-subscriber messages (Task-962). */
25711
+ * recovery pass for queued-no-subscriber messages (Task-962).
25712
+ *
25713
+ * Task 1814 — armed through the registry, so it appears on /activity. This
25714
+ * gateway is constructed once at module scope in server/index.ts and reached
25715
+ * everywhere else through the singleton accessor in ./instance, so the sweep
25716
+ * is per-install, not per-instance. Returns the registry's disarm function
25717
+ * rather than the interval handle; no caller reads it.
25718
+ *
25719
+ * Two semantics move with it. The timer is now unref'd, where the bare
25720
+ * setInterval was not: a loop is a passenger of the process, never what
25721
+ * keeps it alive. And a throwing sweep is recorded as lastError instead of
25722
+ * surfacing as an uncaught timer exception, so a sweep that fails every
25723
+ * tick now reads as a failing row on /activity rather than a crash.
25724
+ *
25725
+ * recoverStalled stays fire-and-forget, exactly as it was: run() returns the
25726
+ * sync note, so the registry's duration covers the sweep and not the
25727
+ * recovery pass. */
25636
25728
  startSweeper(intervalMs = SWEEP_INTERVAL_MS2) {
25637
- return setInterval(() => {
25638
- const now = Date.now();
25639
- this.reconcile(now);
25640
- void this.recoverStalled(now);
25641
- }, intervalMs);
25729
+ return registerLoop({
25730
+ name: "webchat-inbound-sweep",
25731
+ intervalMs,
25732
+ run: () => {
25733
+ const now = Date.now();
25734
+ const events = this.reconcile(now);
25735
+ void this.recoverStalled(now);
25736
+ return events.length > 0 ? `${events.length} terminal events` : void 0;
25737
+ }
25738
+ });
25642
25739
  }
25643
25740
  /** Task-962 — the per-session delivery-failure marker for the /session pointer.
25644
25741
  * Non-null only when redelivery was exhausted for `key`; cleared on the next
@@ -26576,9 +26673,28 @@ var TelegramGateway = class {
26576
26673
  }
26577
26674
  return events;
26578
26675
  }
26579
- /** Start the periodic reconciliation sweep in the long-lived UI process. */
26676
+ /** Arm the periodic reconciliation sweep in the long-lived UI process.
26677
+ *
26678
+ * Task 1814 — armed through the registry, so it appears on /activity. This
26679
+ * gateway is constructed once at module scope in server/index.ts and reached
26680
+ * everywhere else through the singleton accessor in ./instance, so the sweep
26681
+ * is per-install, not per-instance. Returns the registry's disarm function
26682
+ * rather than the interval handle; no caller reads it.
26683
+ *
26684
+ * Two semantics move with it. The timer is now unref'd, where the bare
26685
+ * setInterval was not: a loop is a passenger of the process, never what
26686
+ * keeps it alive. And a throwing sweep is recorded as lastError instead of
26687
+ * surfacing as an uncaught timer exception, so a sweep that fails every
26688
+ * tick now reads as a failing row on /activity rather than a crash. */
26580
26689
  startSweeper(intervalMs = SWEEP_INTERVAL_MS3) {
26581
- return setInterval(() => this.reconcile(Date.now()), intervalMs);
26690
+ return registerLoop({
26691
+ name: "telegram-native-sweep",
26692
+ intervalMs,
26693
+ run: () => {
26694
+ const events = this.reconcile(Date.now());
26695
+ return events.length > 0 ? `${events.length} terminal events` : void 0;
26696
+ }
26697
+ });
26582
26698
  }
26583
26699
  /** Handle one inbound telegram message. */
26584
26700
  async handleInbound(input) {
@@ -27569,8 +27685,12 @@ function runDuplicateSenderAudit() {
27569
27685
  console.error(`[whatsapp-native] op=duplicate-sender-audit-error error=${err instanceof Error ? err.message : String(err)}`);
27570
27686
  }
27571
27687
  }
27572
- setTimeout(runDuplicateSenderAudit, 0).unref();
27573
- setInterval(runDuplicateSenderAudit, 15 * 6e4).unref();
27688
+ registerLoop({
27689
+ name: "whatsapp-duplicate-sender-audit",
27690
+ intervalMs: 15 * 6e4,
27691
+ firstRunDelayMs: 0,
27692
+ run: runDuplicateSenderAudit
27693
+ });
27574
27694
  var webchatGateway = new WebchatGateway({
27575
27695
  fetchStandingRules: fetchAccountStandingRules,
27576
27696
  gatewayUrl: webchatGatewayUrl(),
@@ -28499,7 +28619,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
28499
28619
  const reconcilePlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join42(__dirname, "..");
28500
28620
  const reconcileScript = resolve36(reconcilePlatformRoot, "plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js");
28501
28621
  const RECONCILE_INTERVAL_MS = 12e4;
28502
- const runReconcile = () => {
28622
+ const runReconcile = (ctx) => {
28503
28623
  if (!existsSync38(reconcileScript)) return;
28504
28624
  try {
28505
28625
  const child = spawn3(process.execPath, [reconcileScript], {
@@ -28507,21 +28627,25 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
28507
28627
  stdio: "inherit"
28508
28628
  });
28509
28629
  child.unref();
28630
+ ctx.child(child.pid);
28510
28631
  child.on("error", (err) => console.error(`[calendar-reconcile] spawn-failed err="${err.message}"`));
28511
28632
  } catch (err) {
28512
28633
  console.error(`[calendar-reconcile] spawn-failed err="${err.message}"`);
28513
28634
  }
28514
28635
  };
28515
- const firstRun = setTimeout(runReconcile, 15e3);
28516
- firstRun.unref();
28517
- const loop = setInterval(runReconcile, RECONCILE_INTERVAL_MS);
28518
- loop.unref();
28636
+ registerLoop({
28637
+ name: "calendar-reconcile",
28638
+ intervalMs: RECONCILE_INTERVAL_MS,
28639
+ firstRunDelayMs: 15e3,
28640
+ detached: true,
28641
+ run: runReconcile
28642
+ });
28519
28643
  }
28520
28644
  {
28521
28645
  const outlookPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join42(__dirname, "..");
28522
28646
  const outlookScript = resolve36(outlookPlatformRoot, "plugins/outlook/mcp/dist/scripts/complete-registration.js");
28523
28647
  const OUTLOOK_COMPLETE_INTERVAL_MS = 3e4;
28524
- const runOutlookComplete = () => {
28648
+ const runOutlookComplete = (ctx) => {
28525
28649
  if (!existsSync38(outlookScript)) return;
28526
28650
  try {
28527
28651
  const child = spawn3(process.execPath, [outlookScript], {
@@ -28529,15 +28653,19 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
28529
28653
  stdio: "inherit"
28530
28654
  });
28531
28655
  child.unref();
28656
+ ctx.child(child.pid);
28532
28657
  child.on("error", (err) => console.error(`[outlook-complete] spawn-failed err="${err.message}"`));
28533
28658
  } catch (err) {
28534
28659
  console.error(`[outlook-complete] spawn-failed err="${err.message}"`);
28535
28660
  }
28536
28661
  };
28537
- const outlookFirst = setTimeout(runOutlookComplete, 18e3);
28538
- outlookFirst.unref();
28539
- const outlookLoop = setInterval(runOutlookComplete, OUTLOOK_COMPLETE_INTERVAL_MS);
28540
- outlookLoop.unref();
28662
+ registerLoop({
28663
+ name: "outlook-devicecode-complete",
28664
+ intervalMs: OUTLOOK_COMPLETE_INTERVAL_MS,
28665
+ firstRunDelayMs: 18e3,
28666
+ detached: true,
28667
+ run: runOutlookComplete
28668
+ });
28541
28669
  }
28542
28670
  {
28543
28671
  const auditRoot = process.env.MAXY_PLATFORM_ROOT ?? join42(__dirname, "..");
@@ -28562,10 +28690,12 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
28562
28690
  console.error(`[outlook-mcp] devicecode-stranded-audit error="${err.message}"`);
28563
28691
  }
28564
28692
  };
28565
- const strandedFirst = setTimeout(runStrandedAudit, 25e3);
28566
- strandedFirst.unref();
28567
- const strandedLoop = setInterval(runStrandedAudit, STRANDED_AUDIT_INTERVAL_MS);
28568
- strandedLoop.unref();
28693
+ registerLoop({
28694
+ name: "outlook-stranded-audit",
28695
+ intervalMs: STRANDED_AUDIT_INTERVAL_MS,
28696
+ firstRunDelayMs: 25e3,
28697
+ run: runStrandedAudit
28698
+ });
28569
28699
  }
28570
28700
  {
28571
28701
  const googleRoot = process.env.MAXY_PLATFORM_ROOT ?? join42(__dirname, "..");
@@ -28578,13 +28708,15 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
28578
28708
  console.error(`[google-oauth] op=pending-audit-error error="${err.message}"`);
28579
28709
  }
28580
28710
  };
28581
- const googlePendingFirst = setTimeout(runGooglePendingAuditSafe, 27e3);
28582
- googlePendingFirst.unref();
28583
- const googlePendingLoop = setInterval(runGooglePendingAuditSafe, GOOGLE_PENDING_AUDIT_INTERVAL_MS);
28584
- googlePendingLoop.unref();
28711
+ registerLoop({
28712
+ name: "google-pending-audit",
28713
+ intervalMs: GOOGLE_PENDING_AUDIT_INTERVAL_MS,
28714
+ firstRunDelayMs: 27e3,
28715
+ run: runGooglePendingAuditSafe
28716
+ });
28585
28717
  const googleAuditScript = resolve36(googleRoot, "plugins/google/mcp/dist/scripts/account-audit.js");
28586
28718
  const GOOGLE_ACCOUNT_AUDIT_INTERVAL_MS = 36e5;
28587
- const runGoogleAccountAudit = () => {
28719
+ const runGoogleAccountAudit = (ctx) => {
28588
28720
  if (!existsSync38(googleAuditScript)) return;
28589
28721
  try {
28590
28722
  const child = spawn3(process.execPath, [googleAuditScript], {
@@ -28592,6 +28724,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
28592
28724
  stdio: "inherit"
28593
28725
  });
28594
28726
  child.unref();
28727
+ ctx.child(child.pid);
28595
28728
  child.on(
28596
28729
  "error",
28597
28730
  (err) => console.error(`[google-account-audit] op=spawn-failed error="${err.message}"`)
@@ -28600,32 +28733,37 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
28600
28733
  console.error(`[google-account-audit] op=spawn-failed error="${err.message}"`);
28601
28734
  }
28602
28735
  };
28603
- const googleAuditFirst = setTimeout(runGoogleAccountAudit, 45e3);
28604
- googleAuditFirst.unref();
28605
- const googleAuditLoop = setInterval(runGoogleAccountAudit, GOOGLE_ACCOUNT_AUDIT_INTERVAL_MS);
28606
- googleAuditLoop.unref();
28736
+ registerLoop({
28737
+ name: "google-account-audit",
28738
+ intervalMs: GOOGLE_ACCOUNT_AUDIT_INTERVAL_MS,
28739
+ firstRunDelayMs: 45e3,
28740
+ detached: true,
28741
+ run: runGoogleAccountAudit
28742
+ });
28607
28743
  }
28608
28744
  {
28609
28745
  const auditPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join42(__dirname, "..");
28610
28746
  const STORAGE_AUDIT_INTERVAL_MS = 3e5;
28611
- const runStorageAuditSafe = () => {
28612
- runStorageAudit(auditPlatformRoot).catch(
28613
- (err) => console.error(`[storage-audit] error="${err.message}"`)
28614
- );
28615
- };
28616
- const auditFirst = setTimeout(runStorageAuditSafe, 2e4);
28617
- auditFirst.unref();
28618
- const auditLoop = setInterval(runStorageAuditSafe, STORAGE_AUDIT_INTERVAL_MS);
28619
- auditLoop.unref();
28620
- const runPagesAuditSafe = () => {
28621
- runPagesAudit(auditPlatformRoot).catch(
28622
- (err) => console.error(`[pages-audit] error="${err.message}"`)
28623
- );
28624
- };
28625
- const pagesAuditFirst = setTimeout(runPagesAuditSafe, 35e3);
28626
- pagesAuditFirst.unref();
28627
- const pagesAuditLoop = setInterval(runPagesAuditSafe, STORAGE_AUDIT_INTERVAL_MS);
28628
- pagesAuditLoop.unref();
28747
+ const runStorageAuditSafe = () => runStorageAudit(auditPlatformRoot).catch((err) => {
28748
+ console.error(`[storage-audit] error="${err.message}"`);
28749
+ throw err;
28750
+ });
28751
+ registerLoop({
28752
+ name: "storage-audit",
28753
+ intervalMs: STORAGE_AUDIT_INTERVAL_MS,
28754
+ firstRunDelayMs: 2e4,
28755
+ run: runStorageAuditSafe
28756
+ });
28757
+ const runPagesAuditSafe = () => runPagesAudit(auditPlatformRoot).catch((err) => {
28758
+ console.error(`[pages-audit] error="${err.message}"`);
28759
+ throw err;
28760
+ });
28761
+ registerLoop({
28762
+ name: "pages-audit",
28763
+ intervalMs: STORAGE_AUDIT_INTERVAL_MS,
28764
+ firstRunDelayMs: 35e3,
28765
+ run: runPagesAuditSafe
28766
+ });
28629
28767
  }
28630
28768
  {
28631
28769
  const publishPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join42(__dirname, "..");
@@ -28639,7 +28777,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
28639
28777
  return null;
28640
28778
  }
28641
28779
  };
28642
- const spawnPublish = (account) => {
28780
+ const spawnPublish = (account, ctx) => {
28643
28781
  if (!existsSync38(publishScript)) return;
28644
28782
  try {
28645
28783
  const child = spawn3(process.execPath, [publishScript], {
@@ -28652,6 +28790,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
28652
28790
  stdio: "inherit"
28653
28791
  });
28654
28792
  child.unref();
28793
+ ctx.child(child.pid);
28655
28794
  child.on("error", (err) => console.error(`[calendar-availability] op=publish result=error reason=spawn-failed err="${err.message}"`));
28656
28795
  } catch (err) {
28657
28796
  console.error(`[calendar-availability] op=publish result=error reason=spawn-failed err="${err.message}"`);
@@ -28683,16 +28822,19 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
28683
28822
  const ageMs = Number.isNaN(parsed) ? null : Date.now() - parsed;
28684
28823
  console.error(`[calendar-availability] op=audit accountId=${account.accountId} snapshotAgeMs=${ageMs ?? "na"}`);
28685
28824
  };
28686
- const publishTick = () => {
28825
+ const publishTick = (ctx) => {
28687
28826
  const account = currentAccount();
28688
28827
  if (!account) return;
28689
- spawnPublish(account);
28828
+ spawnPublish(account, ctx);
28690
28829
  auditSnapshotAge(account);
28691
28830
  };
28692
- const firstPublish = setTimeout(publishTick, 2e4);
28693
- firstPublish.unref();
28694
- const publishLoop = setInterval(publishTick, PUBLISH_INTERVAL_MS);
28695
- publishLoop.unref();
28831
+ registerLoop({
28832
+ name: "calendar-availability-publish",
28833
+ intervalMs: PUBLISH_INTERVAL_MS,
28834
+ firstRunDelayMs: 2e4,
28835
+ detached: true,
28836
+ run: publishTick
28837
+ });
28696
28838
  }
28697
28839
  startTimeEntryCensus(() => getSession());
28698
28840
  startLedgerCensus(() => getSession());
@@ -28701,7 +28843,7 @@ startLedgerCensus(() => getSession());
28701
28843
  const auditScript = resolve36(auditPlatformRoot, "plugins/connector/mcp/dist/scripts/audit-connectors.js");
28702
28844
  const auditLogDir = process.env.LOG_DIR ?? LOG_DIR;
28703
28845
  const CONNECTOR_AUDIT_INTERVAL_MS = 3e5;
28704
- const runConnectorAudit = () => {
28846
+ const runConnectorAudit = (ctx) => {
28705
28847
  if (!existsSync38(auditScript)) return;
28706
28848
  try {
28707
28849
  const child = spawn3(process.execPath, [auditScript], {
@@ -28709,15 +28851,19 @@ startLedgerCensus(() => getSession());
28709
28851
  stdio: "inherit"
28710
28852
  });
28711
28853
  child.unref();
28854
+ ctx.child(child.pid);
28712
28855
  child.on("error", (err) => console.error(`[connector-audit] spawn-failed err="${err.message}"`));
28713
28856
  } catch (err) {
28714
28857
  console.error(`[connector-audit] spawn-failed err="${err.message}"`);
28715
28858
  }
28716
28859
  };
28717
- const firstAudit = setTimeout(runConnectorAudit, 3e4);
28718
- firstAudit.unref();
28719
- const auditLoop = setInterval(runConnectorAudit, CONNECTOR_AUDIT_INTERVAL_MS);
28720
- auditLoop.unref();
28860
+ registerLoop({
28861
+ name: "connector-audit",
28862
+ intervalMs: CONNECTOR_AUDIT_INTERVAL_MS,
28863
+ firstRunDelayMs: 3e4,
28864
+ detached: true,
28865
+ run: runConnectorAudit
28866
+ });
28721
28867
  }
28722
28868
  {
28723
28869
  const census = pwaCensus({
@@ -28740,15 +28886,19 @@ startAuthHealthHeartbeat();
28740
28886
  loopHist.enable();
28741
28887
  const LOG_THRESHOLD_MS = 1e3;
28742
28888
  const WINDOW_MS2 = 3e4;
28743
- const lagTimer = setInterval(() => {
28744
- const maxMs = loopHist.max / 1e6;
28745
- const p99Ms = loopHist.percentile(99) / 1e6;
28746
- loopHist.reset();
28747
- if (maxMs >= LOG_THRESHOLD_MS) {
28748
- console.error(`[event-loop] lag p99=${Math.round(p99Ms)} max=${Math.round(maxMs)} window=${WINDOW_MS2 / 1e3}s`);
28889
+ registerLoop({
28890
+ name: "event-loop-lag",
28891
+ intervalMs: WINDOW_MS2,
28892
+ run: () => {
28893
+ const maxMs = loopHist.max / 1e6;
28894
+ const p99Ms = loopHist.percentile(99) / 1e6;
28895
+ loopHist.reset();
28896
+ if (maxMs >= LOG_THRESHOLD_MS) {
28897
+ console.error(`[event-loop] lag p99=${Math.round(p99Ms)} max=${Math.round(maxMs)} window=${WINDOW_MS2 / 1e3}s`);
28898
+ }
28899
+ return `p99 ${Math.round(p99Ms)}ms, max ${Math.round(maxMs)}ms`;
28749
28900
  }
28750
- }, WINDOW_MS2);
28751
- lagTimer.unref();
28901
+ });
28752
28902
  }
28753
28903
  var SUBAPP_MANIFEST = [
28754
28904
  { prefix: "/api/health", file: "server/routes/health.ts", subapp: health_default },
@@ -28840,11 +28990,14 @@ async function runAdminUserReconcileTick() {
28840
28990
  console.error(`[adminuser-self-heal] rejected: ${err instanceof Error ? err.message : String(err)}`);
28841
28991
  }
28842
28992
  }
28843
- void runAdminUserReconcileTick();
28844
- var adminUserReconcileTimer = setInterval(() => {
28845
- void runAdminUserReconcileTick();
28846
- }, ADMINUSER_RECONCILE_INTERVAL_MS);
28847
- if (typeof adminUserReconcileTimer.unref === "function") adminUserReconcileTimer.unref();
28993
+ registerLoop({
28994
+ name: "adminuser-reconcile",
28995
+ intervalMs: ADMINUSER_RECONCILE_INTERVAL_MS,
28996
+ firstRunDelayMs: 0,
28997
+ // Returned, not void-called: the registry awaits it, so the recorded
28998
+ // duration and outcome describe the tick rather than the call that started it.
28999
+ run: () => runAdminUserReconcileTick()
29000
+ });
28848
29001
  var INGEST_AUDIT_INTERVAL_MS = 15 * 60 * 1e3;
28849
29002
  var runIngestAuditTick = () => {
28850
29003
  try {
@@ -28853,9 +29006,12 @@ var runIngestAuditTick = () => {
28853
29006
  console.error(`[wa-ingest-audit] rejected: ${err instanceof Error ? err.message : String(err)}`);
28854
29007
  }
28855
29008
  };
28856
- runIngestAuditTick();
28857
- var ingestAuditTimer = setInterval(runIngestAuditTick, INGEST_AUDIT_INTERVAL_MS);
28858
- if (typeof ingestAuditTimer.unref === "function") ingestAuditTimer.unref();
29009
+ registerLoop({
29010
+ name: "wa-ingest-audit",
29011
+ intervalMs: INGEST_AUDIT_INTERVAL_MS,
29012
+ firstRunDelayMs: 0,
29013
+ run: runIngestAuditTick
29014
+ });
28859
29015
  var USERS_RECONCILE_INTERVAL_MS = 60 * 60 * 1e3;
28860
29016
  function countUsersRows() {
28861
29017
  if (!existsSync38(USERS_FILE)) return 0;
@@ -28882,9 +29038,12 @@ function runUsersReconcileTick() {
28882
29038
  console.error(`[users-reconcile] op=error detail=${err instanceof Error ? err.message : String(err)}`);
28883
29039
  }
28884
29040
  }
28885
- runUsersReconcileTick();
28886
- var usersReconcileTimer = setInterval(runUsersReconcileTick, USERS_RECONCILE_INTERVAL_MS);
28887
- if (typeof usersReconcileTimer.unref === "function") usersReconcileTimer.unref();
29041
+ registerLoop({
29042
+ name: "users-reconcile",
29043
+ intervalMs: USERS_RECONCILE_INTERVAL_MS,
29044
+ firstRunDelayMs: 0,
29045
+ run: runUsersReconcileTick
29046
+ });
28888
29047
  startGraphHealthTimer();
28889
29048
  void migrateUploads().catch((err) => console.error(`[uploads-migrate] failed err="${err instanceof Error ? err.message : String(err)}"`)).finally(() => {
28890
29049
  void startFileWatcher().catch((err) => {
@@ -28893,9 +29052,19 @@ void migrateUploads().catch((err) => console.error(`[uploads-migrate] failed err
28893
29052
  });
28894
29053
  void sweepStaleUploadTemps().catch((err) => console.error(`[data-upload] op=sweep-failed err="${err instanceof Error ? err.message : String(err)}"`));
28895
29054
  var CHUNK_IDLE_TTL_MS = 30 * 60 * 1e3;
28896
- setInterval(() => {
28897
- void evictStaleChunkUploads(CHUNK_IDLE_TTL_MS).catch((err) => console.error(`[data-upload] op=evict-failed err="${err instanceof Error ? err.message : String(err)}"`));
28898
- }, 10 * 60 * 1e3).unref();
29055
+ registerLoop({
29056
+ name: "upload-chunk-evict",
29057
+ intervalMs: 10 * 60 * 1e3,
29058
+ // The domain-tagged line is kept and the rejection rethrown: existing
29059
+ // `[data-upload]` greps keep working AND the registry records the failure,
29060
+ // rather than swallowing it and reporting the run as healthy.
29061
+ run: () => evictStaleChunkUploads(CHUNK_IDLE_TTL_MS).then((evicted) => `evicted ${evicted}`).catch((err) => {
29062
+ console.error(
29063
+ `[data-upload] op=evict-failed err="${err instanceof Error ? err.message : String(err)}"`
29064
+ );
29065
+ throw err;
29066
+ })
29067
+ });
28899
29068
  void migrateAdminWebchatSidecars().catch((err) => console.error(`[sidecar-backfill] failed err="${err instanceof Error ? err.message : String(err)}"`));
28900
29069
  try {
28901
29070
  const accounts = enumerateValidAccountIds(getAccountsDirFromEnv());