@camstack/system 1.2.455 → 1.2.457

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 (133) hide show
  1. package/dist/addon-runner.js +96 -10
  2. package/dist/addon-runner.mjs +96 -10
  3. package/dist/{agent-https-CLWrOO3o.mjs → agent-https-CK9Z4sSR.mjs} +32 -5
  4. package/dist/{agent-https-CCvlRQKV.js → agent-https-CoyOZ3pC.js} +37 -4
  5. package/dist/auth/auth-manager.d.ts +9 -0
  6. package/dist/auth/legacy-token-tables.d.ts +16 -0
  7. package/dist/auth/user-management-internal.d.ts +9 -28
  8. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  9. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  10. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  11. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  12. package/dist/builtins/alerts/alerts.addon.js +2 -2
  13. package/dist/builtins/alerts/alerts.addon.mjs +2 -2
  14. package/dist/builtins/autotrack/index.js +1 -1
  15. package/dist/builtins/autotrack/index.mjs +1 -1
  16. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +2 -2
  17. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +2 -2
  18. package/dist/builtins/calendars/calendars.addon.js +1 -1
  19. package/dist/builtins/calendars/calendars.addon.mjs +1 -1
  20. package/dist/builtins/camera-grid/index.js +1 -1
  21. package/dist/builtins/camera-grid/index.mjs +1 -1
  22. package/dist/builtins/composer/composer.addon.js +2 -2
  23. package/dist/builtins/composer/composer.addon.mjs +2 -2
  24. package/dist/builtins/console-logging/console-destination.d.ts +1 -2
  25. package/dist/builtins/console-logging/index.js +1 -4
  26. package/dist/builtins/console-logging/index.mjs +1 -4
  27. package/dist/builtins/core-blocks/core-blocks.addon.js +2 -2
  28. package/dist/builtins/core-blocks/core-blocks.addon.mjs +2 -2
  29. package/dist/builtins/device-history/capture/capture-engine.d.ts +6 -0
  30. package/dist/builtins/device-history/device-history.addon.js +441 -83
  31. package/dist/builtins/device-history/device-history.addon.mjs +441 -83
  32. package/dist/builtins/device-history/query/history-query.d.ts +35 -9
  33. package/dist/builtins/device-history/query/point-shape.d.ts +33 -0
  34. package/dist/builtins/device-history/retention/retention.d.ts +1 -1
  35. package/dist/builtins/device-history/rollup/rollup-engine.d.ts +2 -0
  36. package/dist/builtins/device-history/rollup/rollup-math.d.ts +6 -0
  37. package/dist/builtins/device-history/store/history-store.d.ts +7 -0
  38. package/dist/builtins/device-history/store/series-registry.d.ts +26 -1
  39. package/dist/builtins/device-manager/device-manager.addon.js +1 -1
  40. package/dist/builtins/device-manager/device-manager.addon.mjs +1 -1
  41. package/dist/builtins/hub-forwarder/hub-forwarder-destination.d.ts +1 -2
  42. package/dist/builtins/hub-forwarder/index.js +1 -4
  43. package/dist/builtins/hub-forwarder/index.mjs +1 -4
  44. package/dist/builtins/keychain/index.js +4 -4
  45. package/dist/builtins/keychain/index.mjs +4 -4
  46. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  47. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  48. package/dist/builtins/local-auth/auth-schema.d.ts +4 -7
  49. package/dist/builtins/local-auth/local-auth.addon.d.ts +7 -8
  50. package/dist/builtins/local-auth/local-auth.addon.js +1086 -1329
  51. package/dist/builtins/local-auth/local-auth.addon.mjs +1085 -1326
  52. package/dist/builtins/local-auth/preferences-record-kinds.d.ts +27 -0
  53. package/dist/builtins/local-auth/record-kinds.d.ts +4 -3
  54. package/dist/builtins/local-auth/user-preferences-store.d.ts +56 -0
  55. package/dist/builtins/local-network/local-network.addon.d.ts +8 -0
  56. package/dist/builtins/local-network/local-network.addon.js +30 -5
  57. package/dist/builtins/local-network/local-network.addon.mjs +30 -5
  58. package/dist/builtins/loki-logging/index.js +323 -7
  59. package/dist/builtins/loki-logging/index.mjs +323 -7
  60. package/dist/builtins/loki-logging/loki-destination.d.ts +20 -3
  61. package/dist/builtins/loki-logging/loki-read.d.ts +51 -0
  62. package/dist/builtins/native-metrics/native-metrics.addon.js +290 -112
  63. package/dist/builtins/native-metrics/native-metrics.addon.mjs +290 -112
  64. package/dist/builtins/native-metrics/node-metrics-history-recorder.d.ts +106 -35
  65. package/dist/builtins/platform-probe/index.js +1 -1
  66. package/dist/builtins/platform-probe/index.mjs +1 -1
  67. package/dist/builtins/record-entries/index.js +1 -1
  68. package/dist/builtins/record-entries/index.mjs +1 -1
  69. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  70. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  71. package/dist/builtins/snapshot/index.js +1 -1
  72. package/dist/builtins/snapshot/index.mjs +1 -1
  73. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  74. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  75. package/dist/builtins/sqlite-storage/index.js +1 -1
  76. package/dist/builtins/sqlite-storage/index.mjs +1 -1
  77. package/dist/builtins/sqlite-storage/records/records-engine-types.d.ts +1 -0
  78. package/dist/builtins/sqlite-storage/records/records-provider.d.ts +13 -0
  79. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +52 -6
  80. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +52 -6
  81. package/dist/builtins/storage-orchestrator/location-history-recorder.d.ts +76 -0
  82. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.d.ts +9 -0
  83. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +144 -1
  84. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +144 -1
  85. package/dist/builtins/system-config/system-config.addon.js +3 -3
  86. package/dist/builtins/system-config/system-config.addon.mjs +3 -3
  87. package/dist/builtins/winston-logging/index.js +165 -3
  88. package/dist/builtins/winston-logging/index.mjs +165 -3
  89. package/dist/builtins/winston-logging/log-file-cursor.d.ts +12 -0
  90. package/dist/builtins/winston-logging/log-file-index-store.d.ts +11 -0
  91. package/dist/builtins/winston-logging/log-file-index.d.ts +77 -0
  92. package/dist/builtins/winston-logging/log-file-read-client.d.ts +44 -0
  93. package/dist/builtins/winston-logging/log-file-read-protocol.d.ts +41 -0
  94. package/dist/builtins/winston-logging/log-file-read-worker.d.ts +1 -0
  95. package/dist/builtins/winston-logging/log-file-reader.d.ts +51 -0
  96. package/dist/builtins/winston-logging/winston-destination.d.ts +20 -2
  97. package/dist/{composition-sources-CSzD5_qX.mjs → composition-sources-B_jeZDRJ.mjs} +1 -1
  98. package/dist/{composition-sources-BxUkLNSo.js → composition-sources-qKyvMAvc.js} +1 -1
  99. package/dist/{constants-DruwCkfp.js → constants-B_X9cWEK.js} +1 -1
  100. package/dist/{constants-DOlYt1t1.mjs → constants-cxKcZRMi.mjs} +1 -1
  101. package/dist/{device-ca-uFbTyY_o.mjs → device-ca-BB6nHAL5.mjs} +1 -1
  102. package/dist/{device-ca-QPBgfqVY.js → device-ca-CVK3YcR_.js} +1 -1
  103. package/dist/{dist-DjQ8C-ku.js → dist-C89nqNnn.js} +721 -154
  104. package/dist/{dist-BpSENkRJ.mjs → dist-Dhux90yB.mjs} +668 -155
  105. package/dist/file-host-worker.js +1 -1
  106. package/dist/file-host-worker.mjs +1 -1
  107. package/dist/{hub-mdns-ciao-DlgkFJVY.mjs → hub-mdns-ciao-B8olZqqd.mjs} +20 -13
  108. package/dist/{hub-mdns-ciao-Bkrvgnma.js → hub-mdns-ciao-DB1Y9agt.js} +20 -13
  109. package/dist/index.d.ts +3 -4
  110. package/dist/index.js +145 -24
  111. package/dist/index.mjs +138 -22
  112. package/dist/kernel/config-schema.d.ts +5 -0
  113. package/dist/kernel/moleculer/accessory-prune-gate.d.ts +55 -0
  114. package/dist/log-file-read-protocol-B7M3X3bT.mjs +785 -0
  115. package/dist/log-file-read-protocol-D0WcLbIV.js +817 -0
  116. package/dist/log-file-read-worker.js +64 -0
  117. package/dist/log-file-read-worker.mjs +64 -0
  118. package/dist/log-read-filter-Bi066jwU.js +134 -0
  119. package/dist/log-read-filter-D7Ev3zm-.mjs +93 -0
  120. package/dist/logging/log-manager.d.ts +10 -6
  121. package/dist/logging/log-read-filter.d.ts +43 -0
  122. package/dist/{phase6-declare-gate-DrmTtz7F.js → phase6-declare-gate-BO-_jLh_.js} +1 -1
  123. package/dist/{phase6-declare-gate-JQUqgQgq.mjs → phase6-declare-gate-x9ZCbQJP.mjs} +1 -1
  124. package/dist/{restore-archive-4OP0EYg6.js → restore-archive-4DOUR5Gu.js} +1 -1
  125. package/dist/{restore-archive-YFSJRtKy.mjs → restore-archive-DyOaPeAr.mjs} +1 -1
  126. package/dist/tls/cert-issue.d.ts +16 -1
  127. package/dist/tls/cert-manager.d.ts +6 -0
  128. package/dist/tls/index.d.ts +1 -1
  129. package/dist/{wal-maintenance-TyuOEVy1.mjs → wal-maintenance-BMiX6UIa.mjs} +0 -0
  130. package/dist/{wal-maintenance-Bl0ZKjVR.js → wal-maintenance-DVN-DiVP.js} +0 -0
  131. package/package.json +1 -1
  132. package/dist/auth/admin-token-manager.d.ts +0 -51
  133. package/dist/auth/scoped-token-manager.d.ts +0 -25
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-DjQ8C-ku.js");
6
+ const require_dist = require("../../dist-C89nqNnn.js");
7
7
  let node_fs_promises = require("node:fs/promises");
