@camstack/core 0.1.14 → 0.1.15

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 (161) hide show
  1. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +220 -0
  2. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js.map +1 -0
  3. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +9 -0
  4. package/dist/builtins/addon-pages-aggregator/index.js +222 -0
  5. package/dist/builtins/addon-pages-aggregator/index.js.map +1 -0
  6. package/dist/builtins/addon-pages-aggregator/index.mjs +9 -0
  7. package/dist/builtins/addon-pages-aggregator/index.mjs.map +1 -0
  8. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +200 -0
  9. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js.map +1 -0
  10. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +9 -0
  11. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs.map +1 -0
  12. package/dist/builtins/addon-widgets-aggregator/index.js +202 -0
  13. package/dist/builtins/addon-widgets-aggregator/index.js.map +1 -0
  14. package/dist/builtins/addon-widgets-aggregator/index.mjs +9 -0
  15. package/dist/builtins/addon-widgets-aggregator/index.mjs.map +1 -0
  16. package/dist/builtins/alerts/alerts.addon.js +443 -0
  17. package/dist/builtins/alerts/alerts.addon.js.map +1 -0
  18. package/dist/builtins/alerts/alerts.addon.mjs +9 -0
  19. package/dist/builtins/alerts/alerts.addon.mjs.map +1 -0
  20. package/dist/builtins/alerts/index.js +443 -0
  21. package/dist/builtins/alerts/index.js.map +1 -0
  22. package/dist/builtins/alerts/index.mjs +8 -0
  23. package/dist/builtins/alerts/index.mjs.map +1 -0
  24. package/dist/builtins/console-logging/index.js +242 -0
  25. package/dist/builtins/console-logging/index.js.map +1 -0
  26. package/dist/builtins/console-logging/index.mjs +11 -0
  27. package/dist/builtins/console-logging/index.mjs.map +1 -0
  28. package/dist/builtins/device-manager/device-manager.addon.js +2155 -0
  29. package/dist/builtins/device-manager/device-manager.addon.js.map +1 -0
  30. package/dist/builtins/device-manager/device-manager.addon.mjs +9 -0
  31. package/dist/builtins/device-manager/device-manager.addon.mjs.map +1 -0
  32. package/dist/builtins/device-manager/index.js +2157 -0
  33. package/dist/builtins/device-manager/index.js.map +1 -0
  34. package/dist/builtins/device-manager/index.mjs +10 -0
  35. package/dist/builtins/device-manager/index.mjs.map +1 -0
  36. package/dist/builtins/hub-forwarder/index.js +297 -0
  37. package/dist/builtins/hub-forwarder/index.js.map +1 -0
  38. package/dist/builtins/hub-forwarder/index.mjs +11 -0
  39. package/dist/builtins/hub-forwarder/index.mjs.map +1 -0
  40. package/dist/builtins/local-auth/index.js +623 -0
  41. package/dist/builtins/local-auth/index.js.map +1 -0
  42. package/dist/builtins/local-auth/index.mjs +8 -0
  43. package/dist/builtins/local-auth/index.mjs.map +1 -0
  44. package/dist/builtins/local-auth/local-auth.addon.js +623 -0
  45. package/dist/builtins/local-auth/local-auth.addon.js.map +1 -0
  46. package/dist/builtins/local-auth/local-auth.addon.mjs +9 -0
  47. package/dist/builtins/local-auth/local-auth.addon.mjs.map +1 -0
  48. package/dist/builtins/local-backup/index.js +53 -68
  49. package/dist/builtins/local-backup/index.js.map +1 -1
  50. package/dist/builtins/local-backup/index.mjs +1 -1
  51. package/dist/builtins/native-metrics/native-metrics.addon.js +898 -0
  52. package/dist/builtins/native-metrics/native-metrics.addon.js.map +1 -0
  53. package/dist/builtins/native-metrics/native-metrics.addon.mjs +7 -0
  54. package/dist/builtins/native-metrics/native-metrics.addon.mjs.map +1 -0
  55. package/dist/builtins/snapshot/index.js +504 -0
  56. package/dist/builtins/snapshot/index.js.map +1 -0
  57. package/dist/builtins/snapshot/index.mjs +477 -0
  58. package/dist/builtins/snapshot/index.mjs.map +1 -0
  59. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +16 -166
  60. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js.map +1 -1
  61. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  62. package/dist/builtins/sqlite-storage/index.js +554 -621
  63. package/dist/builtins/sqlite-storage/index.js.map +1 -1
  64. package/dist/builtins/sqlite-storage/index.mjs +9 -11
  65. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +368 -130
  66. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js.map +1 -1
  67. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +1 -1
  68. package/dist/builtins/system-config/index.js +189 -0
  69. package/dist/builtins/system-config/index.js.map +1 -0
  70. package/dist/builtins/system-config/index.mjs +10 -0
  71. package/dist/builtins/system-config/index.mjs.map +1 -0
  72. package/dist/builtins/system-config/system-config.addon.js +187 -0
  73. package/dist/builtins/system-config/system-config.addon.js.map +1 -0
  74. package/dist/builtins/system-config/system-config.addon.mjs +9 -0
  75. package/dist/builtins/system-config/system-config.addon.mjs.map +1 -0
  76. package/dist/builtins/winston-logging/index.js +185 -65
  77. package/dist/builtins/winston-logging/index.js.map +1 -1
  78. package/dist/builtins/winston-logging/index.mjs +2 -1
  79. package/dist/chunk-2CIYKDRN.mjs +1 -0
  80. package/dist/chunk-2CIYKDRN.mjs.map +1 -0
  81. package/dist/chunk-2F76X6NL.mjs +136 -0
  82. package/dist/chunk-2F76X6NL.mjs.map +1 -0
  83. package/dist/chunk-2QUFBZ7M.mjs +1 -0
  84. package/dist/chunk-2QUFBZ7M.mjs.map +1 -0
  85. package/dist/chunk-3BK2Y7GY.mjs +593 -0
  86. package/dist/chunk-3BK2Y7GY.mjs.map +1 -0
  87. package/dist/chunk-4OOHFJHT.mjs +421 -0
  88. package/dist/chunk-4OOHFJHT.mjs.map +1 -0
  89. package/dist/chunk-4XHB7IHT.mjs +809 -0
  90. package/dist/chunk-4XHB7IHT.mjs.map +1 -0
  91. package/dist/{chunk-2F3XZYRW.mjs → chunk-6M2HSSTQ.mjs} +16 -7
  92. package/dist/chunk-6M2HSSTQ.mjs.map +1 -0
  93. package/dist/{chunk-SO4LROOT.mjs → chunk-7FI7SQS7.mjs} +54 -69
  94. package/dist/chunk-7FI7SQS7.mjs.map +1 -0
  95. package/dist/chunk-ED57RCQE.mjs +171 -0
  96. package/dist/chunk-ED57RCQE.mjs.map +1 -0
  97. package/dist/chunk-FZN56HGQ.mjs +626 -0
  98. package/dist/chunk-FZN56HGQ.mjs.map +1 -0
  99. package/dist/chunk-GL4OOB25.mjs +51 -0
  100. package/dist/chunk-GL4OOB25.mjs.map +1 -0
  101. package/dist/chunk-KDG2NTDB.mjs +137 -0
  102. package/dist/chunk-KDG2NTDB.mjs.map +1 -0
  103. package/dist/chunk-NRBQWBDM.mjs +191 -0
  104. package/dist/chunk-NRBQWBDM.mjs.map +1 -0
  105. package/dist/chunk-O4V246GG.mjs +2137 -0
  106. package/dist/chunk-O4V246GG.mjs.map +1 -0
  107. package/dist/chunk-QT57H266.mjs +163 -0
  108. package/dist/chunk-QT57H266.mjs.map +1 -0
  109. package/dist/chunk-QX4RH25I.mjs +141 -0
  110. package/dist/chunk-QX4RH25I.mjs.map +1 -0
  111. package/dist/chunk-TB562PZX.mjs +86 -0
  112. package/dist/chunk-TB562PZX.mjs.map +1 -0
  113. package/dist/chunk-TDYPZXK5.mjs +1 -0
  114. package/dist/chunk-TDYPZXK5.mjs.map +1 -0
  115. package/dist/chunk-UJI4LN5P.mjs +36 -0
  116. package/dist/chunk-UJI4LN5P.mjs.map +1 -0
  117. package/dist/chunk-W6RTHQGP.mjs +1 -0
  118. package/dist/chunk-W6RTHQGP.mjs.map +1 -0
  119. package/dist/chunk-ZELBCPDC.mjs +369 -0
  120. package/dist/chunk-ZELBCPDC.mjs.map +1 -0
  121. package/dist/index.d.mts +1103 -544
  122. package/dist/index.d.ts +1103 -544
  123. package/dist/index.js +7032 -6033
  124. package/dist/index.js.map +1 -1
  125. package/dist/index.mjs +568 -2226
  126. package/dist/index.mjs.map +1 -1
  127. package/dist/resource-monitor-UZUGPIAU.mjs +9 -0
  128. package/dist/resource-monitor-UZUGPIAU.mjs.map +1 -0
  129. package/dist/storage-location-manager-HFNB3PCS.mjs +7 -0
  130. package/dist/storage-location-manager-HFNB3PCS.mjs.map +1 -0
  131. package/package.json +123 -2
  132. package/dist/builtins/local-backup/index.d.mts +0 -42
  133. package/dist/builtins/local-backup/index.d.ts +0 -42
  134. package/dist/builtins/sqlite-storage/filesystem-storage.addon.d.mts +0 -2
  135. package/dist/builtins/sqlite-storage/filesystem-storage.addon.d.ts +0 -2
  136. package/dist/builtins/sqlite-storage/index.d.mts +0 -4
  137. package/dist/builtins/sqlite-storage/index.d.ts +0 -4
  138. package/dist/builtins/sqlite-storage/sqlite-settings.addon.d.mts +0 -2
  139. package/dist/builtins/sqlite-storage/sqlite-settings.addon.d.ts +0 -2
  140. package/dist/builtins/winston-logging/index.d.mts +0 -30
  141. package/dist/builtins/winston-logging/index.d.ts +0 -30
  142. package/dist/chunk-2F3XZYRW.mjs.map +0 -1
  143. package/dist/chunk-LQFPAEQF.mjs +0 -147
  144. package/dist/chunk-LQFPAEQF.mjs.map +0 -1
  145. package/dist/chunk-R3DIIBBX.mjs +0 -532
  146. package/dist/chunk-R3DIIBBX.mjs.map +0 -1
  147. package/dist/chunk-SMNR44VG.mjs +0 -386
  148. package/dist/chunk-SMNR44VG.mjs.map +0 -1
  149. package/dist/chunk-SO4LROOT.mjs.map +0 -1
  150. package/dist/chunk-SPA4JBKN.mjs +0 -175
  151. package/dist/chunk-SPA4JBKN.mjs.map +0 -1
  152. package/dist/dist-3BY63UQ5.mjs +0 -2151
  153. package/dist/dist-3BY63UQ5.mjs.map +0 -1
  154. package/dist/filesystem-storage.addon-C42r589X.d.mts +0 -57
  155. package/dist/filesystem-storage.addon-C42r589X.d.ts +0 -57
  156. package/dist/sql-schema-CKz78rId.d.mts +0 -97
  157. package/dist/sql-schema-CKz78rId.d.ts +0 -97
  158. package/dist/sqlite-settings.addon-KwG-uKMP.d.mts +0 -79
  159. package/dist/sqlite-settings.addon-KwG-uKMP.d.ts +0 -79
  160. package/dist/storage-location-manager-KKDQNAKA.mjs +0 -7
  161. /package/dist/{storage-location-manager-KKDQNAKA.mjs.map → builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs.map} +0 -0
