@orkify/cli 1.0.0-beta.5

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 (203) hide show
  1. package/LICENSE +191 -0
  2. package/README.md +1701 -0
  3. package/bin/orkify +3 -0
  4. package/boot/systemd/orkify@.service +30 -0
  5. package/dist/agent-name.d.ts +4 -0
  6. package/dist/agent-name.js +42 -0
  7. package/dist/alerts/AlertEvaluator.d.ts +14 -0
  8. package/dist/alerts/AlertEvaluator.js +135 -0
  9. package/dist/cli/commands/autostart.d.ts +3 -0
  10. package/dist/cli/commands/autostart.js +11 -0
  11. package/dist/cli/commands/crash-test.d.ts +3 -0
  12. package/dist/cli/commands/crash-test.js +17 -0
  13. package/dist/cli/commands/daemon-reload.d.ts +3 -0
  14. package/dist/cli/commands/daemon-reload.js +72 -0
  15. package/dist/cli/commands/delete.d.ts +3 -0
  16. package/dist/cli/commands/delete.js +37 -0
  17. package/dist/cli/commands/deploy.d.ts +6 -0
  18. package/dist/cli/commands/deploy.js +266 -0
  19. package/dist/cli/commands/down.d.ts +3 -0
  20. package/dist/cli/commands/down.js +36 -0
  21. package/dist/cli/commands/flush.d.ts +3 -0
  22. package/dist/cli/commands/flush.js +28 -0
  23. package/dist/cli/commands/kill.d.ts +3 -0
  24. package/dist/cli/commands/kill.js +35 -0
  25. package/dist/cli/commands/list.d.ts +14 -0
  26. package/dist/cli/commands/list.js +361 -0
  27. package/dist/cli/commands/logs.d.ts +3 -0
  28. package/dist/cli/commands/logs.js +107 -0
  29. package/dist/cli/commands/mcp.d.ts +3 -0
  30. package/dist/cli/commands/mcp.js +151 -0
  31. package/dist/cli/commands/reload.d.ts +3 -0
  32. package/dist/cli/commands/reload.js +54 -0
  33. package/dist/cli/commands/restart.d.ts +3 -0
  34. package/dist/cli/commands/restart.js +43 -0
  35. package/dist/cli/commands/restore.d.ts +3 -0
  36. package/dist/cli/commands/restore.js +88 -0
  37. package/dist/cli/commands/run.d.ts +8 -0
  38. package/dist/cli/commands/run.js +212 -0
  39. package/dist/cli/commands/snap.d.ts +3 -0
  40. package/dist/cli/commands/snap.js +30 -0
  41. package/dist/cli/commands/up.d.ts +3 -0
  42. package/dist/cli/commands/up.js +125 -0
  43. package/dist/cli/crash-recovery.d.ts +2 -0
  44. package/dist/cli/crash-recovery.js +67 -0
  45. package/dist/cli/index.d.ts +3 -0
  46. package/dist/cli/index.js +46 -0
  47. package/dist/cli/parse.d.ts +28 -0
  48. package/dist/cli/parse.js +97 -0
  49. package/dist/cluster/ClusterWrapper.d.ts +18 -0
  50. package/dist/cluster/ClusterWrapper.js +602 -0
  51. package/dist/config/ConfigStore.d.ts +11 -0
  52. package/dist/config/ConfigStore.js +21 -0
  53. package/dist/config/schema.d.ts +103 -0
  54. package/dist/config/schema.js +49 -0
  55. package/dist/constants.d.ts +83 -0
  56. package/dist/constants.js +289 -0
  57. package/dist/cron/CronScheduler.d.ts +25 -0
  58. package/dist/cron/CronScheduler.js +149 -0
  59. package/dist/daemon/GracefulManager.d.ts +8 -0
  60. package/dist/daemon/GracefulManager.js +29 -0
  61. package/dist/daemon/ManagedProcess.d.ts +71 -0
  62. package/dist/daemon/ManagedProcess.js +1020 -0
  63. package/dist/daemon/Orchestrator.d.ts +51 -0
  64. package/dist/daemon/Orchestrator.js +416 -0
  65. package/dist/daemon/RotatingWriter.d.ts +27 -0
  66. package/dist/daemon/RotatingWriter.js +264 -0
  67. package/dist/daemon/index.d.ts +2 -0
  68. package/dist/daemon/index.js +106 -0
  69. package/dist/daemon/startDaemon.d.ts +30 -0
  70. package/dist/daemon/startDaemon.js +693 -0
  71. package/dist/deploy/CommandPoller.d.ts +13 -0
  72. package/dist/deploy/CommandPoller.js +53 -0
  73. package/dist/deploy/DeployExecutor.d.ts +33 -0
  74. package/dist/deploy/DeployExecutor.js +340 -0
  75. package/dist/deploy/config.d.ts +20 -0
  76. package/dist/deploy/config.js +161 -0
  77. package/dist/deploy/env.d.ts +2 -0
  78. package/dist/deploy/env.js +17 -0
  79. package/dist/deploy/tarball.d.ts +32 -0
  80. package/dist/deploy/tarball.js +243 -0
  81. package/dist/detect/framework.d.ts +2 -0
  82. package/dist/detect/framework.js +24 -0
  83. package/dist/ipc/DaemonClient.d.ts +31 -0
  84. package/dist/ipc/DaemonClient.js +248 -0
  85. package/dist/ipc/DaemonServer.d.ts +28 -0
  86. package/dist/ipc/DaemonServer.js +166 -0
  87. package/dist/ipc/MultiUserClient.d.ts +27 -0
  88. package/dist/ipc/MultiUserClient.js +203 -0
  89. package/dist/ipc/protocol.d.ts +7 -0
  90. package/dist/ipc/protocol.js +53 -0
  91. package/dist/ipc/restoreDaemon.d.ts +8 -0
  92. package/dist/ipc/restoreDaemon.js +19 -0
  93. package/dist/machine-id.d.ts +11 -0
  94. package/dist/machine-id.js +51 -0
  95. package/dist/mcp/auth.d.ts +118 -0
  96. package/dist/mcp/auth.js +245 -0
  97. package/dist/mcp/http.d.ts +20 -0
  98. package/dist/mcp/http.js +229 -0
  99. package/dist/mcp/index.d.ts +3 -0
  100. package/dist/mcp/index.js +8 -0
  101. package/dist/mcp/server.d.ts +37 -0
  102. package/dist/mcp/server.js +413 -0
  103. package/dist/probe/compute-fingerprint.d.ts +27 -0
  104. package/dist/probe/compute-fingerprint.js +65 -0
  105. package/dist/probe/parse-frames.d.ts +21 -0
  106. package/dist/probe/parse-frames.js +57 -0
  107. package/dist/probe/resolve-sourcemaps.d.ts +25 -0
  108. package/dist/probe/resolve-sourcemaps.js +281 -0
  109. package/dist/state/StateStore.d.ts +11 -0
  110. package/dist/state/StateStore.js +78 -0
  111. package/dist/telemetry/TelemetryReporter.d.ts +49 -0
  112. package/dist/telemetry/TelemetryReporter.js +451 -0
  113. package/dist/types/index.d.ts +373 -0
  114. package/dist/types/index.js +2 -0
  115. package/package.json +148 -0
  116. package/packages/cache/README.md +114 -0
  117. package/packages/cache/dist/CacheClient.d.ts +26 -0
  118. package/packages/cache/dist/CacheClient.d.ts.map +1 -0
  119. package/packages/cache/dist/CacheClient.js +174 -0
  120. package/packages/cache/dist/CacheClient.js.map +1 -0
  121. package/packages/cache/dist/CacheFileStore.d.ts +45 -0
  122. package/packages/cache/dist/CacheFileStore.d.ts.map +1 -0
  123. package/packages/cache/dist/CacheFileStore.js +446 -0
  124. package/packages/cache/dist/CacheFileStore.js.map +1 -0
  125. package/packages/cache/dist/CachePersistence.d.ts +9 -0
  126. package/packages/cache/dist/CachePersistence.d.ts.map +1 -0
  127. package/packages/cache/dist/CachePersistence.js +67 -0
  128. package/packages/cache/dist/CachePersistence.js.map +1 -0
  129. package/packages/cache/dist/CachePrimary.d.ts +25 -0
  130. package/packages/cache/dist/CachePrimary.d.ts.map +1 -0
  131. package/packages/cache/dist/CachePrimary.js +155 -0
  132. package/packages/cache/dist/CachePrimary.js.map +1 -0
  133. package/packages/cache/dist/CacheStore.d.ts +50 -0
  134. package/packages/cache/dist/CacheStore.d.ts.map +1 -0
  135. package/packages/cache/dist/CacheStore.js +271 -0
  136. package/packages/cache/dist/CacheStore.js.map +1 -0
  137. package/packages/cache/dist/constants.d.ts +6 -0
  138. package/packages/cache/dist/constants.d.ts.map +1 -0
  139. package/packages/cache/dist/constants.js +9 -0
  140. package/packages/cache/dist/constants.js.map +1 -0
  141. package/packages/cache/dist/index.d.ts +16 -0
  142. package/packages/cache/dist/index.d.ts.map +1 -0
  143. package/packages/cache/dist/index.js +86 -0
  144. package/packages/cache/dist/index.js.map +1 -0
  145. package/packages/cache/dist/serialize.d.ts +9 -0
  146. package/packages/cache/dist/serialize.d.ts.map +1 -0
  147. package/packages/cache/dist/serialize.js +40 -0
  148. package/packages/cache/dist/serialize.js.map +1 -0
  149. package/packages/cache/dist/types.d.ts +123 -0
  150. package/packages/cache/dist/types.d.ts.map +1 -0
  151. package/packages/cache/dist/types.js +2 -0
  152. package/packages/cache/dist/types.js.map +1 -0
  153. package/packages/cache/package.json +27 -0
  154. package/packages/cache/src/CacheClient.ts +227 -0
  155. package/packages/cache/src/CacheFileStore.ts +528 -0
  156. package/packages/cache/src/CachePersistence.ts +89 -0
  157. package/packages/cache/src/CachePrimary.ts +172 -0
  158. package/packages/cache/src/CacheStore.ts +308 -0
  159. package/packages/cache/src/constants.ts +10 -0
  160. package/packages/cache/src/index.ts +100 -0
  161. package/packages/cache/src/serialize.ts +49 -0
  162. package/packages/cache/src/types.ts +156 -0
  163. package/packages/cache/tsconfig.json +18 -0
  164. package/packages/cache/tsconfig.tsbuildinfo +1 -0
  165. package/packages/next/README.md +166 -0
  166. package/packages/next/dist/error-capture.d.ts +34 -0
  167. package/packages/next/dist/error-capture.d.ts.map +1 -0
  168. package/packages/next/dist/error-capture.js +130 -0
  169. package/packages/next/dist/error-capture.js.map +1 -0
  170. package/packages/next/dist/error-handler.d.ts +10 -0
  171. package/packages/next/dist/error-handler.d.ts.map +1 -0
  172. package/packages/next/dist/error-handler.js +186 -0
  173. package/packages/next/dist/error-handler.js.map +1 -0
  174. package/packages/next/dist/isr-cache.d.ts +9 -0
  175. package/packages/next/dist/isr-cache.d.ts.map +1 -0
  176. package/packages/next/dist/isr-cache.js +86 -0
  177. package/packages/next/dist/isr-cache.js.map +1 -0
  178. package/packages/next/dist/stream.d.ts +5 -0
  179. package/packages/next/dist/stream.d.ts.map +1 -0
  180. package/packages/next/dist/stream.js +22 -0
  181. package/packages/next/dist/stream.js.map +1 -0
  182. package/packages/next/dist/types.d.ts +33 -0
  183. package/packages/next/dist/types.d.ts.map +1 -0
  184. package/packages/next/dist/types.js +6 -0
  185. package/packages/next/dist/types.js.map +1 -0
  186. package/packages/next/dist/use-cache.d.ts +4 -0
  187. package/packages/next/dist/use-cache.d.ts.map +1 -0
  188. package/packages/next/dist/use-cache.js +86 -0
  189. package/packages/next/dist/use-cache.js.map +1 -0
  190. package/packages/next/dist/utils.d.ts +32 -0
  191. package/packages/next/dist/utils.d.ts.map +1 -0
  192. package/packages/next/dist/utils.js +88 -0
  193. package/packages/next/dist/utils.js.map +1 -0
  194. package/packages/next/package.json +52 -0
  195. package/packages/next/src/error-capture.ts +177 -0
  196. package/packages/next/src/error-handler.ts +221 -0
  197. package/packages/next/src/isr-cache.ts +100 -0
  198. package/packages/next/src/stream.ts +23 -0
  199. package/packages/next/src/types.ts +33 -0
  200. package/packages/next/src/use-cache.ts +99 -0
  201. package/packages/next/src/utils.ts +102 -0
  202. package/packages/next/tsconfig.json +19 -0
  203. package/packages/next/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,451 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import { arch, cpus, hostname, platform, totalmem } from 'node:os';