8
8
  let node_util = require("node:util");
9
9
  let node_os = require("node:os");
@@ -1025,16 +1025,107 @@ function mergeLoadSeries(input) {
1025
1025
  }
1026
1026
  //#endregion
1027
1027
  //#region src/builtins/native-metrics/node-metrics-history-recorder.ts
1028
+ /**
1029
+ * Node load history via device-history (D850, LTS P4) — hub only: the hub's
1030
+ * bus carries every node's snapshots, so the hub's writer is the cluster's.
1031
+ *
1032
+ * Per RUNNER, never per PID. A series was once scoped by process id, and a pid
1033
+ * changes on every runner restart: the hub held ~11.8k node series, most of
1034
+ * them one restart long, and no chart could follow a runner across a respawn.
1035
+ * The scope is now the fold's own function key (`load-series-fold.ts`): the
1036
+ * runner's addon id, or `__root__` / `__unattributed__`, so a runner keeps ONE
1037
+ * series for life and the cold tier folds exactly like the hot ring. The old
1038
+ * `process-*` keys are no longer written; their rows age out by retention.
1039
+ *
1040
+ * Node TOTALS ride the resources snapshot: CPU %, memory %, CPU temperature
1041
+ * and, when the node has one, GPU utilisation and temperature. A reading the
1042
+ * node did not take (`null`) is no point — never 0 (D393).
1043
+ *
1044
+ * The in-memory rings still serve the last hour for charts.
1045
+ */
1028
1046
  var ADDON_ID = "metrics-native";
