harnery 0.36.0 → 0.37.0

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 (311) hide show
  1. package/dist/commander.js +4 -0
  2. package/dist/commands/agents.d.ts.map +1 -1
  3. package/dist/commands/agents.js +87 -46
  4. package/dist/commands/artifacts.d.ts.map +1 -1
  5. package/dist/commands/artifacts.js +26 -1
  6. package/dist/commands/browse.d.ts.map +1 -1
  7. package/dist/commands/browse.js +11 -0
  8. package/dist/commands/diagnostics.d.ts +4 -0
  9. package/dist/commands/diagnostics.d.ts.map +1 -0
  10. package/dist/commands/diagnostics.js +229 -0
  11. package/dist/commands/files.d.ts +12 -0
  12. package/dist/commands/files.d.ts.map +1 -1
  13. package/dist/commands/files.js +54 -1
  14. package/dist/commands/governor.d.ts.map +1 -1
  15. package/dist/commands/governor.js +4 -0
  16. package/dist/commands/ledger-v3.d.ts.map +1 -1
  17. package/dist/commands/ledger-v3.js +71 -2
  18. package/dist/commands/qa-run.d.ts +4 -0
  19. package/dist/commands/qa-run.d.ts.map +1 -0
  20. package/dist/commands/qa-run.js +141 -0
  21. package/dist/commands/resources.d.ts +4 -0
  22. package/dist/commands/resources.d.ts.map +1 -0
  23. package/dist/commands/resources.js +29 -0
  24. package/dist/commands/supervisor.d.ts +4 -0
  25. package/dist/commands/supervisor.d.ts.map +1 -0
  26. package/dist/commands/supervisor.js +101 -0
  27. package/dist/commands/web.d.ts.map +1 -1
  28. package/dist/commands/web.js +17 -0
  29. package/dist/commands/workflow.d.ts.map +1 -1
  30. package/dist/commands/workflow.js +4 -0
  31. package/dist/core/agents/cli.js +7 -3
  32. package/dist/core/agents/health.d.ts +17 -0
  33. package/dist/core/agents/health.d.ts.map +1 -0
  34. package/dist/core/agents/health.js +109 -0
  35. package/dist/core/agents/mailbox.d.ts +92 -0
  36. package/dist/core/agents/mailbox.d.ts.map +1 -0
  37. package/dist/core/agents/mailbox.js +325 -0
  38. package/dist/core/agents/reconcile-coordination-v3.d.ts +44 -0
  39. package/dist/core/agents/reconcile-coordination-v3.d.ts.map +1 -0
  40. package/dist/core/agents/reconcile-coordination-v3.js +58 -0
  41. package/dist/core/agents/render/prompt-context.d.ts +5 -0
  42. package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
  43. package/dist/core/agents/render/prompt-context.js +34 -13
  44. package/dist/core/agents/render/session-context.d.ts +8 -0
  45. package/dist/core/agents/render/session-context.d.ts.map +1 -1
  46. package/dist/core/agents/render/session-context.js +34 -3
  47. package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -1
  48. package/dist/core/agents/rules/commit-conflict.js +50 -1
  49. package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
  50. package/dist/core/agents/rules/stop-hook.js +6 -10
  51. package/dist/core/agents/session-finalizer-v3.js +4 -1
  52. package/dist/core/artifacts/index.d.ts +26 -1
  53. package/dist/core/artifacts/index.d.ts.map +1 -1
  54. package/dist/core/artifacts/index.js +146 -6
  55. package/dist/core/config.d.ts +12 -0
  56. package/dist/core/config.d.ts.map +1 -1
  57. package/dist/core/config.js +43 -0
  58. package/dist/core/diagnostics/advice.d.ts +9 -0
  59. package/dist/core/diagnostics/advice.d.ts.map +1 -0
  60. package/dist/core/diagnostics/advice.js +112 -0
  61. package/dist/core/diagnostics/bundle.d.ts +35 -0
  62. package/dist/core/diagnostics/bundle.d.ts.map +1 -0
  63. package/dist/core/diagnostics/bundle.js +874 -0
  64. package/dist/core/diagnostics/comparison.d.ts +5 -0
  65. package/dist/core/diagnostics/comparison.d.ts.map +1 -0
  66. package/dist/core/diagnostics/comparison.js +444 -0
  67. package/dist/core/diagnostics/contract.d.ts +245 -0
  68. package/dist/core/diagnostics/contract.d.ts.map +1 -0
  69. package/dist/core/diagnostics/contract.js +18 -0
  70. package/dist/core/diagnostics/identity.d.ts +3 -0
  71. package/dist/core/diagnostics/identity.d.ts.map +1 -0
  72. package/dist/core/diagnostics/identity.js +10 -0
  73. package/dist/core/diagnostics/index.d.ts +8 -0
  74. package/dist/core/diagnostics/index.d.ts.map +1 -0
  75. package/dist/core/diagnostics/index.js +7 -0
  76. package/dist/core/diagnostics/replay.d.ts +8 -0
  77. package/dist/core/diagnostics/replay.d.ts.map +1 -0
  78. package/dist/core/diagnostics/replay.js +213 -0
  79. package/dist/core/diagnostics/sanitize.d.ts +9 -0
  80. package/dist/core/diagnostics/sanitize.d.ts.map +1 -0
  81. package/dist/core/diagnostics/sanitize.js +84 -0
  82. package/dist/core/events/legacy-storage/compression.d.ts +13 -0
  83. package/dist/core/events/legacy-storage/compression.d.ts.map +1 -0
  84. package/dist/core/events/legacy-storage/compression.js +107 -0
  85. package/dist/core/events/legacy-storage/index.d.ts +1 -0
  86. package/dist/core/events/legacy-storage/index.d.ts.map +1 -1
  87. package/dist/core/events/legacy-storage/index.js +1 -0
  88. package/dist/core/events/v3/archive-retention.d.ts +33 -0
  89. package/dist/core/events/v3/archive-retention.d.ts.map +1 -0
  90. package/dist/core/events/v3/archive-retention.js +195 -0
  91. package/dist/core/events/v3/capabilities.d.ts +1 -1
  92. package/dist/core/events/v3/capabilities.d.ts.map +1 -1
  93. package/dist/core/events/v3/capabilities.js +1 -0
  94. package/dist/core/events/v3/index.d.ts +1 -0
  95. package/dist/core/events/v3/index.d.ts.map +1 -1
  96. package/dist/core/events/v3/index.js +1 -0
  97. package/dist/core/events/v3/live-routing.d.ts.map +1 -1
  98. package/dist/core/events/v3/live-routing.js +1 -0
  99. package/dist/core/events/v3/producers/hook-base.d.ts +1 -1
  100. package/dist/core/events/v3/producers/hook-base.d.ts.map +1 -1
  101. package/dist/core/events/v3/producers/hook-base.js +13 -2
  102. package/dist/core/events/v3/producers/intake.d.ts.map +1 -1
  103. package/dist/core/events/v3/producers/intake.js +1 -0
  104. package/dist/core/events/v3/producers/recorder.d.ts +16 -0
  105. package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
  106. package/dist/core/events/v3/producers/recorder.js +88 -1
  107. package/dist/core/hooks/adapter/output.d.ts +8 -0
  108. package/dist/core/hooks/adapter/output.d.ts.map +1 -1
  109. package/dist/core/hooks/adapter/output.js +11 -1
  110. package/dist/core/hooks/cli.js +183 -192
  111. package/dist/core/hooks/health.d.ts +59 -0
  112. package/dist/core/hooks/health.d.ts.map +1 -0
  113. package/dist/core/hooks/health.js +84 -0
  114. package/dist/core/resources/contract.d.ts +90 -0
  115. package/dist/core/resources/contract.d.ts.map +1 -0
  116. package/dist/core/resources/contract.js +2 -0
  117. package/dist/core/resources/index.d.ts +6 -0
  118. package/dist/core/resources/index.d.ts.map +1 -0
  119. package/dist/core/resources/index.js +5 -0
  120. package/dist/core/resources/sampler.d.ts +27 -0
  121. package/dist/core/resources/sampler.d.ts.map +1 -0
  122. package/dist/core/resources/sampler.js +357 -0
  123. package/dist/core/resources/service-status.d.ts +3 -0
  124. package/dist/core/resources/service-status.d.ts.map +1 -0
  125. package/dist/core/resources/service-status.js +48 -0
  126. package/dist/core/resources/service.d.ts +26 -0
  127. package/dist/core/resources/service.d.ts.map +1 -0
  128. package/dist/core/resources/service.js +292 -0
  129. package/dist/core/resources/storage.d.ts +13 -0
  130. package/dist/core/resources/storage.d.ts.map +1 -0
  131. package/dist/core/resources/storage.js +30 -0
  132. package/dist/core/storage/atomic-json.d.ts +3 -0
  133. package/dist/core/storage/atomic-json.d.ts.map +1 -0
  134. package/dist/core/storage/atomic-json.js +15 -0
  135. package/dist/core/storage/builtins.d.ts.map +1 -1
  136. package/dist/core/storage/builtins.js +45 -6
  137. package/dist/core/storage/logger.d.ts +2 -0
  138. package/dist/core/storage/logger.d.ts.map +1 -1
  139. package/dist/core/storage/logger.js +2 -0
  140. package/dist/core/storage/query.d.ts +14 -0
  141. package/dist/core/storage/query.d.ts.map +1 -1
  142. package/dist/core/storage/query.js +51 -0
  143. package/dist/core/supervisor/activity.d.ts +4 -0
  144. package/dist/core/supervisor/activity.d.ts.map +1 -0
  145. package/dist/core/supervisor/activity.js +141 -0
  146. package/dist/core/supervisor/contract.d.ts +268 -0
  147. package/dist/core/supervisor/contract.d.ts.map +1 -0
  148. package/dist/core/supervisor/contract.js +35 -0
  149. package/dist/core/supervisor/explanations.d.ts +4 -0
  150. package/dist/core/supervisor/explanations.d.ts.map +1 -0
  151. package/dist/core/supervisor/explanations.js +71 -0
  152. package/dist/core/supervisor/findings.d.ts +20 -0
  153. package/dist/core/supervisor/findings.d.ts.map +1 -0
  154. package/dist/core/supervisor/findings.js +399 -0
  155. package/dist/core/supervisor/history.d.ts +9 -0
  156. package/dist/core/supervisor/history.d.ts.map +1 -0
  157. package/dist/core/supervisor/history.js +46 -0
  158. package/dist/core/supervisor/hook-health-alerts.d.ts +14 -0
  159. package/dist/core/supervisor/hook-health-alerts.d.ts.map +1 -0
  160. package/dist/core/supervisor/hook-health-alerts.js +70 -0
  161. package/dist/core/supervisor/hook-health-storage.d.ts +5 -0
  162. package/dist/core/supervisor/hook-health-storage.d.ts.map +1 -0
  163. package/dist/core/supervisor/hook-health-storage.js +24 -0
  164. package/dist/core/supervisor/hook-health.d.ts +72 -0
  165. package/dist/core/supervisor/hook-health.d.ts.map +1 -0
  166. package/dist/core/supervisor/hook-health.js +203 -0
  167. package/dist/core/supervisor/hooks.d.ts +10 -0
  168. package/dist/core/supervisor/hooks.d.ts.map +1 -0
  169. package/dist/core/supervisor/hooks.js +28 -0
  170. package/dist/core/supervisor/index.d.ts +15 -0
  171. package/dist/core/supervisor/index.d.ts.map +1 -0
  172. package/dist/core/supervisor/index.js +14 -0
  173. package/dist/core/supervisor/log-feed.d.ts +9 -0
  174. package/dist/core/supervisor/log-feed.d.ts.map +1 -0
  175. package/dist/core/supervisor/log-feed.js +103 -0
  176. package/dist/core/supervisor/service.d.ts +29 -0
  177. package/dist/core/supervisor/service.d.ts.map +1 -0
  178. package/dist/core/supervisor/service.js +450 -0
  179. package/dist/core/supervisor/services.d.ts +11 -0
  180. package/dist/core/supervisor/services.d.ts.map +1 -0
  181. package/dist/core/supervisor/services.js +155 -0
  182. package/dist/core/supervisor/status.d.ts +3 -0
  183. package/dist/core/supervisor/status.d.ts.map +1 -0
  184. package/dist/core/supervisor/status.js +48 -0
  185. package/dist/core/supervisor/storage.d.ts +30 -0
  186. package/dist/core/supervisor/storage.d.ts.map +1 -0
  187. package/dist/core/supervisor/storage.js +73 -0
  188. package/dist/core/supervisor/timeline.d.ts +3 -0
  189. package/dist/core/supervisor/timeline.d.ts.map +1 -0
  190. package/dist/core/supervisor/timeline.js +87 -0
  191. package/dist/core/workflow/admission.d.ts +23 -0
  192. package/dist/core/workflow/admission.d.ts.map +1 -0
  193. package/dist/core/workflow/admission.js +136 -0
  194. package/dist/core/workflow/engine.d.ts.map +1 -1
  195. package/dist/core/workflow/engine.js +77 -2
  196. package/dist/core/workflow/index.d.ts +3 -2
  197. package/dist/core/workflow/index.d.ts.map +1 -1
  198. package/dist/core/workflow/index.js +2 -1
  199. package/dist/core/workflow/proof.d.ts +2 -1
  200. package/dist/core/workflow/proof.d.ts.map +1 -1
  201. package/dist/core/workflow/proof.js +39 -1
  202. package/dist/core/workflow/run-state.d.ts +3 -2
  203. package/dist/core/workflow/run-state.d.ts.map +1 -1
  204. package/dist/core/workflow/run-state.js +8 -1
  205. package/dist/core/workflow/types.d.ts +33 -1
  206. package/dist/core/workflow/types.d.ts.map +1 -1
  207. package/dist/core/workflow/types.js +2 -1
  208. package/dist/lib/browser/client.d.ts +5 -0
  209. package/dist/lib/browser/client.d.ts.map +1 -1
  210. package/dist/lib/browser/client.js +1 -0
  211. package/dist/lib/browser/index.d.ts +2 -0
  212. package/dist/lib/browser/index.d.ts.map +1 -1
  213. package/dist/lib/browser/index.js +2 -0
  214. package/dist/lib/browser/qa-run-contracts.d.ts +173 -0
  215. package/dist/lib/browser/qa-run-contracts.d.ts.map +1 -0
  216. package/dist/lib/browser/qa-run-contracts.js +226 -0
  217. package/dist/lib/browser/qa-run.d.ts +51 -0
  218. package/dist/lib/browser/qa-run.d.ts.map +1 -0
  219. package/dist/lib/browser/qa-run.js +684 -0
  220. package/dist/lib/coord-root-id.d.ts +9 -0
  221. package/dist/lib/coord-root-id.d.ts.map +1 -0
  222. package/dist/lib/coord-root-id.js +17 -0
  223. package/dist/lib/instructions/apply.d.ts +3 -1
  224. package/dist/lib/instructions/apply.d.ts.map +1 -1
  225. package/dist/lib/instructions/apply.js +74 -3
  226. package/dist/lib/instructions/templates.d.ts.map +1 -1
  227. package/dist/lib/instructions/templates.js +6 -0
  228. package/package.json +12 -1
  229. package/src/commander.ts +26 -0
  230. package/src/commands/agents.ts +102 -49
  231. package/src/commands/artifacts.ts +40 -2
  232. package/src/commands/browse.ts +15 -0
  233. package/src/commands/diagnostics.ts +271 -0
  234. package/src/commands/files.ts +102 -2
  235. package/src/commands/governor.ts +8 -0
  236. package/src/commands/ledger-v3.ts +82 -0
  237. package/src/commands/qa-run.ts +184 -0
  238. package/src/commands/resources.ts +38 -0
  239. package/src/commands/supervisor.ts +141 -0
  240. package/src/commands/web.ts +19 -0
  241. package/src/commands/workflow.ts +8 -0
  242. package/src/core/agents/cli.ts +7 -3
  243. package/src/core/agents/health.ts +142 -0
  244. package/src/core/agents/mailbox.ts +373 -0
  245. package/src/core/agents/reconcile-coordination-v3.ts +77 -0
  246. package/src/core/agents/render/prompt-context.ts +42 -12
  247. package/src/core/agents/render/session-context.ts +37 -3
  248. package/src/core/agents/rules/commit-conflict.ts +58 -1
  249. package/src/core/agents/rules/stop-hook.ts +6 -12
  250. package/src/core/agents/session-finalizer-v3.ts +4 -1
  251. package/src/core/artifacts/index.ts +196 -10
  252. package/src/core/config.ts +111 -2
  253. package/src/core/diagnostics/advice.ts +147 -0
  254. package/src/core/diagnostics/bundle.ts +1142 -0
  255. package/src/core/diagnostics/comparison.ts +565 -0
  256. package/src/core/diagnostics/contract.ts +311 -0
  257. package/src/core/diagnostics/identity.ts +11 -0
  258. package/src/core/diagnostics/index.ts +7 -0
  259. package/src/core/diagnostics/replay.ts +314 -0
  260. package/src/core/diagnostics/sanitize.ts +97 -0
  261. package/src/core/events/legacy-storage/compression.ts +130 -0
  262. package/src/core/events/legacy-storage/index.ts +1 -0
  263. package/src/core/events/v3/archive-retention.ts +251 -0
  264. package/src/core/events/v3/capabilities.ts +2 -0
  265. package/src/core/events/v3/index.ts +9 -0
  266. package/src/core/events/v3/live-routing.ts +1 -0
  267. package/src/core/events/v3/producers/hook-base.ts +14 -2
  268. package/src/core/events/v3/producers/intake.ts +1 -0
  269. package/src/core/events/v3/producers/recorder.ts +110 -1
  270. package/src/core/hooks/adapter/output.ts +12 -1
  271. package/src/core/hooks/cli.ts +192 -215
  272. package/src/core/hooks/health.ts +146 -0
  273. package/src/core/resources/contract.ts +98 -0
  274. package/src/core/resources/index.ts +5 -0
  275. package/src/core/resources/sampler.ts +458 -0
  276. package/src/core/resources/service-status.ts +62 -0
  277. package/src/core/resources/service.ts +337 -0
  278. package/src/core/resources/storage.ts +44 -0
  279. package/src/core/storage/atomic-json.ts +15 -0
  280. package/src/core/storage/builtins.ts +62 -6
  281. package/src/core/storage/logger.ts +2 -0
  282. package/src/core/storage/query.ts +58 -0
  283. package/src/core/supervisor/activity.ts +193 -0
  284. package/src/core/supervisor/contract.ts +312 -0
  285. package/src/core/supervisor/explanations.ts +104 -0
  286. package/src/core/supervisor/findings.ts +788 -0
  287. package/src/core/supervisor/history.ts +61 -0
  288. package/src/core/supervisor/hook-health-alerts.ts +108 -0
  289. package/src/core/supervisor/hook-health-storage.ts +25 -0
  290. package/src/core/supervisor/hook-health.ts +301 -0
  291. package/src/core/supervisor/hooks.ts +35 -0
  292. package/src/core/supervisor/index.ts +14 -0
  293. package/src/core/supervisor/log-feed.ts +131 -0
  294. package/src/core/supervisor/service.ts +511 -0
  295. package/src/core/supervisor/services.ts +203 -0
  296. package/src/core/supervisor/status.ts +59 -0
  297. package/src/core/supervisor/storage.ts +126 -0
  298. package/src/core/supervisor/timeline.ts +133 -0
  299. package/src/core/workflow/admission.ts +215 -0
  300. package/src/core/workflow/engine.ts +93 -1
  301. package/src/core/workflow/index.ts +10 -0
  302. package/src/core/workflow/proof.ts +54 -1
  303. package/src/core/workflow/run-state.ts +13 -1
  304. package/src/core/workflow/types.ts +39 -1
  305. package/src/lib/browser/client.ts +6 -0
  306. package/src/lib/browser/index.ts +29 -0
  307. package/src/lib/browser/qa-run-contracts.ts +380 -0
  308. package/src/lib/browser/qa-run.ts +784 -0
  309. package/src/lib/coord-root-id.ts +23 -0
  310. package/src/lib/instructions/apply.ts +81 -2
  311. package/src/lib/instructions/templates.ts +6 -0