3
+ import { getAgentName } from '../agent-name.js';
4
+ import { TELEMETRY_FLUSH_TIMEOUT, TELEMETRY_LOG_FLUSH_MAX_LINES, TELEMETRY_LOG_MAX_LINE_LENGTH, TELEMETRY_LOG_RING_SIZE, TELEMETRY_MAX_BATCH_SIZE, TELEMETRY_METRICS_INTERVAL, TELEMETRY_REQUEST_TIMEOUT, } from '../constants.js';
5
+ import { getMachineId } from '../machine-id.js';
6
+ import { computeFingerprint, parseFunctionName } from '../probe/compute-fingerprint.js';
7
+ import { resolveSourceMaps } from '../probe/resolve-sourcemaps.js';
8
+ export class TelemetryReporter extends EventEmitter {
9
+ config;
10
+ orchestrator;
11
+ events = [];
12
+ metrics = [];
13
+ errors = [];
14
+ logRings = new Map();
15
+ logFlushBuffer = [];
16
+ logFlushDropped = 0;
17
+ timer = null;
18
+ hostName;
19
+ agentName;
20
+ machineId;
21
+ hostInfo;
22
+ _deployStatus = null;
23
+ configStore;
24
+ alertEvaluator;
25
+ mcpCapable = false;
26
+ /** Previous cumulative cache counters per worker — keyed by "processName:workerId" */
27
+ prevCacheCounters = new Map();
28
+ constructor(config, orchestrator, configStore, alertEvaluator) {
29
+ super();
30
+ this.config = config;
31
+ this.orchestrator = orchestrator;
32
+ this.configStore = configStore ?? null;
33
+ this.alertEvaluator = alertEvaluator ?? null;
34
+ this.hostName = hostname();
35
+ this.agentName = getAgentName();
36
+ this.machineId = getMachineId();
37
+ this.hostInfo = {
38
+ os: platform(),
39
+ arch: arch(),
40
+ nodeVersion: process.version,
41
+ cpuCount: cpus().length,
42
+ totalMemory: totalmem(),
43
+ };
44
+ }
45
+ start() {
46
+ this.bindEvents();
47
+ this.timer = setInterval(() => {
48
+ void this.collectAndFlush();
49
+ }, TELEMETRY_METRICS_INTERVAL);
50
+ this.timer.unref();
51
+ }
52
+ async shutdown() {
53
+ if (this.timer) {
54
+ clearInterval(this.timer);
55
+ this.timer = null;
56
+ }
57
+ await Promise.race([
58
+ this.collectAndFlush(),
59
+ new Promise((resolve) => setTimeout(resolve, TELEMETRY_FLUSH_TIMEOUT).unref()),
60
+ ]);
61
+ }
62
+ bindEvents() {
63
+ this.orchestrator.on('process:start', (data) => {
64
+ this.pushEvent('process:start', data.processName, { processId: data.processId });
65
+ });
66
+ this.orchestrator.on('process:stop', (data) => {
67
+ this.pushEvent('process:stop', data.processName, { processId: data.processId });
68
+ });
69
+ this.orchestrator.on('reload:start', (data) => {
70
+ this.pushEvent('process:reload', data.processName, { processId: data.processId });
71
+ });
72
+ this.orchestrator.on('reload:complete', (data) => {
73
+ this.pushEvent('process:reloaded', data.processName, { processId: data.processId });
74
+ });
75
+ this.orchestrator.on('worker:ready', (data) => {
76
+ this.pushEvent('worker:ready', data.processName, { workerId: data.workerId });
77
+ });
78
+ this.orchestrator.on('worker:exit', (data) => {
79
+ if (data.code !== 0 && data.code !== null) {
80
+ const lastLogs = this.getWorkerLogs(data.processName, data.workerId);
81
+ this.pushEvent('worker:crash', data.processName, {
82
+ workerId: data.workerId,
83
+ exitCode: data.code,
84
+ signal: data.signal,
85
+ lastLogs,
86
+ });
87
+ this.clearWorkerLogs(data.processName, data.workerId);
88
+ }
89
+ else {
90
+ this.pushEvent('worker:exit', data.processName, {
91
+ workerId: data.workerId,
92
+ exitCode: data.code,
93
+ signal: data.signal,
94
+ });
95
+ }
96
+ });
97
+ this.orchestrator.on('worker:maxRestarts', (data) => {
98
+ this.pushEvent('worker:maxRestarts', data.processName, { workerId: data.workerId });
99
+ });
100
+ this.orchestrator.on('worker:memoryRestart', (data) => {
101
+ this.pushEvent('worker:memoryRestart', data.processName, {
102
+ workerId: data.workerId,
103
+ details: { memory: data.memory, limit: data.limit },
104
+ });
105
+ });
106
+ this.orchestrator.on('log', (data) => {
107
+ // Ring buffer for crash/error context (last N lines per worker)
108
+ let processRings = this.logRings.get(data.processName);
109
+ if (!processRings) {
110
+ processRings = new Map();
111
+ this.logRings.set(data.processName, processRings);
112
+ }
113
+ let ring = processRings.get(data.workerId);
114
+ if (!ring) {
115
+ ring = [];
116
+ processRings.set(data.workerId, ring);
117
+ }
118
+ ring.push(data.data);
119
+ if (ring.length > TELEMETRY_LOG_RING_SIZE) {
120
+ ring.shift();
121
+ }
122
+ // Flush buffer for log ingestion (drained each flush cycle)
123
+ // Skip primary process logs (workerId -1) — they're internal ClusterWrapper messages
124
+ if (data.workerId < 0)
125
+ return;
126
+ const line = data.data.length > TELEMETRY_LOG_MAX_LINE_LENGTH
127
+ ? data.data.slice(0, TELEMETRY_LOG_MAX_LINE_LENGTH) + '...'
128
+ : data.data;
129
+ // Detect level: stderr defaults to error, but downgrade to warn if the
130
+ // line contains warn/warning without error (e.g. console.warn output)
131
+ let level = data.type === 'err' ? 'error' : 'info';
132
+ if (level === 'error') {
133
+ const prefix = line.slice(0, 80).toLowerCase();
134
+ if (/\bwarn(ing)?\b/.test(prefix) && !/\berror\b/.test(prefix)) {
135
+ level = 'warn';
136
+ }
137
+ }
138
+ this.logFlushBuffer.push({
139
+ processName: data.processName,
140
+ workerId: data.workerId,
141
+ timestamp: Date.now(),
142
+ level,
143
+ message: line,
144
+ });
145
+ // If buffer grows too large between flushes, start dropping oldest
146
+ const maxBuffer = TELEMETRY_LOG_FLUSH_MAX_LINES * 10;
147
+ if (this.logFlushBuffer.length > maxBuffer) {
148
+ const excess = this.logFlushBuffer.length - maxBuffer;
149
+ this.logFlushBuffer.splice(0, excess);
150
+ this.logFlushDropped += excess;
151
+ }
152
+ });
153
+ this.orchestrator.on('worker:error:captured', (data) => {
154
+ const err = data.error;
155
+ const lastLogs = this.getWorkerLogs(data.processName, data.workerId);
156
+ const message = err.message || '';
157
+ const errorEvent = {
158
+ processName: data.processName,
159
+ workerId: data.workerId,
160
+ timestamp: err.timestamp || Date.now(),
161
+ errorType: err.errorType,
162
+ name: err.name || 'Error',
163
+ message,
164
+ stack: err.stack || '',
165
+ fingerprint: err.fingerprint || '',
166
+ sourceContext: err.sourceContext || null,
167
+ topFrame: err.topFrame || null,
168
+ diagnostics: err.diagnostics || null,
169
+ nodeVersion: err.nodeVersion || '',
170
+ pid: err.pid || 0,
171
+ lastLogs,
172
+ url: err.url || undefined,
173
+ userAgent: err.userAgent || undefined,
174
+ };
175
+ // Resolve source maps for bundled/minified code
176
+ const originalTopFrame = errorEvent.topFrame;
177
+ const resolved = resolveSourceMaps(errorEvent.sourceContext, errorEvent.topFrame);
178
+ errorEvent.sourceContext = resolved.sourceContext;
179
+ errorEvent.topFrame = resolved.topFrame;
180
+ errorEvent.resolved = resolved.resolved;
181
+ // Compute stable fingerprint: prefer source map function name, fall back to raw stack
182
+ let functionName = resolved.resolvedFunctionName;
183
+ if (!functionName && originalTopFrame) {
184
+ functionName = parseFunctionName(errorEvent.stack, originalTopFrame.file, originalTopFrame.line);
185
+ }
186
+ const effectiveTop = errorEvent.topFrame;
187
+ errorEvent.fingerprint = computeFingerprint({
188
+ errorName: errorEvent.name,
189
+ message,
190
+ file: effectiveTop?.file ?? '',
191
+ line: effectiveTop?.line ?? 0,
192
+ functionName,
193
+ });
194
+ this.errors.push(errorEvent);
195
+ if (this.errors.length >= TELEMETRY_MAX_BATCH_SIZE) {
196
+ void this.collectAndFlush();
197
+ }
198
+ });
199
+ }
200
+ /**
201
+ * Get logs for a specific worker. In cluster mode, worker stdout/stderr flows
202
+ * through the primary process (workerId -1), so if the worker-specific ring
203
+ * is empty we fall back to the primary's ring.
204
+ */
205
+ getWorkerLogs(processName, workerId) {
206
+ const processRings = this.logRings.get(processName);
207
+ if (!processRings)
208
+ return [];
209
+ const workerRing = processRings.get(workerId);
210
+ if (workerRing && workerRing.length > 0)
211
+ return workerRing.slice();
212
+ // Fallback: cluster primary ring (workerId -1) captures all worker output
213
+ const primaryRing = processRings.get(-1);
214
+ return primaryRing?.slice() ?? [];
215
+ }
216
+ clearWorkerLogs(processName, workerId) {
217
+ const processRings = this.logRings.get(processName);
218
+ if (!processRings)
219
+ return;
220
+ processRings.delete(workerId);
221
+ // Also clear primary ring on crash — it contained this worker's logs
222
+ if (workerId !== -1)
223
+ processRings.delete(-1);
224
+ }
225
+ pushEvent(type, processName, fields) {
226
+ this.events.push({
227
+ type,
228
+ processName,
229
+ timestamp: Date.now(),
230
+ ...fields,
231
+ });
232
+ if (this.events.length >= TELEMETRY_MAX_BATCH_SIZE) {
233
+ void this.collectAndFlush();
234
+ }
235
+ }
236
+ emitEvent(type, processName, fields) {
237
+ this.pushEvent(type, processName, fields);
238
+ }
239
+ setDeployStatus(status) {
240
+ this._deployStatus = status;
241
+ }
242
+ setMcpCapable(v) {
243
+ this.mcpCapable = v;
244
+ }
245
+ async collectAndFlush() {
246
+ // Collect metrics snapshot
247
+ const processList = this.orchestrator.list();
248
+ const now = Date.now();
249
+ const metricsSnapshots = processList.map((p) => ({
250
+ processName: p.name,
251
+ processId: p.id,
252
+ execMode: p.execMode,
253
+ status: p.status,
254
+ workers: p.workers.map((w) => {
255
+ const base = {
256
+ id: w.id,
257
+ pid: w.pid,
258
+ cpu: w.cpu,
259
+ memory: w.memory,
260
+ uptime: w.uptime,
261
+ restarts: w.restarts,
262
+ crashes: w.crashes,
263
+ status: w.status,
264
+ stale: w.stale,
265
+ heapUsed: w.heapUsed,
266
+ heapTotal: w.heapTotal,
267
+ external: w.external,
268
+ arrayBuffers: w.arrayBuffers,
269
+ eventLoopLag: w.eventLoopLag,
270
+ eventLoopLagP95: w.eventLoopLagP95,
271
+ activeHandles: w.activeHandles,
272
+ };
273
+ if (w.cacheSize === undefined)
274
+ return base;
275
+ // Compute per-flush deltas for cumulative counters
276
+ const key = `${p.name}:${w.id}`;
277
+ const prev = this.prevCacheCounters.get(key);
278
+ const curHits = w.cacheHits ?? 0;
279
+ const curMisses = w.cacheMisses ?? 0;
280
+ const deltaHits = prev ? Math.max(0, curHits - prev.hits) : curHits;
281
+ const deltaMisses = prev ? Math.max(0, curMisses - prev.misses) : curMisses;
282
+ this.prevCacheCounters.set(key, { hits: curHits, misses: curMisses });
283
+ return {
284
+ ...base,
285
+ cacheSize: w.cacheSize,
286
+ cacheTotalBytes: w.cacheTotalBytes,
287
+ cacheHits: deltaHits,
288
+ cacheMisses: deltaMisses,
289
+ cacheHitRate: w.cacheHitRate,
290
+ };
291
+ }),
292
+ timestamp: now,
293
+ }));
294
+ // Drain flush buffer — cap at TELEMETRY_LOG_FLUSH_MAX_LINES per worker
295
+ const logsToSend = this.drainLogFlushBuffer();
296
+ // Buffer swap
297
+ const eventsToSend = this.events;
298
+ const metricsToSend = [...this.metrics, ...metricsSnapshots];
299
+ const errorsToSend = this.errors;
300
+ this.events = [];
301
+ this.metrics = [];
302
+ this.errors = [];
303
+ // Drain alert events before the emptiness check
304
+ const alertsToSend = this.alertEvaluator?.drainAlerts() ?? [];
305
+ if (eventsToSend.length === 0 &&
306
+ metricsToSend.length === 0 &&
307
+ errorsToSend.length === 0 &&
308
+ logsToSend.length === 0 &&
309
+ alertsToSend.length === 0) {
310
+ return;
311
+ }
312
+ const daemonStatus = this.orchestrator.getDaemonStatus();
313
+ const payload = {
314
+ daemonPid: daemonStatus.pid,
315
+ daemonUptime: daemonStatus.uptime,
316
+ hostname: this.hostName,
317
+ agentName: this.agentName,
318
+ machineId: this.machineId,
319
+ host: this.hostInfo,
320
+ events: eventsToSend,
321
+ metrics: metricsToSend,
322
+ errors: errorsToSend,
323
+ logs: logsToSend,
324
+ sentAt: Date.now(),
325
+ };
326
+ // Include config hash for sync
327
+ const configHash = this.configStore?.getHash();
328
+ if (configHash !== undefined) {
329
+ payload.configHash = configHash;
330
+ }
331
+ // Include MCP capability flag
332
+ if (this.mcpCapable) {
333
+ payload.mcpCapable = true;
334
+ }
335
+ // Include alert events
336
+ if (alertsToSend.length > 0) {
337
+ payload.alerts = alertsToSend;
338
+ }
339
+ // Include deploy status if active
340
+ if (this._deployStatus) {
341
+ payload.deployStatus = this._deployStatus;
342
+ const terminalPhases = ['success', 'failed', 'rolled_back'];
343
+ if (terminalPhases.includes(this._deployStatus.phase)) {
344
+ this._deployStatus = null; // Clear after terminal state is sent
345
+ }
346
+ }
347
+ try {
348
+ const response = await fetch(`${this.config.apiHost}/api/v1/ingest/telemetry`, {
349
+ method: 'POST',
350
+ headers: {
351
+ 'Content-Type': 'application/json',
352
+ Authorization: `Bearer ${this.config.apiKey}`,
353
+ },
354
+ body: JSON.stringify(payload),
355
+ signal: AbortSignal.timeout(TELEMETRY_REQUEST_TIMEOUT),
356
+ });
357
+ if (!response.ok) {
358
+ const body = await response.text().catch(() => '');
359
+ console.error(`Telemetry flush failed: ${response.status} ${response.statusText} ${body}`);
360
+ this.restoreBuffers(eventsToSend, metricsToSend, errorsToSend, logsToSend, alertsToSend);
361
+ }
362
+ else {
363
+ // Parse response for pending commands and config sync
364
+ try {
365
+ const responseBody = (await response.json());
366
+ if (responseBody.has_commands) {
367
+ this.emit('commands:pending');
368
+ }
369
+ if (responseBody.config && responseBody.config_hash) {
370
+ this.configStore?.update(responseBody.config, responseBody.config_hash);
371
+ }
372
+ }
373
+ catch {
374
+ // Ignore JSON parse errors on response
375
+ }
376
+ }
377
+ }
378
+ catch (err) {
379
+ console.error('Telemetry flush error:', err instanceof Error ? err.message : err);
380
+ this.restoreBuffers(eventsToSend, metricsToSend, errorsToSend, logsToSend, alertsToSend);
381
+ }
382
+ }
383
+ /**
384
+ * Drain the log flush buffer, capping at TELEMETRY_LOG_FLUSH_MAX_LINES per
385
+ * worker. When lines are dropped, prepend a truncation marker.
386
+ */
387
+ drainLogFlushBuffer() {
388
+ const buffer = this.logFlushBuffer;
389
+ const dropped = this.logFlushDropped;
390
+ this.logFlushBuffer = [];
391
+ this.logFlushDropped = 0;
392
+ if (buffer.length === 0)
393
+ return [];
394
+ // Group by processName:workerId, keep the last N lines per worker
395
+ const groups = new Map();
396
+ for (const entry of buffer) {
397
+ const key = `${entry.processName}:${entry.workerId}`;
398
+ let group = groups.get(key);
399
+ if (!group) {
400
+ group = [];
401
+ groups.set(key, group);
402
+ }
403
+ group.push(entry);
404
+ }
405
+ const result = [];
406
+ for (const [, entries] of groups) {
407
+ if (entries.length > TELEMETRY_LOG_FLUSH_MAX_LINES) {
408
+ const excess = entries.length - TELEMETRY_LOG_FLUSH_MAX_LINES;
409
+ const kept = entries.slice(-TELEMETRY_LOG_FLUSH_MAX_LINES);
410
+ // Prepend truncation marker
411
+ const first = kept[0];
412
+ result.push({
413
+ processName: first.processName,
414
+ workerId: first.workerId,
415
+ timestamp: first.timestamp - 1,
416
+ level: 'info',
417
+ message: `\u22EF ${excess} log lines truncated`,
418
+ });
419
+ result.push(...kept);
420
+ }
421
+ else {
422
+ result.push(...entries);
423
+ }
424
+ }
425
+ // If there were globally dropped lines (buffer overflow between flushes)
426
+ if (dropped > 0 && result.length > 0) {
427
+ const first = result[0];
428
+ result.unshift({
429
+ processName: first.processName,
430
+ workerId: first.workerId,
431
+ timestamp: first.timestamp - 1,
432
+ level: 'info',
433
+ message: `\u22EF ${dropped} log lines dropped (buffer overflow)`,
434
+ });
435
+ }
436
+ return result;
437
+ }
438
+ restoreBuffers(events, metrics, errors, logs, alerts) {
439
+ const maxSize = TELEMETRY_MAX_BATCH_SIZE * 2;
440
+ this.events = [...events, ...this.events].slice(-maxSize);
441
+ this.metrics = [...metrics, ...this.metrics].slice(-maxSize);
442
+ this.errors = [...errors, ...this.errors].slice(-maxSize);
443
+ // Restore logs back to flush buffer (prepend so they'll be sent first next time)
444
+ this.logFlushBuffer = [...logs, ...this.logFlushBuffer].slice(-maxSize);
445
+ // Re-queue unsent alerts back into the evaluator's buffer
446
+ if (alerts && alerts.length > 0 && this.alertEvaluator) {
447
+ this.alertEvaluator.restoreAlerts(alerts);
448
+ }
449
+ }
450
+ }
451
+ //# sourceMappingURL=TelemetryReporter.js.map