1029
- var NODE_PROCESS_CPU_KEY = `addon:${ADDON_ID}/process-cpu-percent`;
1030
- var NODE_PROCESS_RSS_KEY = `addon:${ADDON_ID}/process-rss-mib`;
1031
- var NODE_CONTAINER_CURRENT_KEY = `addon:${ADDON_ID}/container-current-mib`;
1047
+ var MIB = 1048576;
1048
+ function keyOf(name) {
1049
+ return `addon:${ADDON_ID}/${name}`;
1050
+ }
1051
+ /** Per-runner series (scope = the fold key). */
1052
+ var RUNNER_CPU_MAIN_KEY = keyOf("runner-cpu-main-percent");
1053
+ var RUNNER_CPU_GC_KEY = keyOf("runner-cpu-gc-percent");
1054
+ var RUNNER_CPU_LIFETIME_KEY = keyOf("runner-cpu-lifetime-percent");
1055
+ var RUNNER_RSS_KEY = keyOf("runner-rss-mib");
1056
+ var RUNNER_PROCESSES_KEY = keyOf("runner-processes");
1057
+ /** Node totals (scope ''). */
1058
+ var NODE_CPU_KEY = keyOf("node-cpu-percent");
1059
+ var NODE_MEMORY_KEY = keyOf("node-memory-percent");
1060
+ var NODE_CPU_TEMPERATURE_KEY = keyOf("node-cpu-temperature");
1061
+ var NODE_GPU_KEY = keyOf("node-gpu-percent");
1062
+ var NODE_GPU_TEMPERATURE_KEY = keyOf("node-gpu-temperature");
1063
+ var NODE_CONTAINER_CURRENT_KEY = keyOf("container-current-mib");
1064
+ keyOf("process-cpu-percent");
1065
+ keyOf("process-rss-mib");
1066
+ var DECLARATIONS = [
1067
+ {
1068
+ name: "runner-cpu-main-percent",
1069
+ displayName: "Runner CPU % (main thread)",
1070
+ unit: "%"
1071
+ },
1072
+ {
1073
+ name: "runner-cpu-gc-percent",
1074
+ displayName: "Runner CPU % (GC)",
1075
+ unit: "%"
1076
+ },
1077
+ {
1078
+ name: "runner-cpu-lifetime-percent",
1079
+ displayName: "Runner CPU % (lifetime average)",
1080
+ unit: "%"
1081
+ },
1082
+ {
1083
+ name: "runner-rss-mib",
1084
+ displayName: "Runner RSS (MiB)",
1085
+ unit: "MiB"
1086
+ },
1087
+ {
1088
+ name: "runner-processes",
1089
+ displayName: "Runner processes",
1090
+ unit: "count"
1091
+ },
1092
+ {
1093
+ name: "node-cpu-percent",
1094
+ displayName: "Node CPU %",
1095
+ unit: "%"
1096
+ },
1097
+ {
1098
+ name: "node-memory-percent",
1099
+ displayName: "Node memory %",
1100
+ unit: "%"
1101
+ },
1102
+ {
1103
+ name: "node-cpu-temperature",
1104
+ displayName: "Node CPU temperature",
1105
+ unit: "°C"
1106
+ },
1107
+ {
1108
+ name: "node-gpu-percent",
1109
+ displayName: "Node GPU %",
1110
+ unit: "%"
1111
+ },
1112
+ {
1113
+ name: "node-gpu-temperature",
1114
+ displayName: "Node GPU temperature",
1115
+ unit: "°C"
1116
+ },
1117
+ {
1118
+ name: "container-current-mib",
1119
+ displayName: "Container memory current (MiB)",
1120
+ unit: "MiB"
1121
+ }
1122
+ ];
1032
1123
  function nodeMetricsHistoryClient(api) {
1033
1124
  return {
1034
1125
  declareSeries: (input) => api.declareSeries.mutate(input),
1035
1126
  record: (input) => api.record.mutate({ points: [...input.points] }),
1036
1127
  queryNode: async (input) => {
1037
- return { series: (await api.queryNode.query(input)).series.map((s) => ({
1128
+ return { series: (await api.queryNode.query({ ...input })).series.map((s) => ({
1038
1129
  scope: s.scope,
1039
1130
  points: s.points.map((p) => ({
1040
1131
  t: p.t,
@@ -1050,78 +1141,112 @@ function refusalCounts(result) {
1050
1141
  for (const r of result.refused) counts[r.reason] = (counts[r.reason] ?? 0) + r.count;
1051
1142
  return counts;
1052
1143
  }
1144
+ /** The fold's function key of a process: the same rule as `foldSnapshotByFunction`. */
1145
+ function runnerScopeOf(process) {
1146
+ if (process.addonId !== null) return process.addonId;
1147
+ return process.classification === "root" ? require_dist.ROOT_BUCKET_KEY : require_dist.UNATTRIBUTED_BUCKET_KEY;
1148
+ }
1149
+ function round1(value) {
1150
+ return Math.round(value * 10) / 10;
1151
+ }
1053
1152
  var NodeMetricsHistoryRecorder = class {
1054
1153
  deps;
1055
1154
  declared = false;
1155
+ lastTotalsAt = /* @__PURE__ */ new Map();
1156
+ /** A not-ready skip was logged and device-history has not been ready since. */
1157
+ waitLogged = false;
1056
1158
  constructor(deps) {
1057
1159
  this.deps = deps;
1058
1160
  }
1059
1161
  async declare() {
1060
1162
  if (this.declared) return;
1061
- const { deviceHistory } = this.deps;
1062
- await deviceHistory.declareSeries({
1063
- name: "process-cpu-percent",
1163
+ for (const d of DECLARATIONS) await this.deps.deviceHistory.declareSeries({
1164
+ name: d.name,
1064
1165
  subjectKind: "node",
1065
1166
  kind: "gauge",
1066
- displayName: "Process CPU %",
1167
+ displayName: d.displayName,
1067
1168
  ingest: "points",
1068
- unit: "%"
1069
- });
1070
- await deviceHistory.declareSeries({
1071
- name: "process-rss-mib",
1072
- subjectKind: "node",
1073
- kind: "gauge",
1074
- displayName: "Process RSS (MiB)",
1075
- ingest: "points",
1076
- unit: "MiB"
1077
- });
1078
- await deviceHistory.declareSeries({
1079
- name: "container-current-mib",
1080
- subjectKind: "node",
1081
- kind: "gauge",
1082
- displayName: "Container memory current (MiB)",
1083
- ingest: "points",
1084
- unit: "MiB"
1169
+ unit: d.unit
1085
1170
  });
1086
1171
  this.declared = true;
1087
1172
  }
1173
+ /** One process snapshot: per-runner sums plus the container's memory. */
1088
1174
  async recordSample(nodeId, atMs, processes, containerMemory) {
1089
- if (!await this.ensureDeclared()) return;
1090
- const points = [];
1175
+ const subject = {
1176
+ kind: "node",
1177
+ nodeId
1178
+ };
1179
+ const point = (key, scope, value) => ({
1180
+ key,
1181
+ subject,
1182
+ scope,
1183
+ ts: atMs,
1184
+ value
1185
+ });
1186
+ const byRunner = /* @__PURE__ */ new Map();
1091
1187
  for (const p of processes) {
1092
- const scope = String(p.pid);
1093
- points.push({
1094
- key: NODE_PROCESS_CPU_KEY,
1095
- subject: {
1096
- kind: "node",
1097
- nodeId
1098
- },
1099
- scope,
1100
- ts: atMs,
1101
- value: p.cpuPercent
1188
+ const scope = runnerScopeOf(p);
1189
+ const t = byRunner.get(scope) ?? {
1190
+ main: 0,
1191
+ gc: 0,
1192
+ splitKnown: true,
1193
+ lifetime: 0,
1194
+ rss: 0,
1195
+ count: 0
1196
+ };
1197
+ const known = p.cpuMainPercent !== null && p.cpuGcPercent !== null;
1198
+ byRunner.set(scope, {
1199
+ main: t.main + (p.cpuMainPercent ?? 0),
1200
+ gc: t.gc + (p.cpuGcPercent ?? 0),
1201
+ splitKnown: t.splitKnown && known,
1202
+ lifetime: t.lifetime + p.cpuPercent,
1203
+ rss: t.rss + p.memoryRssBytes,
1204
+ count: t.count + 1
1102
1205
  });
1206
+ }
1207
+ const points = [];
1208
+ for (const [scope, t] of byRunner) {
1209
+ if (t.splitKnown) {
1210
+ points.push(point(RUNNER_CPU_MAIN_KEY, scope, round1(t.main)));
1211
+ points.push(point(RUNNER_CPU_GC_KEY, scope, round1(t.gc)));
1212
+ }
1213
+ points.push(point(RUNNER_CPU_LIFETIME_KEY, scope, round1(t.lifetime)));
1214
+ points.push(point(RUNNER_RSS_KEY, scope, Math.round(t.rss / MIB)));
1215
+ points.push(point(RUNNER_PROCESSES_KEY, scope, t.count));
1216
+ }
1217
+ if (containerMemory !== null) points.push(point(NODE_CONTAINER_CURRENT_KEY, "", Math.round(containerMemory.currentBytes / MIB)));
1218
+ await this.write(nodeId, atMs, points);
1219
+ }
1220
+ /** One resources snapshot: the node's totals, at most every {@link NODE_TOTALS_MIN_INTERVAL_MS}. */
1221
+ async recordTotals(nodeId, atMs, snapshot) {
1222
+ const last = this.lastTotalsAt.get(nodeId);
1223
+ if (last !== void 0 && atMs >= last && atMs - last < 15e3) return;
1224
+ this.lastTotalsAt.set(nodeId, atMs);
1225
+ const subject = {
1226
+ kind: "node",
1227
+ nodeId
1228
+ };
1229
+ const points = [];
1230
+ const add = (key, value) => {
1231
+ if (value === null || !Number.isFinite(value)) return;
1103
1232
  points.push({
1104
- key: NODE_PROCESS_RSS_KEY,
1105
- subject: {
1106
- kind: "node",
1107
- nodeId
1108
- },
1109
- scope,
1233
+ key,
1234
+ subject,
1235
+ scope: "",
1110
1236
  ts: atMs,
1111
- value: Math.round(p.memoryRssBytes / 1048576)
1237
+ value: round1(value)
1112
1238
  });
1113
- }
1114
- if (containerMemory !== null) points.push({
1115
- key: NODE_CONTAINER_CURRENT_KEY,
1116
- subject: {
1117
- kind: "node",
1118
- nodeId
1119
- },
1120
- scope: "",
1121
- ts: atMs,
1122
- value: Math.round(containerMemory.currentBytes / 1048576)
1123
- });
1239
+ };
1240
+ add(NODE_CPU_KEY, snapshot.cpu.total);
1241
+ add(NODE_MEMORY_KEY, snapshot.memory.percent);
1242
+ add(NODE_CPU_TEMPERATURE_KEY, snapshot.cpuTemperature);
1243
+ add(NODE_GPU_KEY, snapshot.gpu?.utilization ?? null);
1244
+ add(NODE_GPU_TEMPERATURE_KEY, snapshot.gpu?.temperature ?? null);
1245
+ await this.write(nodeId, atMs, points);
1246
+ }
1247
+ async write(nodeId, atMs, points) {
1124
1248
  if (points.length === 0) return;
1249
+ if (!await this.ensureDeclared()) return;
1125
1250
  try {
1126
1251
  const result = await this.deps.deviceHistory.record({ points });
1127
1252
  if (result.refused.length > 0) {
@@ -1143,7 +1268,7 @@ var NodeMetricsHistoryRecorder = class {
1143
1268
  async readContainerPoints(nodeId, sinceMs, maxPoints) {
1144
1269
  if (!await this.ensureDeclared()) return [];
1145
1270
  const result = await this.deps.deviceHistory.queryNode({
1146
- nodeId,
1271
+ forNodeId: nodeId,
1147
1272
  key: NODE_CONTAINER_CURRENT_KEY,
1148
1273
  from: sinceMs + 1,
1149
1274
  to: Date.now(),
@@ -1156,7 +1281,7 @@ var NodeMetricsHistoryRecorder = class {
1156
1281
  out.push({
1157
1282
  atMs: p.t,
1158
1283
  source: "cgroup-v2",
1159
- currentBytes: p.value * 1048576,
1284
+ currentBytes: p.value * MIB,
1160
1285
  limitBytes: null,
1161
1286
  anonBytes: null,
1162
1287
  fileBytes: null,
@@ -1167,63 +1292,46 @@ var NodeMetricsHistoryRecorder = class {
1167
1292
  }
1168
1293
  return out.toSorted((a, b) => a.atMs - b.atMs);
1169
1294
  }
1295
+ /**
1296
+ * The cold tier as process samples the fold reads like the hot ring's. A
1297
+ * runner's sums come back as `processes` equal rows (sums preserved, count
1298
+ * right); its CPU split is `null` where it was not recorded.
1299
+ */
1170
1300
  async readProcessSamples(nodeId, sinceMs, maxPoints) {
1171
1301
  if (!await this.ensureDeclared()) return [];
1172
1302
  const to = Date.now();
1173
- const [cpuResult, rssResult] = await Promise.all([this.deps.deviceHistory.queryNode({
1174
- nodeId,
1175
- key: NODE_PROCESS_CPU_KEY,
1303
+ const read = (key) => this.deps.deviceHistory.queryNode({
1304
+ forNodeId: nodeId,
1305
+ key,
1176
1306
  from: sinceMs + 1,
1177
1307
  to,
1178
1308
  resolution: "raw",
1179
1309
  maxPoints
1180
- }), this.deps.deviceHistory.queryNode({
1181
- nodeId,
1182
- key: NODE_PROCESS_RSS_KEY,
1183
- from: sinceMs + 1,
1184
- to,
1185
- resolution: "raw",
1186
- maxPoints
1187
- })]);
1188
- const byAt = /* @__PURE__ */ new Map();
1189
- const ingest = (series, field) => {
1190
- for (const s of series) {
1191
- const pid = Number(s.scope);
1192
- if (!Number.isFinite(pid)) continue;
1193
- for (const p of s.points) {
1194
- if (p.value === null || !Number.isFinite(p.value)) continue;
1195
- let perPid = byAt.get(p.t);
1196
- if (perPid === void 0) {
1197
- perPid = /* @__PURE__ */ new Map();
1198
- byAt.set(p.t, perPid);
1199
- }
1200
- const base = perPid.get(pid) ?? {
1201
- pid,
1202
- addonId: null,
1203
- classification: "managed",
1204
- cpuPercent: 0,
1205
- memoryRssBytes: 0,
1206
- cpuMainPercent: null,
1207
- cpuGcPercent: null
1208
- };
1209
- perPid.set(pid, field === "cpuPercent" ? {
1210
- ...base,
1211
- cpuPercent: p.value
1212
- } : {
1213
- ...base,
1214
- memoryRssBytes: p.value * 1048576
1215
- });
1216
- }
1217
- }
1218
- };
1219
- ingest(cpuResult.series, "cpuPercent");
1220
- ingest(rssResult.series, "memoryRssBytes");
1221
- return [...byAt.entries()].toSorted((a, b) => a[0] - b[0]).map(([atMs, perPid]) => ({
1222
- atMs,
1223
- processes: [...perPid.values()]
1224
- }));
1310
+ });
1311
+ const [main, gc, lifetime, rss, count] = await Promise.all([
1312
+ read(RUNNER_CPU_MAIN_KEY),
1313
+ read(RUNNER_CPU_GC_KEY),
1314
+ read(RUNNER_CPU_LIFETIME_KEY),
1315
+ read(RUNNER_RSS_KEY),
1316
+ read(RUNNER_PROCESSES_KEY)
1317
+ ]);
1318
+ return assembleRunnerSamples({
1319
+ main,
1320
+ gc,
1321
+ lifetime,
1322
+ rss,
1323
+ count
1324
+ });
1225
1325
  }
1226
1326
  async ensureDeclared() {
1327
+ if (this.deps.historyReady !== void 0 && !this.deps.historyReady()) {
1328
+ if (!this.waitLogged) {
1329
+ this.waitLogged = true;
1330
+ this.deps.logger.info("node metrics history waits for device-history — samples until it is ready are not stored");
1331
+ }
1332
+ return false;
1333
+ }
1334
+ this.waitLogged = false;
1227
1335
  try {
1228
1336
  await this.declare();
1229
1337
  return true;
@@ -1233,6 +1341,55 @@ var NodeMetricsHistoryRecorder = class {
1233
1341
  }
1234
1342
  }
1235
1343
  };
1344
+ /** `atMs -> scope -> value` of one key's answer, finite values only. */
1345
+ function indexAnswer(answer) {
1346
+ const out = /* @__PURE__ */ new Map();
1347
+ for (const s of answer.series) for (const p of s.points) {
1348
+ if (p.value === null || !Number.isFinite(p.value)) continue;
1349
+ const at = out.get(p.t) ?? /* @__PURE__ */ new Map();
1350
+ at.set(s.scope, p.value);
1351
+ out.set(p.t, at);
1352
+ }
1353
+ return out;
1354
+ }
1355
+ /**
1356
+ * Per-runner cold points back into process rows. A sample exists where the
1357
+ * runner's lifetime CPU and RSS were both recorded (always written together).
1358
+ */
1359
+ function assembleRunnerSamples(answers) {
1360
+ const main = indexAnswer(answers.main);
1361
+ const gc = indexAnswer(answers.gc);
1362
+ const rss = indexAnswer(answers.rss);
1363
+ const count = indexAnswer(answers.count);
1364
+ const samples = [];
1365
+ for (const [atMs, lifetimeByScope] of indexAnswer(answers.lifetime)) {
1366
+ const processes = [];
1367
+ for (const [scope, lifetime] of lifetimeByScope) {
1368
+ const rssMib = rss.get(atMs)?.get(scope);
1369
+ if (rssMib === void 0) continue;
1370
+ const n = Math.max(1, Math.round(count.get(atMs)?.get(scope) ?? 1));
1371
+ const m = main.get(atMs)?.get(scope);
1372
+ const g = gc.get(atMs)?.get(scope);
1373
+ const split = m !== void 0 && g !== void 0;
1374
+ const isRoot = scope === require_dist.ROOT_BUCKET_KEY;
1375
+ const isUnattributed = scope === require_dist.UNATTRIBUTED_BUCKET_KEY;
1376
+ for (let i = 0; i < n; i += 1) processes.push({
1377
+ pid: 0,
1378
+ addonId: isRoot || isUnattributed ? null : scope,
1379
+ classification: isRoot ? "root" : isUnattributed ? "system" : "managed",
1380
+ cpuPercent: lifetime / n,
1381
+ memoryRssBytes: rssMib * MIB / n,
1382
+ cpuMainPercent: split ? m / n : null,
1383
+ cpuGcPercent: split ? g / n : null
1384
+ });
1385
+ }
1386
+ if (processes.length > 0) samples.push({
1387
+ atMs,
1388
+ processes
1389
+ });
1390
+ }
1391
+ return samples.toSorted((a, b) => a.atMs - b.atMs);
1392
+ }
1236
1393
  //#endregion
1237
1394
  //#region src/builtins/native-metrics/gpu-probe.ts
1238
1395
  /**
@@ -2196,6 +2353,8 @@ var ThreadCpuTracker = class {
2196
2353
  };
2197
2354
  //#endregion
2198
2355
  //#region src/builtins/native-metrics/native-metrics.addon.ts
2356
+ /** The hub's node id — `isHub` below is the same test; device-history lives there. */
2357
+ var HUB_NODE_ID = "hub";
2199
2358
  /**
2200
2359
  * Cadence for the per-node metric snapshot bus events. ~5 s matches
2201
2360
  * the existing UI polling cadence (ProcessesTab, ProcessResources)
@@ -2317,7 +2476,7 @@ var NativeMetricsAddon = class NativeMetricsAddon extends require_dist.BaseAddon
2317
2476
  }
2318
2477
  /** Is this the hub? The same test every other addon uses (`addon-ai`). */
2319
2478
  get isHub() {
2320
- return (this.ctx.kernel.cluster?.broker?.nodeID ?? "hub") === "hub";
2479
+ return (this.ctx.kernel.cluster?.broker?.nodeID ?? HUB_NODE_ID) === HUB_NODE_ID;
2321
2480
  }
2322
2481
  /** Process rows observed across the whole fleet, for the cost projection. */
2323
2482
  observedFleetRows() {
@@ -2368,19 +2527,38 @@ var NativeMetricsAddon = class NativeMetricsAddon extends require_dist.BaseAddon
2368
2527
  };
2369
2528
  this.applyLoadSeriesConfig();
2370
2529
  if (this.isHub) {
2371
- this.metricsHistory = new NodeMetricsHistoryRecorder({
2530
+ const historyScope = {
2531
+ type: "node",
2532
+ nodeId: HUB_NODE_ID
2533
+ };
2534
+ const historyHandle = this.ctx.useCapability("device-history", historyScope);
2535
+ const recorder = new NodeMetricsHistoryRecorder({
2372
2536
  deviceHistory: nodeMetricsHistoryClient(this.ctx.api.deviceHistory),
2373
- logger: this.ctx.logger.child("NodeMetricsHistory")
2374
- });
2375
- this.metricsHistory.declare().catch((err) => {
2376
- this.ctx.logger.warn("node metrics history declare failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
2537
+ logger: this.ctx.logger.child("NodeMetricsHistory"),
2538
+ historyReady: () => historyHandle.isReady
2377
2539
  });
2540
+ this.metricsHistory = recorder;
2541
+ const declareNow = () => {
2542
+ recorder.declare().catch((err) => {
2543
+ this.ctx.logger.warn("node metrics history declare failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
2544
+ });
2545
+ };
2546
+ this.ctx.addDisposer(this.ctx.onCapabilityStateChange("device-history", historyScope, (state) => {
2547
+ if (state === "ready") declareNow();
2548
+ }));
2549
+ if (historyHandle.isReady) declareNow();
2378
2550
  }
2379
2551
  this.memoryProbe = new ContainerMemoryProbe({ logger: this.ctx.logger.child("ContainerMemory") });
2380
2552
  this.pidsProbe = new ContainerPidsProbe({ logger: this.ctx.logger.child("ContainerPids") });
2381
2553
  if (this.snapshotTimer) clearInterval(this.snapshotTimer);
2382
2554
  this.snapshotTimer = setInterval(() => this.runTick("resources snapshot", () => this.emitResourcesSnapshot()), METRICS_SNAPSHOT_INTERVAL_MS);
2383
2555
  this.startProcessSnapshotTimer();
2556
+ if (this.metricsHistory !== null) {
2557
+ const history = this.metricsHistory;
2558
+ this.ctx.addDisposer(this.ctx.eventBus.subscribe({ category: require_dist.EventCategory.MetricsNodeResourcesSnapshot }, (event) => {
2559
+ history.recordTotals(event.data.nodeId, event.data.timestamp, event.data.snapshot);
2560
+ }));
2561
+ }
2384
2562
  this.ctx.addDisposer(this.ctx.eventBus.subscribe({ category: require_dist.EventCategory.MetricsNodeProcessesSnapshot }, (event) => this.retainSnapshot(event.data.nodeId, event.data.timestamp, event.data.processes, event.data.containerMemory)));
2385
2563
  return [{
2386
2564
  capability: require_dist.metricsProviderCapability,