@@ -0,0 +1,809 @@
1
+ // src/builtins/native-metrics/native-metrics.addon.ts
2
+ import { BaseAddon, EventCategory, createEvent, metricsProviderCapability } from "@camstack/types";
3
+ import { execFile as execFile2 } from "child_process";
4
+ import { promisify } from "util";
5
+
6
+ // src/builtins/native-metrics/native-metrics-provider.ts
7
+ import * as os from "os";
8
+ import * as fs from "fs";
9
+ import { execFile, execFileSync } from "child_process";
10
+ var IS_DARWIN = os.platform() === "darwin";
11
+ var IS_LINUX = os.platform() === "linux";
12
+ var NativeMetricsProvider = class {
13
+ id = "native";
14
+ cachedSnapshot = null;
15
+ samplingTimer = null;
16
+ macMemTimer = null;
17
+ prevCpu;
18
+ diagnostics = null;
19
+ consecutiveSampleErrors = 0;
20
+ constructor() {
21
+ this.prevCpu = aggregateCpuTimes();
22
+ }
23
+ /**
24
+ * Optional diagnostics sink for sampling errors. The first failure and
25
+ * every Nth failure thereafter are reported so a permanently-broken
26
+ * collector doesn't spam logs but is still observable.
27
+ */
28
+ setDiagnostics(diag) {
29
+ this.diagnostics = diag;
30
+ }
31
+ // ─── Lifecycle (not on the cap contract) ────────────────────────────
32
+ startSampling(intervalMs) {
33
+ if (IS_DARWIN) {
34
+ sampleMacMemory(this);
35
+ this.macMemTimer = setInterval(() => sampleMacMemory(this), 5e3);
36
+ if (this.macMemTimer.unref) this.macMemTimer.unref();
37
+ }
38
+ this.collectSnapshot().then((s) => {
39
+ this.cachedSnapshot = s;
40
+ this.consecutiveSampleErrors = 0;
41
+ }).catch((err) => this.reportSampleError(err));
42
+ this.samplingTimer = setInterval(() => {
43
+ this.collectSnapshot().then((s) => {
44
+ this.cachedSnapshot = s;
45
+ this.consecutiveSampleErrors = 0;
46
+ }).catch((err) => this.reportSampleError(err));
47
+ }, intervalMs);
48
+ if (this.samplingTimer.unref) this.samplingTimer.unref();
49
+ }
50
+ reportSampleError(err) {
51
+ this.consecutiveSampleErrors++;
52
+ if (!this.diagnostics) return;
53
+ const shouldLog = this.consecutiveSampleErrors === 1 || this.consecutiveSampleErrors % 60 === 0;
54
+ if (!shouldLog) return;
55
+ this.diagnostics.warn("metrics-provider sample failed", {
56
+ consecutiveErrors: this.consecutiveSampleErrors,
57
+ error: err instanceof Error ? err.message : String(err)
58
+ });
59
+ }
60
+ stopSampling() {
61
+ if (this.samplingTimer) {
62
+ clearInterval(this.samplingTimer);
63
+ this.samplingTimer = null;
64
+ }
65
+ if (this.macMemTimer) {
66
+ clearInterval(this.macMemTimer);
67
+ this.macMemTimer = null;
68
+ }
69
+ }
70
+ // ─── Cap contract — async methods ───────────────────────────────────
71
+ async collectSnapshot() {
72
+ const cpu = this.sampleCpu();
73
+ const memory = this.getMemoryInfo();
74
+ const processInfo = getProcessResources();
75
+ const [gpu, network, disk, cpuTemp] = await Promise.all([
76
+ getGpuInfoInternal().catch(() => null),
77
+ takeNetworkIoSnapshot(),
78
+ takeDiskIoSnapshot(),
79
+ getCpuTemperatureInternal().catch(() => null)
80
+ ]);
81
+ const snapshot = {
82
+ cpu,
83
+ memory,
84
+ gpu,
85
+ network,
86
+ disk,
87
+ pressure: {
88
+ cpu: getPressure("cpu"),
89
+ memory: getPressure("memory"),
90
+ io: getPressure("io")
91
+ },
92
+ process: processInfo,
93
+ cpuTemperature: cpuTemp,
94
+ timestampMs: Date.now()
95
+ };
96
+ this.cachedSnapshot = snapshot;
97
+ return snapshot;
98
+ }
99
+ async getCached() {
100
+ return this.cachedSnapshot;
101
+ }
102
+ async getCurrent() {
103
+ const s = this.cachedSnapshot ?? await this.collectSnapshot();
104
+ const mb = (bytes) => Math.round(bytes / 1024 / 1024);
105
+ return {
106
+ cpuPercent: s.cpu.total,
107
+ memoryPercent: s.memory.percent,
108
+ memoryUsedMB: mb(s.memory.usedBytes),
109
+ memoryTotalMB: mb(s.memory.totalBytes),
110
+ temperature: s.cpuTemperature ?? void 0,
111
+ gpuPercent: s.gpu?.utilization,
112
+ gpuMemoryPercent: s.gpu && s.gpu.memoryTotalBytes > 0 ? Math.round(s.gpu.memoryUsedBytes / s.gpu.memoryTotalBytes * 1e3) / 10 : void 0
113
+ };
114
+ }
115
+ async getDiskSpace(input) {
116
+ return getDiskSpaceInternal(input.dirPath);
117
+ }
118
+ async getGpuInfo() {
119
+ return getGpuInfoInternal();
120
+ }
121
+ async getCpuTemperature() {
122
+ return getCpuTemperatureInternal();
123
+ }
124
+ async getProcessStats(input) {
125
+ const { getPidStats } = await import("./resource-monitor-UZUGPIAU.mjs");
126
+ const raw = await getPidStats(input.pids);
127
+ const result = [];
128
+ for (const [pid, s] of raw) {
129
+ result.push({ pid, cpu: s.cpu, memory: s.memory });
130
+ }
131
+ return result;
132
+ }
133
+ // ─── Internal: CPU sampling ─────────────────────────────────────────
134
+ sampleCpu() {
135
+ const curr = aggregateCpuTimes();
136
+ const prev = this.prevCpu;
137
+ this.prevCpu = curr;
138
+ const dUser = curr.user - prev.user;
139
+ const dNice = curr.nice - prev.nice;
140
+ const dSys = curr.sys - prev.sys;
141
+ const dIrq = curr.irq - prev.irq;
142
+ const dIdle = curr.idle - prev.idle;
143
+ const dTotal = dUser + dNice + dSys + dIrq + dIdle;
144
+ const pct = (v) => dTotal === 0 ? 0 : Math.round(v / dTotal * 1e3) / 10;
145
+ return {
146
+ total: pct(dUser + dNice + dSys + dIrq),
147
+ user: pct(dUser),
148
+ system: pct(dSys),
149
+ irq: pct(dIrq),
150
+ nice: pct(dNice),
151
+ loadAvg: os.loadavg(),
152
+ cores: os.cpus().length
153
+ };
154
+ }
155
+ // ─── Internal: Memory ───────────────────────────────────────────────
156
+ /** Cached macOS memory (set by sampleMacMemory timer). */
157
+ _cachedMacMem = null;
158
+ getMemoryInfo() {
159
+ const total = os.totalmem();
160
+ if (total === 0) {
161
+ return { percent: 0, totalBytes: 0, usedBytes: 0, availableBytes: 0, swapUsedBytes: 0, swapTotalBytes: 0 };
162
+ }
163
+ if (IS_DARWIN && this._cachedMacMem !== null) {
164
+ return this._cachedMacMem;
165
+ }
166
+ const free = os.freemem();
167
+ const used = total - free;
168
+ let swapUsedBytes = 0;
169
+ let swapTotalBytes = 0;
170
+ if (IS_LINUX) {
171
+ try {
172
+ const meminfo = fs.readFileSync("/proc/meminfo", "utf-8");
173
+ const swapTotal = parseInt(meminfo.match(/SwapTotal:\s+(\d+)/)?.[1] ?? "0", 10) * 1024;
174
+ const swapFree = parseInt(meminfo.match(/SwapFree:\s+(\d+)/)?.[1] ?? "0", 10) * 1024;
175
+ swapTotalBytes = swapTotal;
176
+ swapUsedBytes = swapTotal - swapFree;
177
+ } catch {
178
+ }
179
+ }
180
+ return {
181
+ percent: Math.round((total - free) / total * 1e3) / 10,
182
+ totalBytes: total,
183
+ usedBytes: used,
184
+ availableBytes: free,
185
+ swapUsedBytes,
186
+ swapTotalBytes
187
+ };
188
+ }
189
+ };
190
+ function aggregateCpuTimes() {
191
+ const cpus2 = os.cpus();
192
+ let user = 0, nice = 0, sys = 0, irq = 0, idle = 0;
193
+ for (const cpu of cpus2) {
194
+ user += cpu.times.user;
195
+ nice += cpu.times.nice;
196
+ sys += cpu.times.sys;
197
+ irq += cpu.times.irq;
198
+ idle += cpu.times.idle;
199
+ }
200
+ return { user, nice, sys, irq, idle };
201
+ }
202
+ function sampleMacMemory(provider) {
203
+ try {
204
+ const output = execFileSync("vm_stat", { timeout: 2e3, encoding: "utf-8" });
205
+ const pageSize = 16384;
206
+ const parse = (label) => {
207
+ const match = output.match(new RegExp(`${label}:\\s+(\\d+)`));
208
+ return match ? parseInt(match[1], 10) * pageSize : 0;
209
+ };
210
+ const free = parse("Pages free");
211
+ const inactive = parse("Pages inactive");
212
+ const purgeable = parse("Pages purgeable");
213
+ const speculative = parse("Pages speculative");
214
+ const available = free + inactive + purgeable + speculative;
215
+ const total = os.totalmem();
216
+ const used = total - available;
217
+ let swapUsedBytes = 0;
218
+ let swapTotalBytes = 0;
219
+ try {
220
+ const swapOut = execFileSync("sysctl", ["-n", "vm.swapusage"], { timeout: 2e3, encoding: "utf-8" });
221
+ const totalMatch = swapOut.match(/total\s*=\s*([\d.]+)M/);
222
+ const usedMatch = swapOut.match(/used\s*=\s*([\d.]+)M/);
223
+ if (totalMatch) swapTotalBytes = parseFloat(totalMatch[1]) * 1024 * 1024;
224
+ if (usedMatch) swapUsedBytes = parseFloat(usedMatch[1]) * 1024 * 1024;
225
+ } catch {
226
+ }
227
+ provider._cachedMacMem = {
228
+ percent: Math.round(used / total * 1e3) / 10,
229
+ totalBytes: total,
230
+ usedBytes: used,
231
+ availableBytes: available,
232
+ swapUsedBytes,
233
+ swapTotalBytes
234
+ };
235
+ } catch {
236
+ provider._cachedMacMem = null;
237
+ }
238
+ }
239
+ function getDiskSpaceInternal(dirPath) {
240
+ return new Promise((resolve, reject) => {
241
+ fs.statfs(dirPath, (err, stats) => {
242
+ if (err) return reject(err);
243
+ const totalBytes = stats.blocks * stats.bsize;
244
+ const availableBytes = stats.bavail * stats.bsize;
245
+ const usedBytes = totalBytes - stats.bfree * stats.bsize;
246
+ resolve({
247
+ path: dirPath,
248
+ totalBytes,
249
+ usedBytes,
250
+ availableBytes,
251
+ percent: totalBytes > 0 ? Math.round(usedBytes / totalBytes * 1e3) / 10 : 0
252
+ });
253
+ });
254
+ });
255
+ }
256
+ async function takeDiskIoSnapshot() {
257
+ const now = Date.now();
258
+ if (IS_LINUX) {
259
+ try {
260
+ const data = fs.readFileSync("/proc/diskstats", "utf-8");
261
+ let readOps = 0, readBytes = 0, writeOps = 0, writeBytes = 0;
262
+ for (const line of data.split("\n")) {
263
+ const parts = line.trim().split(/\s+/);
264
+ if (parts.length < 14) continue;
265
+ const devName = parts[2];
266
+ if (/\d+$/.test(devName) && !/^nvme\d+n\d+$/.test(devName)) continue;
267
+ readOps += parseInt(parts[3], 10) || 0;
268
+ readBytes += (parseInt(parts[5], 10) || 0) * 512;
269
+ writeOps += parseInt(parts[7], 10) || 0;
270
+ writeBytes += (parseInt(parts[9], 10) || 0) * 512;
271
+ }
272
+ return { readBytes, writeBytes, readOps, writeOps, timestampMs: now };
273
+ } catch {
274
+ }
275
+ }
276
+ return { readBytes: 0, writeBytes: 0, readOps: 0, writeOps: 0, timestampMs: now };
277
+ }
278
+ async function takeNetworkIoSnapshot() {
279
+ const now = Date.now();
280
+ if (IS_LINUX) {
281
+ try {
282
+ const data = fs.readFileSync("/proc/net/dev", "utf-8");
283
+ let rxBytes = 0, txBytes = 0, rxPackets = 0, txPackets = 0, rxErrors = 0, txErrors = 0;
284
+ for (const line of data.split("\n")) {
285
+ const match = line.match(/^\s*(\w+):\s+(.+)$/);
286
+ if (!match) continue;
287
+ if (match[1] === "lo") continue;
288
+ const cols = match[2].trim().split(/\s+/);
289
+ rxBytes += parseInt(cols[0], 10) || 0;
290
+ rxPackets += parseInt(cols[1], 10) || 0;
291
+ rxErrors += parseInt(cols[2], 10) || 0;
292
+ txBytes += parseInt(cols[8], 10) || 0;
293
+ txPackets += parseInt(cols[9], 10) || 0;
294
+ txErrors += parseInt(cols[10], 10) || 0;
295
+ }
296
+ return { rxBytes, txBytes, rxPackets, txPackets, rxErrors, txErrors, timestampMs: now };
297
+ } catch {
298
+ }
299
+ }
300
+ if (IS_DARWIN) {
301
+ try {
302
+ const stdout = await execAsync("netstat", ["-ib"]);
303
+ let rxBytes = 0, txBytes = 0, rxPackets = 0, txPackets = 0, rxErrors = 0, txErrors = 0;
304
+ for (const line of stdout.split("\n")) {
305
+ const parts = line.trim().split(/\s+/);
306
+ if (parts.length < 11) continue;
307
+ if (parts[0] === "lo0" || parts[0] === "Name") continue;
308
+ if (!parts[2]?.startsWith("Link#")) continue;
309
+ rxPackets += parseInt(parts[4], 10) || 0;
310
+ rxErrors += parseInt(parts[5], 10) || 0;
311
+ rxBytes += parseInt(parts[6], 10) || 0;
312
+ txPackets += parseInt(parts[7], 10) || 0;
313
+ txErrors += parseInt(parts[8], 10) || 0;
314
+ txBytes += parseInt(parts[9], 10) || 0;
315
+ }
316
+ return { rxBytes, txBytes, rxPackets, txPackets, rxErrors, txErrors, timestampMs: now };
317
+ } catch {
318
+ }
319
+ }
320
+ return { rxBytes: 0, txBytes: 0, rxPackets: 0, txPackets: 0, rxErrors: 0, txErrors: 0, timestampMs: now };
321
+ }
322
+ async function getCpuTemperatureInternal() {
323
+ if (IS_LINUX) {
324
+ const paths = ["/sys/class/thermal/thermal_zone0/temp", "/sys/class/hwmon/hwmon0/temp1_input"];
325
+ for (const p of paths) {
326
+ try {
327
+ const raw = fs.readFileSync(p, "utf-8").trim();
328
+ const millideg = parseInt(raw, 10);
329
+ if (!isNaN(millideg)) return Math.round(millideg / 100) / 10;
330
+ } catch {
331
+ continue;
332
+ }
333
+ }
334
+ }
335
+ if (IS_DARWIN) {
336
+ try {
337
+ const stdout = await execAsync("sudo", ["-n", "powermetrics", "--samplers", "smc", "-i", "1", "-n", "1"], 3e3);
338
+ const match = stdout.match(/CPU die temperature:\s+([\d.]+)\s*C/);
339
+ if (match) return Math.round(parseFloat(match[1]) * 10) / 10;
340
+ } catch {
341
+ }
342
+ }
343
+ return null;
344
+ }
345
+ async function getGpuInfoInternal() {
346
+ if (IS_DARWIN) {
347
+ try {
348
+ const stdout = await execAsync("ioreg", ["-r", "-d", "1", "-c", "IOAccelerator"]);
349
+ const utilMatch = stdout.match(/"GPU Core Utilization"\s*=\s*(\d+)/);
350
+ const util = utilMatch ? parseInt(utilMatch[1], 10) : 0;
351
+ const utilPct = util > 1e3 ? Math.round(util / 1e7) : util;
352
+ return {
353
+ utilization: Math.min(100, utilPct),
354
+ model: "Apple Silicon GPU",
355
+ memoryUsedBytes: 0,
356
+ memoryTotalBytes: 0,
357
+ temperature: null
358
+ };
359
+ } catch {
360
+ }
361
+ }
362
+ if (IS_LINUX) {
363
+ try {
364
+ const stdout = await execAsync("nvidia-smi", [
365
+ "--query-gpu=utilization.gpu,name,memory.used,memory.total,temperature.gpu",
366
+ "--format=csv,noheader,nounits"
367
+ ]);
368
+ const parts = stdout.trim().split(",").map((s) => s.trim());
369
+ if (parts.length >= 5) {
370
+ return {
371
+ utilization: parseInt(parts[0], 10) || 0,
372
+ model: parts[1] ?? "Unknown GPU",
373
+ memoryUsedBytes: (parseInt(parts[2], 10) || 0) * 1024 * 1024,
374
+ memoryTotalBytes: (parseInt(parts[3], 10) || 0) * 1024 * 1024,
375
+ temperature: parseInt(parts[4], 10) || null
376
+ };
377
+ }
378
+ } catch {
379
+ }
380
+ }
381
+ return null;
382
+ }
383
+ function getProcessResources() {
384
+ let openFds = 0;
385
+ let threadCount = 0;
386
+ if (IS_LINUX) {
387
+ try {
388
+ openFds = fs.readdirSync(`/proc/${process.pid}/fd`).length;
389
+ } catch {
390
+ }
391
+ try {
392
+ const status = fs.readFileSync(`/proc/${process.pid}/status`, "utf-8");
393
+ const match = status.match(/Threads:\s+(\d+)/);
394
+ if (match) threadCount = parseInt(match[1], 10);
395
+ } catch {
396
+ }
397
+ }
398
+ const nodeProcess = process;
399
+ return {
400
+ openFds,
401
+ threadCount,
402
+ activeHandles: (nodeProcess._getActiveHandles?.() ?? []).length,
403
+ activeRequests: (nodeProcess._getActiveRequests?.() ?? []).length
404
+ };
405
+ }
406
+ function getPressure(resource) {
407
+ if (!IS_LINUX) return null;
408
+ try {
409
+ const data = fs.readFileSync(`/proc/pressure/${resource}`, "utf-8");
410
+ const parseLine = (prefix) => {
411
+ const match = data.match(new RegExp(`${prefix} avg10=(\\d+\\.\\d+) avg60=(\\d+\\.\\d+) avg300=(\\d+\\.\\d+)`));
412
+ if (!match) return null;
413
+ return { avg10: parseFloat(match[1]), avg60: parseFloat(match[2]), avg300: parseFloat(match[3]) };
414
+ };
415
+ const some = parseLine("some");
416
+ if (!some) return null;
417
+ return { some, full: parseLine("full") };
418
+ } catch {
419
+ return null;
420
+ }
421
+ }
422
+ function execAsync(cmd, args, timeoutMs = 5e3) {
423
+ return new Promise((resolve, reject) => {
424
+ execFile(cmd, args, { timeout: timeoutMs }, (err, stdout) => {
425
+ if (err) return reject(err);
426
+ resolve(stdout);
427
+ });
428
+ });
429
+ }
430
+
431
+ // src/builtins/native-metrics/native-metrics.addon.ts
432
+ var execFileAsync = promisify(execFile2);
433
+ var CAMSTACK_CMD_RE = /(camstack|tsx\s+watch\s.*launcher\.ts|packages\/agent\/dist\/cli\.js|inference_pool\.py|bench-(inference-pool|nodeav)|node .*\/packages\/)/;
434
+ var SUPERVISOR_BOUNDARY_RE = /(tsx\s+watch\s.*launcher\.ts|packages\/agent\/dist\/cli\.js|\.bin\/concurrently|\/concurrently\/dist|\bnpm-cli\.js\b|npm exec |\.bin\/vite|\/vite\/bin\/vite\.js|node_modules\/\.bin\/(vite|concurrently|tsup|rollup|esbuild|tsx)(\s|$))/;
435
+ var METRICS_SNAPSHOT_INTERVAL_MS = 5e3;
436
+ var METRICS_SNAPSHOT_HEARTBEAT_MS = 6e4;
437
+ function coarsenResourcesSnapshot(snapshot) {
438
+ if (!snapshot || typeof snapshot !== "object") return JSON.stringify(snapshot);
439
+ const round = (v) => {
440
+ if (typeof v === "number") {
441
+ if (v >= 0 && v <= 100) return Math.round(v / 5) * 5;
442
+ return Math.round(v / 1e5) * 1e5;
443
+ }
444
+ if (Array.isArray(v)) return v.map(round);
445
+ if (v && typeof v === "object") {
446
+ const out = {};
447
+ for (const [k, val] of Object.entries(v)) {
448
+ out[k] = round(val);
449
+ }
450
+ return out;
451
+ }
452
+ return v;
453
+ };
454
+ return JSON.stringify(round(snapshot));
455
+ }
456
+ function coarsenProcessList(processes) {
457
+ const summary = processes.filter((p) => !!p && typeof p === "object").map((p) => {
458
+ const pid = p["pid"];
459
+ const addonId = p["addonId"];
460
+ const state = p["state"];
461
+ const cpu = typeof p["cpuPercent"] === "number" ? Math.round(p["cpuPercent"] / 5) * 5 : null;
462
+ const mem = typeof p["memoryRss"] === "number" ? Math.round(p["memoryRss"] / (50 * 1024 * 1024)) : null;
463
+ return [pid, addonId, state, cpu, mem];
464
+ });
465
+ return JSON.stringify(summary);
466
+ }
467
+ function narrowWorkerState(state) {
468
+ switch (state) {
469
+ case "starting":
470
+ case "running":
471
+ case "stopping":
472
+ case "stopped":
473
+ case "crashed":
474
+ return state;
475
+ default:
476
+ return "running";
477
+ }
478
+ }
479
+ var NativeMetricsAddon = class extends BaseAddon {
480
+ provider = null;
481
+ startedAtMs = Date.now();
482
+ snapshotTimer = null;
483
+ /**
484
+ * Snapshot-equality cache for the resources + processes emit.
485
+ * Stores the coarsened JSON and timestamp; a tick where the
486
+ * coarsened payload matches the cache (and the heartbeat hasn't
487
+ * elapsed) is skipped.
488
+ */
489
+ lastResourcesEmit = null;
490
+ lastProcessesEmit = null;
491
+ constructor() {
492
+ super({ samplingIntervalMs: 5e3 });
493
+ }
494
+ async onInitialize() {
495
+ const provider = new NativeMetricsProvider();
496
+ provider.setDiagnostics({
497
+ warn: (message, meta) => this.ctx.logger.warn(message, { meta: meta ?? {} })
498
+ });
499
+ provider.startSampling(this.config.samplingIntervalMs);
500
+ this.provider = provider;
501
+ this.startedAtMs = Date.now();
502
+ this.ctx.logger.info("Native metrics provider started", { meta: { intervalMs: this.config.samplingIntervalMs } });
503
+ const composed = {
504
+ collectSnapshot: () => provider.collectSnapshot(),
505
+ getCached: () => provider.getCached(),
506
+ getCurrent: () => provider.getCurrent(),
507
+ getDiskSpace: (params) => provider.getDiskSpace(params),
508
+ getGpuInfo: () => provider.getGpuInfo(),
509
+ getCpuTemperature: () => provider.getCpuTemperature(),
510
+ getProcessStats: (params) => provider.getProcessStats(params),
511
+ listAddonInstances: () => this.listAddonInstances(),
512
+ getAddonStats: (params) => this.getAddonStats(params.addonId),
513
+ listNodeProcesses: () => this.listNodeProcesses(),
514
+ killProcess: (params) => this.killProcess(params)
515
+ };
516
+ this.snapshotTimer = setInterval(
517
+ () => this.emitMetricsSnapshots(),
518
+ METRICS_SNAPSHOT_INTERVAL_MS
519
+ );
520
+ return [{ capability: metricsProviderCapability, provider: composed }];
521
+ }
522
+ async onShutdown() {
523
+ if (this.snapshotTimer) {
524
+ clearInterval(this.snapshotTimer);
525
+ this.snapshotTimer = null;
526
+ }
527
+ this.provider?.stopSampling();
528
+ this.provider = null;
529
+ }
530
+ /**
531
+ * Emit one `metrics.node-resources-snapshot` + one
532
+ * `metrics.node-processes-snapshot` for this node. UI consumers
533
+ * subscribe and read state directly from the payload.
534
+ */
535
+ async emitMetricsSnapshots() {
536
+ const provider = this.provider;
537
+ const eventBus = this.ctx.eventBus;
538
+ if (!provider || !eventBus) return;
539
+ const rawNodeId = this.ctx.kernel.localNodeId ?? this.ctx.id;
540
+ const nodeId = rawNodeId.includes("/") ? rawNodeId.split("/")[0] : rawNodeId;
541
+ const timestamp = Date.now();
542
+ try {
543
+ const cached = await provider.getCached();
544
+ const snapshot = cached ?? await provider.collectSnapshot();
545
+ const coarse = coarsenResourcesSnapshot(snapshot);
546
+ const prev = this.lastResourcesEmit;
547
+ const heartbeatDue = !prev || timestamp - prev.emittedAt >= METRICS_SNAPSHOT_HEARTBEAT_MS;
548
+ if (!prev || prev.coarse !== coarse || heartbeatDue) {
549
+ this.lastResourcesEmit = { coarse, emittedAt: timestamp };
550
+ eventBus.emit(createEvent(
551
+ EventCategory.MetricsNodeResourcesSnapshot,
552
+ { type: "node", id: nodeId, nodeId },
553
+ { nodeId, snapshot, timestamp }
554
+ ));
555
+ }
556
+ } catch {
557
+ }
558
+ try {
559
+ const processes = await this.listNodeProcesses();
560
+ const coarse = coarsenProcessList(processes);
561
+ const prev = this.lastProcessesEmit;
562
+ const heartbeatDue = !prev || timestamp - prev.emittedAt >= METRICS_SNAPSHOT_HEARTBEAT_MS;
563
+ if (!prev || prev.coarse !== coarse || heartbeatDue) {
564
+ this.lastProcessesEmit = { coarse, emittedAt: timestamp };
565
+ eventBus.emit(createEvent(
566
+ EventCategory.MetricsNodeProcessesSnapshot,
567
+ { type: "node", id: nodeId, nodeId },
568
+ { nodeId, processes, timestamp }
569
+ ));
570
+ }
571
+ } catch {
572
+ }
573
+ }
574
+ async onConfigChanged() {
575
+ this.provider?.stopSampling();
576
+ this.provider?.startSampling(this.config.samplingIntervalMs);
577
+ }
578
+ async listWorkerInstances() {
579
+ const broker = this.ctx.kernel.cluster?.broker;
580
+ if (!broker) return [];
581
+ try {
582
+ return await broker.call("$process.list");
583
+ } catch {
584
+ return [];
585
+ }
586
+ }
587
+ async listAddonInstances() {
588
+ const broker = this.ctx.kernel.cluster?.broker;
589
+ const hubNodeId = broker?.nodeID ?? "hub";
590
+ const uptimeSec = Math.max(0, Math.floor((Date.now() - this.startedAtMs) / 1e3));
591
+ const instances = [{
592
+ addonId: "$hub",
593
+ nodeId: hubNodeId,
594
+ role: "hub",
595
+ pid: process.pid,
596
+ state: "running",
597
+ uptimeSec
598
+ }];
599
+ const workers = await this.listWorkerInstances();
600
+ for (const w of workers) {
601
+ instances.push({
602
+ addonId: w.addonId,
603
+ nodeId: w.nodeId,
604
+ role: "worker",
605
+ pid: w.pid,
606
+ state: narrowWorkerState(w.state),
607
+ uptimeSec: w.uptimeSeconds
608
+ });
609
+ }
610
+ return instances;
611
+ }
612
+ async getAddonStats(addonId) {
613
+ const provider = this.provider;
614
+ if (!provider) return null;
615
+ if (addonId === "$hub") {
616
+ const stats2 = await provider.getProcessStats({ pids: [process.pid] });
617
+ return stats2[0] ?? null;
618
+ }
619
+ const workers = await this.listWorkerInstances();
620
+ const target = workers.find((w) => w.addonId === addonId);
621
+ if (!target) return null;
622
+ const stats = await provider.getProcessStats({ pids: [target.pid] });
623
+ return stats[0] ?? null;
624
+ }
625
+ /**
626
+ * Walk the OS process table and classify each camstack-shaped process.
627
+ *
628
+ * Classification (ancestry-driven, NOT pattern-driven):
629
+ * - root — the current node's own pid (`process.pid`).
630
+ * - managed — pid is registered in the kernel's `$process.list`
631
+ * (forked addon worker spawned by this hub).
632
+ * - system — ancestry walk crosses a SUPERVISOR_BOUNDARY_RE match
633
+ * (tsx-watch launcher, agent CLI, concurrently, vite,
634
+ * npm exec wrapper). The process belongs to the dev
635
+ * tree even if not in `$process.list`. NEVER killable.
636
+ * - ghost — ancestry walk reaches `ppid=1` without crossing any
637
+ * supervisor boundary AND the parent isn't visible in
638
+ * `ps`. A truly orphaned camstack-shaped process. The
639
+ * ONLY classification that's eligible for kill.
640
+ *
641
+ * Old pattern-only ghost detection produced false positives: every
642
+ * monorepo-path process matched CAMSTACK_CMD_RE, ancestry walk
643
+ * stopping at ppid=hub returned false-positive ghosts whenever a
644
+ * concurrently sibling sat above hub. Ancestry-driven classification
645
+ * fixes that.
646
+ */
647
+ async listNodeProcesses() {
648
+ const ps = await this.runPs();
649
+ if (ps.length === 0) return [];
650
+ const managedPids = /* @__PURE__ */ new Map();
651
+ const workers = await this.listWorkerInstances();
652
+ for (const w of workers) {
653
+ managedPids.set(w.pid, { addonId: w.addonId, nodeId: w.nodeId });
654
+ }
655
+ const hubNodeId = this.ctx.kernel.cluster?.broker?.nodeID ?? "hub";
656
+ const selfPid = process.pid;
657
+ const psIndex = /* @__PURE__ */ new Map();
658
+ for (const p of ps) psIndex.set(p.pid, { ppid: p.ppid, command: p.command });
659
+ const classifyByAncestry = (startPid) => {
660
+ let cur = startPid;
661
+ for (let depth = 0; depth < 32; depth++) {
662
+ const node = psIndex.get(cur);
663
+ if (!node) return "ghost";
664
+ if (cur === selfPid) return "system";
665
+ if (SUPERVISOR_BOUNDARY_RE.test(node.command)) return "system";
666
+ if (node.ppid === 1) return "ghost";
667
+ if (node.ppid === selfPid) return "system";
668
+ cur = node.ppid;
669
+ }
670
+ return "system";
671
+ };
672
+ const out = [];
673
+ for (const p of ps) {
674
+ if (!CAMSTACK_CMD_RE.test(p.command)) continue;
675
+ const managed = managedPids.get(p.pid);
676
+ let classification;
677
+ if (p.pid === selfPid) {
678
+ classification = "root";
679
+ } else if (managed) {
680
+ classification = "managed";
681
+ } else {
682
+ classification = classifyByAncestry(p.pid);
683
+ }
684
+ const orphaned = classification === "ghost";
685
+ out.push({
686
+ pid: p.pid,
687
+ ppid: p.ppid,
688
+ pgid: p.pgid,
689
+ classification,
690
+ addonId: managed?.addonId ?? null,
691
+ nodeId: managed?.nodeId ?? (p.pid === selfPid ? hubNodeId : null),
692
+ command: p.command,
693
+ cpuPercent: p.cpuPercent,
694
+ memoryRssBytes: p.memoryRssBytes,
695
+ uptimeSec: p.uptimeSec,
696
+ orphaned
697
+ });
698
+ }
699
+ return out;
700
+ }
701
+ /**
702
+ * Send SIGTERM / SIGKILL to a pid. Refuses pids that don't appear in
703
+ * `listNodeProcesses()` to prevent arbitrary system kills — a dedicated
704
+ * admin-path for resurrected zombies, not a generic shell replacement.
705
+ *
706
+ * `root`-classified pids (the running launcher / agent CLI / hub itself)
707
+ * are also refused: killing them tears down the whole node and the
708
+ * operator's intent is almost always to nuke a leaked child, not the
709
+ * supervisor that keeps the rest alive. Process restart goes through
710
+ * the dedicated `$process.restart` action, not this kill API.
711
+ */
712
+ async killProcess(input) {
713
+ const visible = await this.listNodeProcesses();
714
+ const match = visible.find((p) => p.pid === input.pid);
715
+ if (!match) {
716
+ return { success: false, reason: "pid not in node process table" };
717
+ }
718
+ if (match.classification === "root" || match.classification === "system") {
719
+ this.ctx.logger.warn("Refused to kill protected process", {
720
+ meta: { pid: input.pid, classification: match.classification, addonId: match.addonId, command: match.command }
721
+ });
722
+ const reason = match.classification === "root" ? "cannot kill root (current node supervisor)" : "cannot kill system (intentional dev-tree ancestor \u2014 vite, concurrently, npm, etc.)";
723
+ return { success: false, reason };
724
+ }
725
+ const signal = input.force ? "SIGKILL" : "SIGTERM";
726
+ try {
727
+ process.kill(input.pid, signal);
728
+ this.ctx.logger.info("Killed node process", { meta: { pid: input.pid, signal, classification: match.classification, addonId: match.addonId } });
729
+ return { success: true, signal };
730
+ } catch (err) {
731
+ const msg = err instanceof Error ? err.message : String(err);
732
+ return { success: false, reason: msg, signal };
733
+ }
734
+ }
735
+ /** Raw `ps` scan returning every pid + command + resource stats. */
736
+ async runPs() {
737
+ try {
738
+ const { stdout } = await execFileAsync(
739
+ "ps",
740
+ ["-eo", "pid=,ppid=,pgid=,pcpu=,rss=,etime=,command="],
741
+ { timeout: 5e3, maxBuffer: 8 * 1024 * 1024 }
742
+ );
743
+ const lines = stdout.split("\n");
744
+ const rows = [];
745
+ for (const line of lines) {
746
+ const trimmed = line.trim();
747
+ if (!trimmed) continue;
748
+ const match = trimmed.match(/^(\d+)\s+(\d+)\s+(\d+)\s+([\d.]+)\s+(\d+)\s+(\S+)\s+(.+)$/);
749
+ if (!match) continue;
750
+ const [, pidS, ppidS, pgidS, cpuS, rssS, etimeS, command] = match;
751
+ rows.push({
752
+ pid: parseInt(pidS, 10),
753
+ ppid: parseInt(ppidS, 10),
754
+ pgid: parseInt(pgidS, 10),
755
+ cpuPercent: Math.round(parseFloat(cpuS) * 10) / 10,
756
+ memoryRssBytes: parseInt(rssS, 10) * 1024,
757
+ uptimeSec: parseEtime(etimeS),
758
+ command
759
+ });
760
+ }
761
+ return rows;
762
+ } catch (err) {
763
+ this.ctx.logger.warn("ps scan failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
764
+ return [];
765
+ }
766
+ }
767
+ globalSettingsSchema() {
768
+ return this.schema({
769
+ sections: [{
770
+ id: "native-metrics-settings",
771
+ title: "System Metrics",
772
+ fields: [
773
+ this.field({
774
+ type: "number",
775
+ key: "samplingIntervalMs",
776
+ label: "Sampling Interval",
777
+ description: "How often to collect system metrics",
778
+ min: 1e3,
779
+ max: 6e4,
780
+ step: 1e3,
781
+ default: 5e3,
782
+ unit: "ms"
783
+ })
784
+ ]
785
+ }]
786
+ });
787
+ }
788
+ };
789
+ function parseEtime(etime) {
790
+ const dashParts = etime.split("-");
791
+ let days = 0;
792
+ let hms = etime;
793
+ if (dashParts.length === 2) {
794
+ days = parseInt(dashParts[0], 10) || 0;
795
+ hms = dashParts[1];
796
+ }
797
+ const parts = hms.split(":").map((p) => parseInt(p, 10) || 0);
798
+ let hours = 0, minutes = 0, seconds = 0;
799
+ if (parts.length === 3) [hours, minutes, seconds] = parts;
800
+ else if (parts.length === 2) [minutes, seconds] = parts;
801
+ else if (parts.length === 1) [seconds] = parts;
802
+ return days * 86400 + hours * 3600 + minutes * 60 + seconds;
803
+ }
804
+
805
+ export {
806
+ NativeMetricsProvider,
807
+ NativeMetricsAddon
808
+ };
809
+ //# sourceMappingURL=chunk-4XHB7IHT.mjs.map