@@ -0,0 +1,146 @@
1
+ import type { Adapter } from "../adapter.ts";
2
+ import { processLogger } from "../storage/logger.ts";
3
+
4
+ export const HOOK_HEALTH_EVENT = "agent_hook.completed" as const;
5
+ export const HOOK_HEALTH_RECEIPT_VERSION = 1 as const;
6
+ export const HOOK_HEALTH_ERROR_PHASE_LIMIT = 8;
7
+
8
+ export type HookHealthOutcome = "completed" | "skipped" | "degraded" | "faulted";
9
+
10
+ export interface HookHealthState {
11
+ readonly started_at_ms: number;
12
+ readonly started_rss_bytes: number;
13
+ readonly event_name: string | null;
14
+ readonly adapter: Adapter | null;
15
+ readonly payload_bytes: number;
16
+ coord_root: string | null;
17
+ owner_id?: string;
18
+ skipped_reason?: string;
19
+ v3_state?: string;
20
+ error_phases: string[];
21
+ }
22
+
23
+ export interface HookHealthReceipt {
24
+ receipt_version: typeof HOOK_HEALTH_RECEIPT_VERSION;
25
+ hook_name: string;
26
+ adapter: Adapter | "unknown";
27
+ outcome: HookHealthOutcome;
28
+ exit_contract: "always-zero";
29
+ exit_code: 0;
30
+ duration_ms: number;
31
+ payload_bytes: number;
32
+ pid: number;
33
+ rss_start_bytes: number;
34
+ rss_end_bytes: number;
35
+ rss_delta_bytes: number;
36
+ retry_worker: boolean;
37
+ error_count: number;
38
+ error_phases: readonly string[];
39
+ owner_id?: string;
40
+ skipped_reason?: string;
41
+ v3_state?: string;
42
+ }
43
+
44
+ export function beginHookHealth(input: {
45
+ started_at_ms: number;
46
+ started_rss_bytes: number;
47
+ event_name: string | null;
48
+ adapter: Adapter | null;
49
+ payload_bytes: number;
50
+ }): HookHealthState {
51
+ return {
52
+ ...input,
53
+ coord_root: null,
54
+ error_phases: [],
55
+ };
56
+ }
57
+
58
+ export function observeHookDebug(
59
+ state: HookHealthState | undefined,
60
+ entry: Readonly<Record<string, unknown>>,
61
+ ): void {
62
+ if (!state) return;
63
+ const skipped = safeToken(entry.skipped);
64
+ if (skipped) state.skipped_reason = skipped;
65
+ const v3State = safeToken(entry.event_v3_state);
66
+ if (v3State) state.v3_state = v3State;
67
+ }
68
+
69
+ export function observeHookError(state: HookHealthState | undefined, phase: unknown): void {
70
+ if (!state || state.error_phases.length >= HOOK_HEALTH_ERROR_PHASE_LIMIT) return;
71
+ state.error_phases.push(safeToken(phase) ?? "unknown");
72
+ }
73
+
74
+ export function finalizeHookHealth(
75
+ state: HookHealthState,
76
+ input: {
77
+ finished_at_ms: number;
78
+ finished_rss_bytes: number;
79
+ pid: number;
80
+ faulted?: boolean;
81
+ },
82
+ ): HookHealthReceipt {
83
+ const duration = Math.max(0, Math.floor(input.finished_at_ms - state.started_at_ms));
84
+ const rssStart = boundedInteger(state.started_rss_bytes);
85
+ const rssEnd = boundedInteger(input.finished_rss_bytes);
86
+ const hookName = safeToken(state.event_name) ?? "unknown";
87
+ return {
88
+ receipt_version: HOOK_HEALTH_RECEIPT_VERSION,
89
+ hook_name: hookName,
90
+ adapter: state.adapter ?? "unknown",
91
+ outcome: input.faulted
92
+ ? "faulted"
93
+ : state.error_phases.length > 0
94
+ ? "degraded"
95
+ : state.skipped_reason
96
+ ? "skipped"
97
+ : "completed",
98
+ exit_contract: "always-zero",
99
+ exit_code: 0,
100
+ duration_ms: duration,
101
+ payload_bytes: boundedInteger(state.payload_bytes),
102
+ pid: boundedInteger(input.pid),
103
+ rss_start_bytes: rssStart,
104
+ rss_end_bytes: rssEnd,
105
+ rss_delta_bytes: rssEnd - rssStart,
106
+ retry_worker: hookName === "runtime-context-retry",
107
+ error_count: state.error_phases.length,
108
+ error_phases: [...state.error_phases],
109
+ ...(state.owner_id ? { owner_id: state.owner_id } : {}),
110
+ ...(state.skipped_reason ? { skipped_reason: state.skipped_reason } : {}),
111
+ ...(state.v3_state ? { v3_state: state.v3_state } : {}),
112
+ };
113
+ }
114
+
115
+ export function writeHookHealthCompletion(
116
+ state: HookHealthState | undefined,
117
+ input: {
118
+ finished_at_ms: number;
119
+ finished_rss_bytes: number;
120
+ pid: number;
121
+ faulted?: boolean;
122
+ },
123
+ ): HookHealthReceipt | undefined {
124
+ if (!state?.coord_root) return undefined;
125
+ const receipt = finalizeHookHealth(state, input);
126
+ try {
127
+ processLogger(state.coord_root, "agent-hook").info(HOOK_HEALTH_EVENT, { ...receipt });
128
+ } catch {
129
+ // Hook health is observer-only. Logging can never change adapter behavior.
130
+ }
131
+ return receipt;
132
+ }
133
+
134
+ function safeToken(value: unknown): string | undefined {
135
+ if (typeof value !== "string") return undefined;
136
+ const normalized = value
137
+ .trim()
138
+ .slice(0, 128)
139
+ .replace(/[^a-zA-Z0-9._:/+-]+/g, "-");
140
+ return /^[a-zA-Z0-9]/.test(normalized) ? normalized : undefined;
141
+ }
142
+
143
+ function boundedInteger(value: number): number {
144
+ if (!Number.isFinite(value)) return 0;
145
+ return Math.max(0, Math.min(Number.MAX_SAFE_INTEGER, Math.floor(value)));
146
+ }
@@ -0,0 +1,98 @@
1
+ export const RESOURCE_SNAPSHOT_SCHEMA_VERSION = 1;
2
+ export const RESOURCE_SERVICE_STATUS_SCHEMA_VERSION = 1;
3
+
4
+ export type ResourceSupportState = "supported" | "partial" | "unsupported" | "error";
5
+
6
+ export interface ResourceProcessSample {
7
+ pid: number;
8
+ ppid: number;
9
+ start_id: string;
10
+ state: string;
11
+ name: string;
12
+ command: string;
13
+ cpu_percent: number | null;
14
+ rss_bytes: number;
15
+ age_seconds: number;
16
+ owner_kind: "agent" | "service" | "unattributed";
17
+ owner_id: string | null;
18
+ owner_root_pid: number | null;
19
+ }
20
+
21
+ export interface ResourceProcessGroup {
22
+ kind: ResourceProcessSample["owner_kind"];
23
+ id: string;
24
+ process_count: number;
25
+ cpu_percent: number | null;
26
+ rss_bytes: number;
27
+ root_pids: number[];
28
+ }
29
+
30
+ export interface ResourceMachineSample {
31
+ cpu_percent: number | null;
32
+ cpu_logical_count: number;
33
+ load_average: [number, number, number] | null;
34
+ memory_total_bytes: number | null;
35
+ memory_available_bytes: number | null;
36
+ memory_used_bytes: number | null;
37
+ memory_percent: number | null;
38
+ swap_total_bytes: number | null;
39
+ swap_used_bytes: number | null;
40
+ process_count: number;
41
+ }
42
+
43
+ export interface ResourceSnapshot {
44
+ schema_version: typeof RESOURCE_SNAPSHOT_SCHEMA_VERSION;
45
+ sampled_at: string;
46
+ interval_ms: number | null;
47
+ sample_duration_ms: number;
48
+ collector_cpu_ms: number;
49
+ platform: NodeJS.Platform;
50
+ namespace: "host" | "wsl" | "unknown";
51
+ support: {
52
+ state: ResourceSupportState;
53
+ sampler: "procfs" | "unsupported";
54
+ reason?: string;
55
+ };
56
+ machine: ResourceMachineSample;
57
+ groups: ResourceProcessGroup[];
58
+ processes: ResourceProcessSample[];
59
+ visible_process_count: number;
60
+ omitted_process_count: number;
61
+ unattributed_process_count: number;
62
+ }
63
+
64
+ export interface ResourceServiceStatusRecord {
65
+ schema_version: typeof RESOURCE_SERVICE_STATUS_SCHEMA_VERSION;
66
+ pid: number;
67
+ start_token?: string;
68
+ host: string;
69
+ nonce: string;
70
+ state: "starting" | "running" | "stopping" | "stopped" | "error";
71
+ started_at: string;
72
+ heartbeat_at: string;
73
+ stopped_at?: string;
74
+ interval_ms: number;
75
+ sample_count: number;
76
+ last_sample_at?: string;
77
+ last_error_code?: string;
78
+ }
79
+
80
+ export interface ResourceServiceStatus {
81
+ running: boolean;
82
+ stale: boolean;
83
+ record?: ResourceServiceStatusRecord;
84
+ status_path: string;
85
+ snapshot_path: string;
86
+ }
87
+
88
+ export interface ResourceSamplerState {
89
+ sampled_at_ms: number;
90
+ cpu_total_ticks: number;
91
+ cpu_idle_ticks: number;
92
+ process_ticks: Map<string, number>;
93
+ }
94
+
95
+ export interface ResourceSampleResult {
96
+ snapshot: ResourceSnapshot;
97
+ state?: ResourceSamplerState;
98
+ }
@@ -0,0 +1,5 @@
1
+ export * from "./contract.ts";
2
+ export * from "./sampler.ts";
3
+ export * from "./service.ts";
4
+ export * from "./service-status.ts";
5
+ export * from "./storage.ts";
@@ -0,0 +1,458 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { existsSync, readdirSync, readFileSync, readlinkSync } from "node:fs";
3
+ import { cpus, loadavg, platform, release } from "node:os";
4
+ import { basename, join } from "node:path";
5
+ import { performance } from "node:perf_hooks";
6
+ import { parsePidmapRow } from "../agents/state/pidmap.ts";
7
+ import { checkPidToken } from "../agents/state/proc-start.ts";
8
+ import {
9
+ RESOURCE_SNAPSHOT_SCHEMA_VERSION,
10
+ type ResourceProcessGroup,
11
+ type ResourceProcessSample,
12
+ type ResourceSampleResult,
13
+ type ResourceSamplerState,
14
+ type ResourceSnapshot,
15
+ } from "./contract.ts";
16
+
17
+ const DEFAULT_CLOCK_TICKS = 100;
18
+ const DEFAULT_PAGE_SIZE = 4_096;
19
+ const DEFAULT_UNATTRIBUTED_CPU_FLOOR = 0.5;
20
+ const DEFAULT_UNATTRIBUTED_RSS_FLOOR = 64 * 1_024 * 1_024;
21
+ const DEFAULT_MAX_PROCESSES = 500;
22
+ const MAX_COMMAND_CHARS = 240;
23
+
24
+ interface LinuxProcessReading {
25
+ pid: number;
26
+ ppid: number;
27
+ state: string;
28
+ name: string;
29
+ command: string;
30
+ ticks: number;
31
+ startTicks: number;
32
+ rssBytes: number;
33
+ ageSeconds: number;
34
+ }
35
+
36
+ interface LinuxCpuReading {
37
+ total: number;
38
+ idle: number;
39
+ }
40
+
41
+ interface ResourceSamplerOptions {
42
+ procRoot?: string;
43
+ nowMs?: number;
44
+ clockTicks?: number;
45
+ pageSize?: number;
46
+ services?: readonly { pid: number; id: string }[];
47
+ unattributedCpuFloor?: number;
48
+ unattributedRssFloor?: number;
49
+ maxProcesses?: number;
50
+ }
51
+
52
+ let cachedClockTicks: number | undefined;
53
+ let cachedPageSize: number | undefined;
54
+
55
+ export function sampleResources(
56
+ coordRoot: string,
57
+ previous?: ResourceSamplerState,
58
+ options: ResourceSamplerOptions = {},
59
+ ): ResourceSampleResult {
60
+ const started = performance.now();
61
+ const cpuBefore = process.cpuUsage();
62
+ const nowMs = options.nowMs ?? Date.now();
63
+ const currentPlatform = platform();
64
+ if (currentPlatform !== "linux") {
65
+ return {
66
+ snapshot: unsupportedSnapshot(currentPlatform, nowMs, started, cpuBefore),
67
+ };
68
+ }
69
+
70
+ const procRoot = options.procRoot ?? "/proc";
71
+ if (!existsSync(join(procRoot, "stat"))) {
72
+ return {
73
+ snapshot: unsupportedSnapshot(
74
+ currentPlatform,
75
+ nowMs,
76
+ started,
77
+ cpuBefore,
78
+ "procfs_unavailable",
79
+ ),
80
+ };
81
+ }
82
+
83
+ const clockTicks = options.clockTicks ?? systemClockTicks();
84
+ const pageSize = options.pageSize ?? systemPageSize();
85
+ const uptimeSeconds = parseUptime(readFileSync(join(procRoot, "uptime"), "utf8"));
86
+ const cpu = parseLinuxCpu(readFileSync(join(procRoot, "stat"), "utf8"));
87
+ const memory = parseMeminfo(readFileSync(join(procRoot, "meminfo"), "utf8"));
88
+ const readings = readLinuxProcesses(procRoot, clockTicks, pageSize, uptimeSeconds);
89
+ const anchors = readOwnershipAnchors(coordRoot, readings, options.services ?? []);
90
+ const byPid = new Map(readings.map((reading) => [reading.pid, reading]));
91
+ const processTicks = new Map<string, number>();
92
+ const intervalMs = previous ? Math.max(0, nowMs - previous.sampled_at_ms) : null;
93
+ const totalTickDelta = previous ? Math.max(0, cpu.total - previous.cpu_total_ticks) : 0;
94
+ const logicalCpus = Math.max(1, cpus().length);
95
+ const processes = readings.map((reading): ResourceProcessSample => {
96
+ const identity = `${reading.pid}:${reading.startTicks}`;
97
+ processTicks.set(identity, reading.ticks);
98
+ const priorTicks = previous?.process_ticks.get(identity);
99
+ const processDelta =
100
+ priorTicks === undefined ? undefined : Math.max(0, reading.ticks - priorTicks);
101
+ const cpuPercent =
102
+ processDelta === undefined || totalTickDelta <= 0
103
+ ? null
104
+ : round1((processDelta / totalTickDelta) * logicalCpus * 100);
105
+ const ownership = resolveOwnership(reading.pid, byPid, anchors);
106
+ return {
107
+ pid: reading.pid,
108
+ ppid: reading.ppid,
109
+ start_id: identity,
110
+ state: reading.state,
111
+ name: reading.name,
112
+ command: reading.command,
113
+ cpu_percent: cpuPercent,
114
+ rss_bytes: reading.rssBytes,
115
+ age_seconds: round1(reading.ageSeconds),
116
+ owner_kind: ownership?.kind ?? "unattributed",
117
+ owner_id: ownership?.id ?? null,
118
+ owner_root_pid: ownership?.rootPid ?? null,
119
+ };
120
+ });
121
+ const visible = processes
122
+ .filter(
123
+ (row) =>
124
+ row.owner_kind !== "unattributed" ||
125
+ (row.cpu_percent ?? 0) >=
126
+ (options.unattributedCpuFloor ?? DEFAULT_UNATTRIBUTED_CPU_FLOOR) ||
127
+ row.rss_bytes >= (options.unattributedRssFloor ?? DEFAULT_UNATTRIBUTED_RSS_FLOOR),
128
+ )
129
+ .sort(processSort)
130
+ .slice(0, options.maxProcesses ?? DEFAULT_MAX_PROCESSES);
131
+ const groups = aggregateGroups(visible);
132
+ const cpuPercent =
133
+ previous && totalTickDelta > 0
134
+ ? round1(
135
+ ((totalTickDelta - Math.max(0, cpu.idle - previous.cpu_idle_ticks)) / totalTickDelta) *
136
+ 100,
137
+ )
138
+ : null;
139
+ const memoryTotal = memory.MemTotal ?? null;
140
+ const memoryAvailable = memory.MemAvailable ?? memory.MemFree ?? null;
141
+ const memoryUsed =
142
+ memoryTotal !== null && memoryAvailable !== null
143
+ ? Math.max(0, memoryTotal - memoryAvailable)
144
+ : null;
145
+ const swapTotal = memory.SwapTotal ?? null;
146
+ const swapFree = memory.SwapFree ?? null;
147
+ const swapUsed =
148
+ swapTotal !== null && swapFree !== null ? Math.max(0, swapTotal - swapFree) : null;
149
+ const collectorCpu = process.cpuUsage(cpuBefore);
150
+ const snapshot: ResourceSnapshot = {
151
+ schema_version: RESOURCE_SNAPSHOT_SCHEMA_VERSION,
152
+ sampled_at: new Date(nowMs).toISOString(),
153
+ interval_ms: intervalMs,
154
+ sample_duration_ms: round1(performance.now() - started),
155
+ collector_cpu_ms: round1((collectorCpu.user + collectorCpu.system) / 1_000),
156
+ platform: currentPlatform,
157
+ namespace: /microsoft|wsl/i.test(release()) ? "wsl" : "host",
158
+ support: { state: "supported", sampler: "procfs" },
159
+ machine: {
160
+ cpu_percent: cpuPercent,
161
+ cpu_logical_count: logicalCpus,
162
+ load_average: loadavg() as [number, number, number],
163
+ memory_total_bytes: memoryTotal,
164
+ memory_available_bytes: memoryAvailable,
165
+ memory_used_bytes: memoryUsed,
166
+ memory_percent:
167
+ memoryUsed !== null && memoryTotal ? round1((memoryUsed / memoryTotal) * 100) : null,
168
+ swap_total_bytes: swapTotal,
169
+ swap_used_bytes: swapUsed,
170
+ process_count: readings.length,
171
+ },
172
+ groups,
173
+ processes: visible,
174
+ visible_process_count: visible.length,
175
+ omitted_process_count: Math.max(0, readings.length - visible.length),
176
+ unattributed_process_count: visible.filter((row) => row.owner_kind === "unattributed").length,
177
+ };
178
+ return {
179
+ snapshot,
180
+ state: {
181
+ sampled_at_ms: nowMs,
182
+ cpu_total_ticks: cpu.total,
183
+ cpu_idle_ticks: cpu.idle,
184
+ process_ticks: processTicks,
185
+ },
186
+ };
187
+ }
188
+
189
+ export function parseLinuxProcessStat(line: string): {
190
+ pid: number;
191
+ name: string;
192
+ state: string;
193
+ ppid: number;
194
+ ticks: number;
195
+ startTicks: number;
196
+ rssPages: number;
197
+ } | null {
198
+ const open = line.indexOf("(");
199
+ const close = line.lastIndexOf(")");
200
+ if (open <= 0 || close <= open) return null;
201
+ const pid = Number(line.slice(0, open).trim());
202
+ const fields = line
203
+ .slice(close + 1)
204
+ .trim()
205
+ .split(/\s+/);
206
+ const state = fields[0] ?? "?";
207
+ const ppid = Number(fields[1]);
208
+ const userTicks = Number(fields[11]);
209
+ const systemTicks = Number(fields[12]);
210
+ const startTicks = Number(fields[19]);
211
+ const rssPages = Number(fields[21]);
212
+ if (![pid, ppid, userTicks, systemTicks, startTicks, rssPages].every(Number.isFinite))
213
+ return null;
214
+ return {
215
+ pid,
216
+ name: line.slice(open + 1, close),
217
+ state,
218
+ ppid,
219
+ ticks: userTicks + systemTicks,
220
+ startTicks,
221
+ rssPages: Math.max(0, rssPages),
222
+ };
223
+ }
224
+
225
+ export function redactCommand(args: readonly string[]): string {
226
+ const secretName =
227
+ /(?:^|[-_])(token|secret|password|passwd|api[-_]?key|authorization)(?:$|[-_])/i;
228
+ const output: string[] = [];
229
+ let redactNext = false;
230
+ for (const raw of args) {
231
+ let value = raw;
232
+ if (redactNext) {
233
+ output.push("<redacted>");
234
+ redactNext = false;
235
+ continue;
236
+ }
237
+ const equals = value.indexOf("=");
238
+ if (equals > 0 && secretName.test(value.slice(0, equals))) {
239
+ value = `${value.slice(0, equals + 1)}<redacted>`;
240
+ } else if (secretName.test(value.replace(/^--?/, ""))) {
241
+ redactNext = true;
242
+ }
243
+ value = value.replace(/(https?:\/\/)[^:@\s/]+:[^@\s/]+@/gi, "$1<redacted>@");
244
+ output.push(value.replace(/[\r\n\t]+/g, " "));
245
+ }
246
+ const joined = output.join(" ").trim();
247
+ return joined.length <= MAX_COMMAND_CHARS ? joined : `${joined.slice(0, MAX_COMMAND_CHARS - 1)}…`;
248
+ }
249
+
250
+ function readLinuxProcesses(
251
+ procRoot: string,
252
+ clockTicks: number,
253
+ pageSize: number,
254
+ uptimeSeconds: number,
255
+ ): LinuxProcessReading[] {
256
+ const rows: LinuxProcessReading[] = [];
257
+ for (const entry of readdirSync(procRoot, { withFileTypes: true })) {
258
+ if (!entry.isDirectory() || !/^\d+$/.test(entry.name)) continue;
259
+ const pidRoot = join(procRoot, entry.name);
260
+ try {
261
+ const parsed = parseLinuxProcessStat(readFileSync(join(pidRoot, "stat"), "utf8"));
262
+ if (!parsed) continue;
263
+ const args = readFileSync(join(pidRoot, "cmdline"), "utf8").split("\0").filter(Boolean);
264
+ let name = parsed.name;
265
+ try {
266
+ name = basename(readlinkSync(join(pidRoot, "exe"))) || name;
267
+ } catch {
268
+ // Kernel threads and racing processes may have no readable executable.
269
+ }
270
+ rows.push({
271
+ pid: parsed.pid,
272
+ ppid: parsed.ppid,
273
+ state: parsed.state,
274
+ name,
275
+ command: redactCommand(args.length > 0 ? args : [parsed.name]),
276
+ ticks: parsed.ticks,
277
+ startTicks: parsed.startTicks,
278
+ rssBytes: parsed.rssPages * pageSize,
279
+ ageSeconds: Math.max(0, uptimeSeconds - parsed.startTicks / clockTicks),
280
+ });
281
+ } catch {
282
+ // Process exits and permission boundaries are normal during a table scan.
283
+ }
284
+ }
285
+ return rows;
286
+ }
287
+
288
+ interface OwnershipAnchor {
289
+ kind: "agent" | "service";
290
+ id: string;
291
+ }
292
+
293
+ function readOwnershipAnchors(
294
+ coordRoot: string,
295
+ readings: readonly LinuxProcessReading[],
296
+ services: readonly { pid: number; id: string }[],
297
+ ): Map<number, OwnershipAnchor> {
298
+ const byPid = new Set(readings.map((row) => row.pid));
299
+ const directory = join(coordRoot, ".harnery", "pid-map");
300
+ const anchors = new Map<number, OwnershipAnchor>();
301
+ if (existsSync(directory)) {
302
+ for (const entry of readdirSync(directory)) {
303
+ if (!/^\d+$/.test(entry)) continue;
304
+ const pid = Number(entry);
305
+ if (!byPid.has(pid)) continue;
306
+ try {
307
+ const row = parsePidmapRow(readFileSync(join(directory, entry), "utf8"));
308
+ if (!row.instanceId || checkPidToken(pid, row.startToken) === "mismatch") continue;
309
+ anchors.set(pid, { kind: "agent", id: row.instanceId });
310
+ } catch {
311
+ // An unreadable or racing row cannot prove ownership.
312
+ }
313
+ }
314
+ }
315
+ for (const service of services) {
316
+ if (byPid.has(service.pid) && service.id.trim()) {
317
+ anchors.set(service.pid, { kind: "service", id: service.id.trim() });
318
+ }
319
+ }
320
+ return anchors;
321
+ }
322
+
323
+ function resolveOwnership(
324
+ pid: number,
325
+ byPid: ReadonlyMap<number, LinuxProcessReading>,
326
+ anchors: ReadonlyMap<number, OwnershipAnchor>,
327
+ ): { kind: "agent" | "service"; id: string; rootPid: number } | undefined {
328
+ const seen = new Set<number>();
329
+ let cursor = pid;
330
+ for (let hops = 0; hops < 64 && cursor > 0 && !seen.has(cursor); hops++) {
331
+ seen.add(cursor);
332
+ const owner = anchors.get(cursor);
333
+ if (owner) return { ...owner, rootPid: cursor };
334
+ cursor = byPid.get(cursor)?.ppid ?? 0;
335
+ }
336
+ return undefined;
337
+ }
338
+
339
+ function aggregateGroups(processes: readonly ResourceProcessSample[]): ResourceProcessGroup[] {
340
+ const groups = new Map<string, ResourceProcessGroup>();
341
+ for (const row of processes) {
342
+ const id = row.owner_id ?? "unattributed";
343
+ const key = `${row.owner_kind}:${id}`;
344
+ const group = groups.get(key) ?? {
345
+ kind: row.owner_kind,
346
+ id,
347
+ process_count: 0,
348
+ cpu_percent: null,
349
+ rss_bytes: 0,
350
+ root_pids: [],
351
+ };
352
+ group.process_count += 1;
353
+ group.rss_bytes += row.rss_bytes;
354
+ if (row.cpu_percent !== null)
355
+ group.cpu_percent = round1((group.cpu_percent ?? 0) + row.cpu_percent);
356
+ if (row.owner_root_pid !== null && !group.root_pids.includes(row.owner_root_pid)) {
357
+ group.root_pids.push(row.owner_root_pid);
358
+ }
359
+ groups.set(key, group);
360
+ }
361
+ return [...groups.values()].sort(
362
+ (left, right) =>
363
+ (right.cpu_percent ?? -1) - (left.cpu_percent ?? -1) ||
364
+ right.rss_bytes - left.rss_bytes ||
365
+ left.id.localeCompare(right.id),
366
+ );
367
+ }
368
+
369
+ function processSort(left: ResourceProcessSample, right: ResourceProcessSample): number {
370
+ return (
371
+ (right.cpu_percent ?? -1) - (left.cpu_percent ?? -1) ||
372
+ right.rss_bytes - left.rss_bytes ||
373
+ left.pid - right.pid
374
+ );
375
+ }
376
+
377
+ function parseLinuxCpu(content: string): LinuxCpuReading {
378
+ const fields = content.split("\n")[0]?.trim().split(/\s+/).slice(1).map(Number) ?? [];
379
+ if (fields.length < 4 || fields.some((value) => !Number.isFinite(value))) {
380
+ throw new Error("procfs_cpu_invalid");
381
+ }
382
+ return {
383
+ total: fields.reduce((sum, value) => sum + value, 0),
384
+ idle: (fields[3] ?? 0) + (fields[4] ?? 0),
385
+ };
386
+ }
387
+
388
+ function parseMeminfo(content: string): Record<string, number> {
389
+ const result: Record<string, number> = {};
390
+ for (const line of content.split("\n")) {
391
+ const match = /^([A-Za-z_()]+):\s+(\d+)\s+kB$/.exec(line.trim());
392
+ if (match) result[match[1]!] = Number(match[2]) * 1_024;
393
+ }
394
+ return result;
395
+ }
396
+
397
+ function parseUptime(content: string): number {
398
+ const value = Number(content.trim().split(/\s+/, 1)[0]);
399
+ if (!Number.isFinite(value) || value < 0) throw new Error("procfs_uptime_invalid");
400
+ return value;
401
+ }
402
+
403
+ function systemClockTicks(): number {
404
+ cachedClockTicks ??= positiveGetconf("CLK_TCK", DEFAULT_CLOCK_TICKS);
405
+ return cachedClockTicks;
406
+ }
407
+
408
+ function systemPageSize(): number {
409
+ cachedPageSize ??= positiveGetconf("PAGESIZE", DEFAULT_PAGE_SIZE);
410
+ return cachedPageSize;
411
+ }
412
+
413
+ function positiveGetconf(key: string, fallback: number): number {
414
+ const result = spawnSync("getconf", [key], { encoding: "utf8", timeout: 1_000 });
415
+ const value = result.status === 0 ? Number(result.stdout.trim()) : Number.NaN;
416
+ return Number.isFinite(value) && value > 0 ? value : fallback;
417
+ }
418
+
419
+ function unsupportedSnapshot(
420
+ currentPlatform: NodeJS.Platform,
421
+ nowMs: number,
422
+ started: number,
423
+ cpuBefore: NodeJS.CpuUsage,
424
+ reason = "platform_unsupported",
425
+ ): ResourceSnapshot {
426
+ const collectorCpu = process.cpuUsage(cpuBefore);
427
+ return {
428
+ schema_version: RESOURCE_SNAPSHOT_SCHEMA_VERSION,
429
+ sampled_at: new Date(nowMs).toISOString(),
430
+ interval_ms: null,
431
+ sample_duration_ms: round1(performance.now() - started),
432
+ collector_cpu_ms: round1((collectorCpu.user + collectorCpu.system) / 1_000),
433
+ platform: currentPlatform,
434
+ namespace: "unknown",
435
+ support: { state: "unsupported", sampler: "unsupported", reason },
436
+ machine: {
437
+ cpu_percent: null,
438
+ cpu_logical_count: cpus().length,
439
+ load_average: null,
440
+ memory_total_bytes: null,
441
+ memory_available_bytes: null,
442
+ memory_used_bytes: null,
443
+ memory_percent: null,
444
+ swap_total_bytes: null,
445
+ swap_used_bytes: null,
446
+ process_count: 0,
447
+ },
448
+ groups: [],
449
+ processes: [],
450
+ visible_process_count: 0,
451
+ omitted_process_count: 0,
452
+ unattributed_process_count: 0,
453
+ };
454
+ }
455
+
456
+ function round1(value: number): number {
457
+ return Math.round(value * 10) / 10;
458
+ }