@loompad/cli 0.1.0 → 1.1.2

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 (688) hide show
  1. package/ARCHITECTURE.md +96 -11
  2. package/CHANGELOG.md +1054 -0
  3. package/README.md +1473 -44
  4. package/THIRD_PARTY_NOTICES.md +275 -0
  5. package/dist/adapters/antigravity-cli.d.ts +62 -0
  6. package/dist/adapters/antigravity-cli.js +258 -0
  7. package/dist/adapters/antigravity-cli.js.map +1 -0
  8. package/dist/adapters/base.d.ts +61 -9
  9. package/dist/adapters/base.js +266 -15
  10. package/dist/adapters/base.js.map +1 -1
  11. package/dist/adapters/bridges/antigravity.d.ts +9 -12
  12. package/dist/adapters/bridges/antigravity.js +9 -12
  13. package/dist/adapters/bridges/antigravity.js.map +1 -1
  14. package/dist/adapters/echo.d.ts +1 -0
  15. package/dist/adapters/echo.js +59 -3
  16. package/dist/adapters/echo.js.map +1 -1
  17. package/dist/adapters/grok.d.ts +7 -0
  18. package/dist/adapters/grok.js +20 -10
  19. package/dist/adapters/grok.js.map +1 -1
  20. package/dist/adapters/index.d.ts +5 -1
  21. package/dist/adapters/index.js +53 -7
  22. package/dist/adapters/index.js.map +1 -1
  23. package/dist/adapters/model.d.ts +137 -0
  24. package/dist/adapters/model.js +527 -0
  25. package/dist/adapters/model.js.map +1 -0
  26. package/dist/adapters/opencode.d.ts +101 -1
  27. package/dist/adapters/opencode.js +874 -98
  28. package/dist/adapters/opencode.js.map +1 -1
  29. package/dist/cli/doctor.d.ts +32 -0
  30. package/dist/cli/doctor.js +89 -4
  31. package/dist/cli/doctor.js.map +1 -1
  32. package/dist/cli/index.js +2651 -87
  33. package/dist/cli/index.js.map +1 -1
  34. package/dist/cli/tui-model.js +3 -1
  35. package/dist/cli/tui-model.js.map +1 -1
  36. package/dist/cli/tui.js +44 -1
  37. package/dist/cli/tui.js.map +1 -1
  38. package/dist/cli/ui.d.ts +2 -0
  39. package/dist/cli/ui.js +13 -0
  40. package/dist/cli/ui.js.map +1 -1
  41. package/dist/core/ades.d.ts +32 -16
  42. package/dist/core/ades.js +57 -20
  43. package/dist/core/ades.js.map +1 -1
  44. package/dist/core/agent-state.d.ts +14 -0
  45. package/dist/core/agent-state.js +32 -0
  46. package/dist/core/agent-state.js.map +1 -0
  47. package/dist/core/approvals.d.ts +33 -0
  48. package/dist/core/approvals.js +29 -0
  49. package/dist/core/approvals.js.map +1 -0
  50. package/dist/core/backup.d.ts +19 -0
  51. package/dist/core/backup.js +86 -0
  52. package/dist/core/backup.js.map +1 -0
  53. package/dist/core/baton.d.ts +2 -2
  54. package/dist/core/baton.js.map +1 -1
  55. package/dist/core/brain-extract.d.ts +17 -0
  56. package/dist/core/brain-extract.js +28 -2
  57. package/dist/core/brain-extract.js.map +1 -1
  58. package/dist/core/brain-index.d.ts +71 -0
  59. package/dist/core/brain-index.js +204 -5
  60. package/dist/core/brain-index.js.map +1 -1
  61. package/dist/core/brain.d.ts +58 -2
  62. package/dist/core/brain.js +79 -1
  63. package/dist/core/brain.js.map +1 -1
  64. package/dist/core/chat-import.d.ts +71 -0
  65. package/dist/core/chat-import.js +419 -0
  66. package/dist/core/chat-import.js.map +1 -0
  67. package/dist/core/checkpoint.d.ts +107 -0
  68. package/dist/core/checkpoint.js +259 -0
  69. package/dist/core/checkpoint.js.map +1 -0
  70. package/dist/core/claude-cli.js +1 -1
  71. package/dist/core/claude-cli.js.map +1 -1
  72. package/dist/core/continuity/artifacts.d.ts +14 -0
  73. package/dist/core/continuity/artifacts.js +118 -0
  74. package/dist/core/continuity/artifacts.js.map +1 -0
  75. package/dist/core/continuity/capabilities.d.ts +65 -0
  76. package/dist/core/continuity/capabilities.js +129 -0
  77. package/dist/core/continuity/capabilities.js.map +1 -0
  78. package/dist/core/continuity/contracts.d.ts +352 -0
  79. package/dist/core/continuity/contracts.js +117 -0
  80. package/dist/core/continuity/contracts.js.map +1 -0
  81. package/dist/core/continuity/engine.d.ts +55 -0
  82. package/dist/core/continuity/engine.js +504 -0
  83. package/dist/core/continuity/engine.js.map +1 -0
  84. package/dist/core/continuity/store.d.ts +73 -0
  85. package/dist/core/continuity/store.js +438 -0
  86. package/dist/core/continuity/store.js.map +1 -0
  87. package/dist/core/conversations.d.ts +19 -0
  88. package/dist/core/conversations.js +87 -0
  89. package/dist/core/conversations.js.map +1 -0
  90. package/dist/core/crew-coordination.d.ts +6 -0
  91. package/dist/core/crew-coordination.js +24 -0
  92. package/dist/core/crew-coordination.js.map +1 -0
  93. package/dist/core/crew-policy.d.ts +18 -0
  94. package/dist/core/crew-policy.js +40 -0
  95. package/dist/core/crew-policy.js.map +1 -0
  96. package/dist/core/crew-protocol.d.ts +64 -0
  97. package/dist/core/crew-protocol.js +146 -0
  98. package/dist/core/crew-protocol.js.map +1 -0
  99. package/dist/core/crew-requests.d.ts +14 -0
  100. package/dist/core/crew-requests.js +22 -0
  101. package/dist/core/crew-requests.js.map +1 -0
  102. package/dist/core/crew.d.ts +395 -0
  103. package/dist/core/crew.js +1857 -0
  104. package/dist/core/crew.js.map +1 -0
  105. package/dist/core/digest.d.ts +40 -0
  106. package/dist/core/digest.js +94 -0
  107. package/dist/core/digest.js.map +1 -0
  108. package/dist/core/distill.js +3 -5
  109. package/dist/core/distill.js.map +1 -1
  110. package/dist/core/eventlog.d.ts +27 -25
  111. package/dist/core/eventlog.js +97 -150
  112. package/dist/core/eventlog.js.map +1 -1
  113. package/dist/core/events/contracts.d.ts +51 -0
  114. package/dist/core/events/contracts.js +2 -0
  115. package/dist/core/events/contracts.js.map +1 -0
  116. package/dist/core/events/jsonl-store.d.ts +23 -0
  117. package/dist/core/events/jsonl-store.js +90 -0
  118. package/dist/core/events/jsonl-store.js.map +1 -0
  119. package/dist/core/events/sqlite-store.d.ts +37 -0
  120. package/dist/core/events/sqlite-store.js +215 -0
  121. package/dist/core/events/sqlite-store.js.map +1 -0
  122. package/dist/core/forecast.d.ts +44 -0
  123. package/dist/core/forecast.js +46 -0
  124. package/dist/core/forecast.js.map +1 -0
  125. package/dist/core/free-pool.d.ts +65 -0
  126. package/dist/core/free-pool.js +171 -0
  127. package/dist/core/free-pool.js.map +1 -0
  128. package/dist/core/git.d.ts +30 -0
  129. package/dist/core/git.js +50 -2
  130. package/dist/core/git.js.map +1 -1
  131. package/dist/core/github-events.d.ts +53 -0
  132. package/dist/core/github-events.js +227 -0
  133. package/dist/core/github-events.js.map +1 -0
  134. package/dist/core/goal-lanes.d.ts +53 -0
  135. package/dist/core/goal-lanes.js +99 -0
  136. package/dist/core/goal-lanes.js.map +1 -0
  137. package/dist/core/hosted.d.ts +27 -0
  138. package/dist/core/hosted.js +50 -0
  139. package/dist/core/hosted.js.map +1 -0
  140. package/dist/core/http-server.d.ts +5 -0
  141. package/dist/core/http-server.js +8 -0
  142. package/dist/core/http-server.js.map +1 -0
  143. package/dist/core/invite-link.d.ts +31 -0
  144. package/dist/core/invite-link.js +45 -0
  145. package/dist/core/invite-link.js.map +1 -0
  146. package/dist/core/loops.d.ts +37 -0
  147. package/dist/core/loops.js +81 -0
  148. package/dist/core/loops.js.map +1 -0
  149. package/dist/core/mcp-catalog.d.ts +182 -0
  150. package/dist/core/mcp-catalog.js +431 -0
  151. package/dist/core/mcp-catalog.js.map +1 -0
  152. package/dist/core/mcp.d.ts +87 -0
  153. package/dist/core/mcp.js +181 -0
  154. package/dist/core/mcp.js.map +1 -0
  155. package/dist/core/memory.d.ts +1 -0
  156. package/dist/core/memory.js +5 -2
  157. package/dist/core/memory.js.map +1 -1
  158. package/dist/core/model-tools.d.ts +97 -0
  159. package/dist/core/model-tools.js +388 -0
  160. package/dist/core/model-tools.js.map +1 -0
  161. package/dist/core/notify.js +19 -1
  162. package/dist/core/notify.js.map +1 -1
  163. package/dist/core/orchestra.d.ts +635 -0
  164. package/dist/core/orchestra.js +2120 -0
  165. package/dist/core/orchestra.js.map +1 -0
  166. package/dist/core/permissions.d.ts +56 -0
  167. package/dist/core/permissions.js +122 -0
  168. package/dist/core/permissions.js.map +1 -0
  169. package/dist/core/preview-proxy.d.ts +70 -0
  170. package/dist/core/preview-proxy.js +563 -0
  171. package/dist/core/preview-proxy.js.map +1 -0
  172. package/dist/core/preview-shot.d.ts +46 -0
  173. package/dist/core/preview-shot.js +109 -0
  174. package/dist/core/preview-shot.js.map +1 -0
  175. package/dist/core/process.d.ts +36 -0
  176. package/dist/core/process.js +168 -0
  177. package/dist/core/process.js.map +1 -0
  178. package/dist/core/prompt-queue.d.ts +136 -0
  179. package/dist/core/prompt-queue.js +269 -0
  180. package/dist/core/prompt-queue.js.map +1 -0
  181. package/dist/core/prompts.d.ts +53 -0
  182. package/dist/core/prompts.js +127 -0
  183. package/dist/core/prompts.js.map +1 -0
  184. package/dist/core/proof.d.ts +82 -0
  185. package/dist/core/proof.js +216 -0
  186. package/dist/core/proof.js.map +1 -0
  187. package/dist/core/provider-text.d.ts +23 -0
  188. package/dist/core/provider-text.js +66 -0
  189. package/dist/core/provider-text.js.map +1 -0
  190. package/dist/core/providers.d.ts +152 -0
  191. package/dist/core/providers.js +343 -0
  192. package/dist/core/providers.js.map +1 -0
  193. package/dist/core/quota.d.ts +33 -0
  194. package/dist/core/quota.js +74 -0
  195. package/dist/core/quota.js.map +1 -0
  196. package/dist/core/recipes.d.ts +50 -0
  197. package/dist/core/recipes.js +99 -0
  198. package/dist/core/recipes.js.map +1 -0
  199. package/dist/core/registry.d.ts +54 -0
  200. package/dist/core/registry.js +62 -5
  201. package/dist/core/registry.js.map +1 -1
  202. package/dist/core/relay-client.d.ts +67 -0
  203. package/dist/core/relay-client.js +184 -0
  204. package/dist/core/relay-client.js.map +1 -0
  205. package/dist/core/relay-protocol.d.ts +140 -0
  206. package/dist/core/relay-protocol.js +189 -0
  207. package/dist/core/relay-protocol.js.map +1 -0
  208. package/dist/core/replay.d.ts +18 -0
  209. package/dist/core/replay.js +191 -0
  210. package/dist/core/replay.js.map +1 -0
  211. package/dist/core/review-triage.d.ts +41 -0
  212. package/dist/core/review-triage.js +124 -0
  213. package/dist/core/review-triage.js.map +1 -0
  214. package/dist/core/router.js +2 -2
  215. package/dist/core/router.js.map +1 -1
  216. package/dist/core/routes.d.ts +39 -3
  217. package/dist/core/routes.js +137 -4
  218. package/dist/core/routes.js.map +1 -1
  219. package/dist/core/run-command.d.ts +14 -0
  220. package/dist/core/run-command.js +45 -0
  221. package/dist/core/run-command.js.map +1 -0
  222. package/dist/core/runner-setup.d.ts +36 -0
  223. package/dist/core/runner-setup.js +114 -0
  224. package/dist/core/runner-setup.js.map +1 -0
  225. package/dist/core/search.js +0 -0
  226. package/dist/core/search.js.map +1 -1
  227. package/dist/core/semantic.d.ts +92 -0
  228. package/dist/core/semantic.js +227 -0
  229. package/dist/core/semantic.js.map +1 -0
  230. package/dist/core/servers.d.ts +115 -0
  231. package/dist/core/servers.js +305 -0
  232. package/dist/core/servers.js.map +1 -0
  233. package/dist/core/setup.js +64 -6
  234. package/dist/core/setup.js.map +1 -1
  235. package/dist/core/shield.d.ts +17 -0
  236. package/dist/core/shield.js +81 -0
  237. package/dist/core/shield.js.map +1 -0
  238. package/dist/core/shift.d.ts +69 -0
  239. package/dist/core/shift.js +120 -0
  240. package/dist/core/shift.js.map +1 -0
  241. package/dist/core/skill-install.d.ts +125 -0
  242. package/dist/core/skill-install.js +328 -0
  243. package/dist/core/skill-install.js.map +1 -0
  244. package/dist/core/skills.d.ts +116 -0
  245. package/dist/core/skills.js +223 -0
  246. package/dist/core/skills.js.map +1 -0
  247. package/dist/core/step-conditions.d.ts +65 -0
  248. package/dist/core/step-conditions.js +102 -0
  249. package/dist/core/step-conditions.js.map +1 -0
  250. package/dist/core/subagents.d.ts +45 -0
  251. package/dist/core/subagents.js +90 -0
  252. package/dist/core/subagents.js.map +1 -0
  253. package/dist/core/team-canon.d.ts +42 -0
  254. package/dist/core/team-canon.js +121 -0
  255. package/dist/core/team-canon.js.map +1 -0
  256. package/dist/core/team-crypto.d.ts +97 -0
  257. package/dist/core/team-crypto.js +175 -0
  258. package/dist/core/team-crypto.js.map +1 -0
  259. package/dist/core/team-hub.d.ts +514 -0
  260. package/dist/core/team-hub.js +964 -0
  261. package/dist/core/team-hub.js.map +1 -0
  262. package/dist/core/team-landing.d.ts +239 -0
  263. package/dist/core/team-landing.js +504 -0
  264. package/dist/core/team-landing.js.map +1 -0
  265. package/dist/core/team-leases.d.ts +44 -0
  266. package/dist/core/team-leases.js +145 -0
  267. package/dist/core/team-leases.js.map +1 -0
  268. package/dist/core/team-memory.d.ts +42 -0
  269. package/dist/core/team-memory.js +84 -0
  270. package/dist/core/team-memory.js.map +1 -0
  271. package/dist/core/team-policy.d.ts +85 -0
  272. package/dist/core/team-policy.js +191 -0
  273. package/dist/core/team-policy.js.map +1 -0
  274. package/dist/core/thread-md.d.ts +6 -0
  275. package/dist/core/thread-md.js +42 -0
  276. package/dist/core/thread-md.js.map +1 -0
  277. package/dist/core/turn-stats.d.ts +61 -0
  278. package/dist/core/turn-stats.js +124 -0
  279. package/dist/core/turn-stats.js.map +1 -0
  280. package/dist/core/updater.d.ts +73 -0
  281. package/dist/core/updater.js +153 -0
  282. package/dist/core/updater.js.map +1 -0
  283. package/dist/core/usage.d.ts +75 -0
  284. package/dist/core/usage.js +163 -0
  285. package/dist/core/usage.js.map +1 -0
  286. package/dist/core/windows-jobs.d.ts +15 -0
  287. package/dist/core/windows-jobs.js +170 -0
  288. package/dist/core/windows-jobs.js.map +1 -0
  289. package/dist/core/worktree-merge.d.ts +57 -0
  290. package/dist/core/worktree-merge.js +123 -0
  291. package/dist/core/worktree-merge.js.map +1 -0
  292. package/dist/core/worktree.d.ts +5 -0
  293. package/dist/core/worktree.js +45 -5
  294. package/dist/core/worktree.js.map +1 -1
  295. package/dist/core/worktrees.d.ts +6 -0
  296. package/dist/core/worktrees.js +21 -0
  297. package/dist/core/worktrees.js.map +1 -0
  298. package/dist/daemon/app-page.d.ts +0 -17
  299. package/dist/daemon/app-page.js +10 -6346
  300. package/dist/daemon/app-page.js.map +1 -1
  301. package/dist/daemon/auth.d.ts +24 -3
  302. package/dist/daemon/auth.js +66 -7
  303. package/dist/daemon/auth.js.map +1 -1
  304. package/dist/daemon/board.d.ts +7 -0
  305. package/dist/daemon/board.js +12 -1
  306. package/dist/daemon/board.js.map +1 -1
  307. package/dist/daemon/brand-icons.d.ts +6 -1
  308. package/dist/daemon/brand-icons.js +11 -3
  309. package/dist/daemon/brand-icons.js.map +1 -1
  310. package/dist/daemon/client.d.ts +665 -1
  311. package/dist/daemon/client.js +381 -3
  312. package/dist/daemon/client.js.map +1 -1
  313. package/dist/daemon/delivery.d.ts +34 -0
  314. package/dist/daemon/delivery.js +63 -0
  315. package/dist/daemon/delivery.js.map +1 -0
  316. package/dist/daemon/deploys.d.ts +62 -0
  317. package/dist/daemon/deploys.js +159 -0
  318. package/dist/daemon/deploys.js.map +1 -0
  319. package/dist/daemon/landing.d.ts +192 -0
  320. package/dist/daemon/landing.js +992 -0
  321. package/dist/daemon/landing.js.map +1 -0
  322. package/dist/daemon/onboard.d.ts +150 -0
  323. package/dist/daemon/onboard.js +390 -0
  324. package/dist/daemon/onboard.js.map +1 -0
  325. package/dist/daemon/proof.d.ts +70 -0
  326. package/dist/daemon/proof.js +172 -0
  327. package/dist/daemon/proof.js.map +1 -0
  328. package/dist/daemon/push.d.ts +19 -1
  329. package/dist/daemon/push.js +90 -3
  330. package/dist/daemon/push.js.map +1 -1
  331. package/dist/daemon/relay.d.ts +82 -0
  332. package/dist/daemon/relay.js +351 -0
  333. package/dist/daemon/relay.js.map +1 -0
  334. package/dist/daemon/routes/agent-discovery.d.ts +4 -0
  335. package/dist/daemon/routes/agent-discovery.js +29 -0
  336. package/dist/daemon/routes/agent-discovery.js.map +1 -0
  337. package/dist/daemon/routes/agent-settings.d.ts +4 -0
  338. package/dist/daemon/routes/agent-settings.js +94 -0
  339. package/dist/daemon/routes/agent-settings.js.map +1 -0
  340. package/dist/daemon/routes/agents.d.ts +4 -0
  341. package/dist/daemon/routes/agents.js +105 -0
  342. package/dist/daemon/routes/agents.js.map +1 -0
  343. package/dist/daemon/routes/alerts.d.ts +4 -0
  344. package/dist/daemon/routes/alerts.js +137 -0
  345. package/dist/daemon/routes/alerts.js.map +1 -0
  346. package/dist/daemon/routes/approvals.d.ts +4 -0
  347. package/dist/daemon/routes/approvals.js +40 -0
  348. package/dist/daemon/routes/approvals.js.map +1 -0
  349. package/dist/daemon/routes/assets.d.ts +3 -0
  350. package/dist/daemon/routes/assets.js +64 -0
  351. package/dist/daemon/routes/assets.js.map +1 -0
  352. package/dist/daemon/routes/board.d.ts +4 -0
  353. package/dist/daemon/routes/board.js +147 -0
  354. package/dist/daemon/routes/board.js.map +1 -0
  355. package/dist/daemon/routes/brain.d.ts +4 -0
  356. package/dist/daemon/routes/brain.js +270 -0
  357. package/dist/daemon/routes/brain.js.map +1 -0
  358. package/dist/daemon/routes/chats.d.ts +4 -0
  359. package/dist/daemon/routes/chats.js +249 -0
  360. package/dist/daemon/routes/chats.js.map +1 -0
  361. package/dist/daemon/routes/clients.d.ts +4 -0
  362. package/dist/daemon/routes/clients.js +49 -0
  363. package/dist/daemon/routes/clients.js.map +1 -0
  364. package/dist/daemon/routes/cloud.d.ts +4 -0
  365. package/dist/daemon/routes/cloud.js +46 -0
  366. package/dist/daemon/routes/cloud.js.map +1 -0
  367. package/dist/daemon/routes/config.d.ts +4 -0
  368. package/dist/daemon/routes/config.js +35 -0
  369. package/dist/daemon/routes/config.js.map +1 -0
  370. package/dist/daemon/routes/context.d.ts +51 -0
  371. package/dist/daemon/routes/context.js +2 -0
  372. package/dist/daemon/routes/context.js.map +1 -0
  373. package/dist/daemon/routes/crews.d.ts +4 -0
  374. package/dist/daemon/routes/crews.js +149 -0
  375. package/dist/daemon/routes/crews.js.map +1 -0
  376. package/dist/daemon/routes/files.d.ts +3 -0
  377. package/dist/daemon/routes/files.js +253 -0
  378. package/dist/daemon/routes/files.js.map +1 -0
  379. package/dist/daemon/routes/git.d.ts +4 -0
  380. package/dist/daemon/routes/git.js +96 -0
  381. package/dist/daemon/routes/git.js.map +1 -0
  382. package/dist/daemon/routes/history.d.ts +4 -0
  383. package/dist/daemon/routes/history.js +101 -0
  384. package/dist/daemon/routes/history.js.map +1 -0
  385. package/dist/daemon/routes/integrations-project.d.ts +3 -0
  386. package/dist/daemon/routes/integrations-project.js +103 -0
  387. package/dist/daemon/routes/integrations-project.js.map +1 -0
  388. package/dist/daemon/routes/integrations.d.ts +3 -0
  389. package/dist/daemon/routes/integrations.js +111 -0
  390. package/dist/daemon/routes/integrations.js.map +1 -0
  391. package/dist/daemon/routes/logs.d.ts +3 -0
  392. package/dist/daemon/routes/logs.js +40 -0
  393. package/dist/daemon/routes/logs.js.map +1 -0
  394. package/dist/daemon/routes/messages.d.ts +4 -0
  395. package/dist/daemon/routes/messages.js +60 -0
  396. package/dist/daemon/routes/messages.js.map +1 -0
  397. package/dist/daemon/routes/observability.d.ts +4 -0
  398. package/dist/daemon/routes/observability.js +247 -0
  399. package/dist/daemon/routes/observability.js.map +1 -0
  400. package/dist/daemon/routes/onboard.d.ts +12 -0
  401. package/dist/daemon/routes/onboard.js +67 -0
  402. package/dist/daemon/routes/onboard.js.map +1 -0
  403. package/dist/daemon/routes/orchestra.d.ts +4 -0
  404. package/dist/daemon/routes/orchestra.js +110 -0
  405. package/dist/daemon/routes/orchestra.js.map +1 -0
  406. package/dist/daemon/routes/pairing.d.ts +4 -0
  407. package/dist/daemon/routes/pairing.js +155 -0
  408. package/dist/daemon/routes/pairing.js.map +1 -0
  409. package/dist/daemon/routes/preview.d.ts +24 -0
  410. package/dist/daemon/routes/preview.js +122 -0
  411. package/dist/daemon/routes/preview.js.map +1 -0
  412. package/dist/daemon/routes/project-actions.d.ts +4 -0
  413. package/dist/daemon/routes/project-actions.js +53 -0
  414. package/dist/daemon/routes/project-actions.js.map +1 -0
  415. package/dist/daemon/routes/project-team.d.ts +4 -0
  416. package/dist/daemon/routes/project-team.js +201 -0
  417. package/dist/daemon/routes/project-team.js.map +1 -0
  418. package/dist/daemon/routes/projects.d.ts +14 -0
  419. package/dist/daemon/routes/projects.js +138 -0
  420. package/dist/daemon/routes/projects.js.map +1 -0
  421. package/dist/daemon/routes/prompts.d.ts +4 -0
  422. package/dist/daemon/routes/prompts.js +44 -0
  423. package/dist/daemon/routes/prompts.js.map +1 -0
  424. package/dist/daemon/routes/proof.d.ts +4 -0
  425. package/dist/daemon/routes/proof.js +22 -0
  426. package/dist/daemon/routes/proof.js.map +1 -0
  427. package/dist/daemon/routes/providers.d.ts +3 -0
  428. package/dist/daemon/routes/providers.js +60 -0
  429. package/dist/daemon/routes/providers.js.map +1 -0
  430. package/dist/daemon/routes/public.d.ts +4 -0
  431. package/dist/daemon/routes/public.js +64 -0
  432. package/dist/daemon/routes/public.js.map +1 -0
  433. package/dist/daemon/routes/queue.d.ts +4 -0
  434. package/dist/daemon/routes/queue.js +140 -0
  435. package/dist/daemon/routes/queue.js.map +1 -0
  436. package/dist/daemon/routes/routing.d.ts +4 -0
  437. package/dist/daemon/routes/routing.js +61 -0
  438. package/dist/daemon/routes/routing.js.map +1 -0
  439. package/dist/daemon/routes/runtime-handler.d.ts +2 -0
  440. package/dist/daemon/routes/runtime-handler.js +63 -0
  441. package/dist/daemon/routes/runtime-handler.js.map +1 -0
  442. package/dist/daemon/routes/search.d.ts +4 -0
  443. package/dist/daemon/routes/search.js +17 -0
  444. package/dist/daemon/routes/search.js.map +1 -0
  445. package/dist/daemon/routes/servers.d.ts +4 -0
  446. package/dist/daemon/routes/servers.js +133 -0
  447. package/dist/daemon/routes/servers.js.map +1 -0
  448. package/dist/daemon/routes/setup.d.ts +3 -0
  449. package/dist/daemon/routes/setup.js +50 -0
  450. package/dist/daemon/routes/setup.js.map +1 -0
  451. package/dist/daemon/routes/shift.d.ts +4 -0
  452. package/dist/daemon/routes/shift.js +30 -0
  453. package/dist/daemon/routes/shift.js.map +1 -0
  454. package/dist/daemon/routes/specs.d.ts +4 -0
  455. package/dist/daemon/routes/specs.js +57 -0
  456. package/dist/daemon/routes/specs.js.map +1 -0
  457. package/dist/daemon/routes/task-delivery.d.ts +4 -0
  458. package/dist/daemon/routes/task-delivery.js +38 -0
  459. package/dist/daemon/routes/task-delivery.js.map +1 -0
  460. package/dist/daemon/routes/team.d.ts +4 -0
  461. package/dist/daemon/routes/team.js +193 -0
  462. package/dist/daemon/routes/team.js.map +1 -0
  463. package/dist/daemon/routes/terminals.d.ts +4 -0
  464. package/dist/daemon/routes/terminals.js +87 -0
  465. package/dist/daemon/routes/terminals.js.map +1 -0
  466. package/dist/daemon/routes/tools.d.ts +4 -0
  467. package/dist/daemon/routes/tools.js +180 -0
  468. package/dist/daemon/routes/tools.js.map +1 -0
  469. package/dist/daemon/routes/updates.d.ts +4 -0
  470. package/dist/daemon/routes/updates.js +89 -0
  471. package/dist/daemon/routes/updates.js.map +1 -0
  472. package/dist/daemon/routes/voice.d.ts +4 -0
  473. package/dist/daemon/routes/voice.js +53 -0
  474. package/dist/daemon/routes/voice.js.map +1 -0
  475. package/dist/daemon/runner.d.ts +186 -0
  476. package/dist/daemon/runner.js +612 -0
  477. package/dist/daemon/runner.js.map +1 -0
  478. package/dist/daemon/runtime/accounting.d.ts +130 -0
  479. package/dist/daemon/runtime/accounting.js +303 -0
  480. package/dist/daemon/runtime/accounting.js.map +1 -0
  481. package/dist/daemon/runtime/agents.d.ts +16 -0
  482. package/dist/daemon/runtime/agents.js +71 -0
  483. package/dist/daemon/runtime/agents.js.map +1 -0
  484. package/dist/daemon/runtime/briefings.d.ts +134 -0
  485. package/dist/daemon/runtime/briefings.js +329 -0
  486. package/dist/daemon/runtime/briefings.js.map +1 -0
  487. package/dist/daemon/runtime/native-usage.d.ts +21 -0
  488. package/dist/daemon/runtime/native-usage.js +71 -0
  489. package/dist/daemon/runtime/native-usage.js.map +1 -0
  490. package/dist/daemon/runtime/queue.d.ts +94 -0
  491. package/dist/daemon/runtime/queue.js +222 -0
  492. package/dist/daemon/runtime/queue.js.map +1 -0
  493. package/dist/daemon/runtime/relay.d.ts +93 -0
  494. package/dist/daemon/runtime/relay.js +240 -0
  495. package/dist/daemon/runtime/relay.js.map +1 -0
  496. package/dist/daemon/runtime/turns.d.ts +202 -0
  497. package/dist/daemon/runtime/turns.js +663 -0
  498. package/dist/daemon/runtime/turns.js.map +1 -0
  499. package/dist/daemon/runtime-support.d.ts +76 -0
  500. package/dist/daemon/runtime-support.js +134 -0
  501. package/dist/daemon/runtime-support.js.map +1 -0
  502. package/dist/daemon/runtime.d.ts +739 -70
  503. package/dist/daemon/runtime.js +2751 -470
  504. package/dist/daemon/runtime.js.map +1 -1
  505. package/dist/daemon/server.d.ts +82 -55
  506. package/dist/daemon/server.js +674 -1435
  507. package/dist/daemon/server.js.map +1 -1
  508. package/dist/daemon/shift.d.ts +70 -0
  509. package/dist/daemon/shift.js +170 -0
  510. package/dist/daemon/shift.js.map +1 -0
  511. package/dist/daemon/specs.d.ts +92 -0
  512. package/dist/daemon/specs.js +285 -0
  513. package/dist/daemon/specs.js.map +1 -0
  514. package/dist/daemon/system.d.ts +208 -0
  515. package/dist/daemon/system.js +557 -0
  516. package/dist/daemon/system.js.map +1 -0
  517. package/dist/daemon/tasks.js +1 -1
  518. package/dist/daemon/team-brain.d.ts +136 -0
  519. package/dist/daemon/team-brain.js +659 -0
  520. package/dist/daemon/team-brain.js.map +1 -0
  521. package/dist/daemon/team-coordinator.d.ts +78 -0
  522. package/dist/daemon/team-coordinator.js +408 -0
  523. package/dist/daemon/team-coordinator.js.map +1 -0
  524. package/dist/daemon/team.d.ts +339 -0
  525. package/dist/daemon/team.js +1433 -0
  526. package/dist/daemon/team.js.map +1 -0
  527. package/dist/daemon/terminals.d.ts +22 -3
  528. package/dist/daemon/terminals.js +236 -16
  529. package/dist/daemon/terminals.js.map +1 -1
  530. package/dist/hub/client.d.ts +120 -0
  531. package/dist/hub/client.js +228 -0
  532. package/dist/hub/client.js.map +1 -0
  533. package/dist/hub/server.d.ts +32 -0
  534. package/dist/hub/server.js +180 -0
  535. package/dist/hub/server.js.map +1 -0
  536. package/dist/hub/supabase-client.d.ts +220 -0
  537. package/dist/hub/supabase-client.js +843 -0
  538. package/dist/hub/supabase-client.js.map +1 -0
  539. package/dist/observability/ask.d.ts +91 -0
  540. package/dist/observability/ask.js +179 -0
  541. package/dist/observability/ask.js.map +1 -0
  542. package/dist/observability/clickhouse.d.ts +33 -0
  543. package/dist/observability/clickhouse.js +48 -0
  544. package/dist/observability/clickhouse.js.map +1 -0
  545. package/dist/observability/decisions.d.ts +176 -0
  546. package/dist/observability/decisions.js +377 -0
  547. package/dist/observability/decisions.js.map +1 -0
  548. package/dist/observability/index.d.ts +89 -0
  549. package/dist/observability/index.js +348 -0
  550. package/dist/observability/index.js.map +1 -0
  551. package/dist/observability/insights.d.ts +151 -0
  552. package/dist/observability/insights.js +319 -0
  553. package/dist/observability/insights.js.map +1 -0
  554. package/dist/observability/logs-query.d.ts +82 -0
  555. package/dist/observability/logs-query.js +102 -0
  556. package/dist/observability/logs-query.js.map +1 -0
  557. package/dist/observability/logs.d.ts +72 -0
  558. package/dist/observability/logs.js +295 -0
  559. package/dist/observability/logs.js.map +1 -0
  560. package/dist/observability/metrics.d.ts +86 -0
  561. package/dist/observability/metrics.js +303 -0
  562. package/dist/observability/metrics.js.map +1 -0
  563. package/dist/observability/otlp.d.ts +127 -0
  564. package/dist/observability/otlp.js +242 -0
  565. package/dist/observability/otlp.js.map +1 -0
  566. package/dist/observability/snapshots.d.ts +43 -0
  567. package/dist/observability/snapshots.js +129 -0
  568. package/dist/observability/snapshots.js.map +1 -0
  569. package/dist/observability/triage.d.ts +49 -0
  570. package/dist/observability/triage.js +284 -0
  571. package/dist/observability/triage.js.map +1 -0
  572. package/dist/providers/adapter.d.ts +50 -0
  573. package/dist/providers/adapter.js +38 -0
  574. package/dist/providers/adapter.js.map +1 -0
  575. package/dist/providers/agent.d.ts +110 -0
  576. package/dist/providers/agent.js +487 -0
  577. package/dist/providers/agent.js.map +1 -0
  578. package/dist/providers/approvals.d.ts +24 -0
  579. package/dist/providers/approvals.js +78 -0
  580. package/dist/providers/approvals.js.map +1 -0
  581. package/dist/providers/attachments.d.ts +20 -0
  582. package/dist/providers/attachments.js +51 -0
  583. package/dist/providers/attachments.js.map +1 -0
  584. package/dist/providers/claude/adapter.d.ts +99 -0
  585. package/dist/providers/claude/adapter.js +790 -0
  586. package/dist/providers/claude/adapter.js.map +1 -0
  587. package/dist/providers/codex/adapter.d.ts +113 -0
  588. package/dist/providers/codex/adapter.js +881 -0
  589. package/dist/providers/codex/adapter.js.map +1 -0
  590. package/dist/providers/codex/instructions.d.ts +11 -0
  591. package/dist/providers/codex/instructions.js +154 -0
  592. package/dist/providers/codex/instructions.js.map +1 -0
  593. package/dist/providers/codex/rpc.d.ts +36 -0
  594. package/dist/providers/codex/rpc.js +104 -0
  595. package/dist/providers/codex/rpc.js.map +1 -0
  596. package/dist/providers/contracts.d.ts +339 -0
  597. package/dist/providers/contracts.js +19 -0
  598. package/dist/providers/contracts.js.map +1 -0
  599. package/dist/providers/directory.d.ts +70 -0
  600. package/dist/providers/directory.js +95 -0
  601. package/dist/providers/directory.js.map +1 -0
  602. package/dist/providers/errors.d.ts +47 -0
  603. package/dist/providers/errors.js +24 -0
  604. package/dist/providers/errors.js.map +1 -0
  605. package/dist/providers/index.d.ts +18 -0
  606. package/dist/providers/index.js +19 -0
  607. package/dist/providers/index.js.map +1 -0
  608. package/dist/providers/ingestion.d.ts +80 -0
  609. package/dist/providers/ingestion.js +330 -0
  610. package/dist/providers/ingestion.js.map +1 -0
  611. package/dist/providers/live.d.ts +22 -0
  612. package/dist/providers/live.js +56 -0
  613. package/dist/providers/live.js.map +1 -0
  614. package/dist/providers/process.d.ts +29 -0
  615. package/dist/providers/process.js +71 -0
  616. package/dist/providers/process.js.map +1 -0
  617. package/dist/providers/reaper.d.ts +33 -0
  618. package/dist/providers/reaper.js +65 -0
  619. package/dist/providers/reaper.js.map +1 -0
  620. package/dist/providers/service.d.ts +87 -0
  621. package/dist/providers/service.js +256 -0
  622. package/dist/providers/service.js.map +1 -0
  623. package/dist/sdk.d.ts +1 -1
  624. package/dist/sdk.js +1 -1
  625. package/dist/sdk.js.map +1 -1
  626. package/dist/types.d.ts +385 -6
  627. package/dist/types.js +1 -0
  628. package/dist/types.js.map +1 -1
  629. package/dist/version.d.ts +7 -0
  630. package/dist/version.js +16 -0
  631. package/dist/version.js.map +1 -0
  632. package/dist/web/app.css +4646 -0
  633. package/dist/web/app.js +22640 -0
  634. package/dist/web/app.js.map +7 -0
  635. package/dist/web/shell.html +39 -0
  636. package/docs/README.md +21 -0
  637. package/docs/SETUP.md +10 -0
  638. package/docs/adapters.md +21 -8
  639. package/docs/brain-continuity.md +186 -0
  640. package/docs/cloud.md +108 -0
  641. package/docs/crews.md +62 -0
  642. package/docs/design-system.md +31 -2
  643. package/docs/integration-notes.md +128 -20
  644. package/docs/loompad.md +105 -0
  645. package/docs/observability.md +220 -0
  646. package/docs/play-store.md +123 -0
  647. package/docs/proposals/agent-teams.md +249 -0
  648. package/docs/proposals/brain-continuity-implementation.md +468 -0
  649. package/docs/proposals/brain-session-continuity.md +623 -0
  650. package/docs/proposals/t3code-port.md +81 -0
  651. package/docs/proposals/t3code-reference.md +95 -0
  652. package/docs/refactoring/AGENT-TEAMS-PROGRESS.md +88 -0
  653. package/docs/refactoring/BOUNDARIES.md +120 -0
  654. package/docs/refactoring/BRAIN-AUDIT.md +176 -0
  655. package/docs/refactoring/BRAIN-NOTES.md +390 -0
  656. package/docs/refactoring/BRAIN-TODO.md +214 -0
  657. package/docs/refactoring/NOTES.md +129 -0
  658. package/docs/refactoring/T3-PORT-NOTES.md +165 -0
  659. package/docs/refactoring/T3-PORT-TODO.md +123 -0
  660. package/docs/refactoring/TODO.md +46 -0
  661. package/docs/refactoring/brain-benchmark-2026-09-28.json +48 -0
  662. package/docs/refactoring/brain-benchmark-audit-2026-09-28.json +48 -0
  663. package/docs/release-notes/v0.2.0.md +35 -0
  664. package/docs/release-notes/v0.2.1.md +59 -0
  665. package/docs/release-notes/v0.2.3.md +77 -0
  666. package/docs/release-notes/v0.2.4.md +128 -0
  667. package/docs/release-notes/v0.2.5.md +153 -0
  668. package/docs/release-notes/v0.2.6.md +62 -0
  669. package/docs/release-notes/v1.0.0.md +120 -0
  670. package/docs/release-notes/v1.0.1.md +77 -0
  671. package/docs/release-notes/v1.1.0.md +71 -0
  672. package/docs/release-notes/v1.1.1.md +38 -0
  673. package/docs/release-notes/v1.1.2.md +21 -0
  674. package/docs/teams-architecture.md +697 -0
  675. package/docs/teams.md +171 -0
  676. package/docs/windows.md +85 -0
  677. package/package.json +22 -9
  678. package/dist/adapters/claude-code.d.ts +0 -26
  679. package/dist/adapters/claude-code.js +0 -209
  680. package/dist/adapters/claude-code.js.map +0 -1
  681. package/dist/adapters/codex.d.ts +0 -51
  682. package/dist/adapters/codex.js +0 -291
  683. package/dist/adapters/codex.js.map +0 -1
  684. package/docs/img/board-linear.png +0 -0
  685. package/docs/img/board-projects.png +0 -0
  686. package/docs/img/brain.png +0 -0
  687. package/docs/img/settings.png +0 -0
  688. package/docs/img/workspace.png +0 -0
@@ -0,0 +1,303 @@
1
+ /**
2
+ * Loom → OpenTelemetry metrics (OTLP/HTTP JSON, `/v1/metrics`).
3
+ *
4
+ * Same shape as the trace exporter next door: aggregate in memory, flush on a
5
+ * short timer, POST, never throw. What differs is that a metric is a fold, not
6
+ * an event — several turns collapse into one datapoint — so this holds a small
7
+ * aggregation table keyed by metric name + attribute set instead of a list.
8
+ *
9
+ * Every number here is derived from a LoomEvent that actually happened. There
10
+ * is no periodic "emit 0 so the chart has a line" path, because a flat zero and
11
+ * "nothing happened" look identical on a graph and only one of them is true.
12
+ *
13
+ * ── Naming ──────────────────────────────────────────────────────────────────
14
+ * `gen_ai.*` names and attributes follow the OpenTelemetry GenAI semantic
15
+ * conventions (semantic-conventions-genai, the GenAI metrics doc split out of
16
+ * the main semconv repo): `gen_ai.client.token.usage` with a
17
+ * `gen_ai.token.type` of `input`/`output`, `gen_ai.client.operation.duration`
18
+ * in seconds, dimensioned by `gen_ai.operation.name`, `gen_ai.system` and
19
+ * `gen_ai.request.model`. `loom.*` names are ours — nothing in the convention
20
+ * covers batons, routes or a per-agent daily spend cap — and follow its style.
21
+ *
22
+ * One deliberate deviation: the convention models `gen_ai.client.token.usage`
23
+ * as a *histogram* (a distribution of tokens-per-request). Loom emits it as a
24
+ * monotonic sum. A histogram earns its keep when you want percentiles over many
25
+ * requests; Loom reports exactly one input/output pair per turn, and what
26
+ * anyone asks this data is "how many tokens did this agent burn today" — a sum
27
+ * answers that exactly, and a histogram's `sum` field only approximates it once
28
+ * you start dropping buckets. `gen_ai.client.operation.duration` stays a
29
+ * histogram, as specified, because turn latency genuinely is a distribution.
30
+ *
31
+ * ── Temporality ─────────────────────────────────────────────────────────────
32
+ * Delta, everywhere (AGGREGATION_TEMPORALITY_DELTA). Cumulative asks the
33
+ * *client* to remember every counter's running total for the life of the
34
+ * process and expects the backend to detect resets. Loom's daemon restarts
35
+ * whenever you edit a config or upgrade the CLI, so cumulative would mean a
36
+ * counter reset every few minutes — the exact case cumulative handles worst.
37
+ * Delta makes each export self-contained: what happened since the last flush,
38
+ * nothing remembered, nothing to reset. Collectors accept both.
39
+ */
40
+ import { encodeAttributes, postOtlp, resourceAttributes, } from "./otlp.js";
41
+ /** OTLP AggregationTemporality. 1 = CUMULATIVE, 2 = DELTA. See the header. */
42
+ const TEMPORALITY_DELTA = 2;
43
+ /** Flush window, matched to the trace exporter's so signals land together. */
44
+ const FLUSH_MS = 1500;
45
+ /** Hard cap on distinct series held between flushes; see `guardCardinality`. */
46
+ const MAX_SERIES = 512;
47
+ /**
48
+ * Bucket boundaries for `gen_ai.client.operation.duration`, in seconds, as
49
+ * specified by the GenAI metrics convention. Kept verbatim rather than tuned:
50
+ * matching the convention is what lets a GenAI dashboard built for any
51
+ * other instrumented service read Loom's turns too.
52
+ */
53
+ const DURATION_BUCKETS_S = [
54
+ 0.01, 0.02, 0.04, 0.08, 0.16, 0.32, 0.64, 1.28, 2.56, 5.12, 10.24, 20.48, 40.96, 81.92,
55
+ ];
56
+ const META = {
57
+ "gen_ai.client.token.usage": { unit: "{token}", description: "Tokens consumed by an agent turn, by token type." },
58
+ "gen_ai.client.operation.duration": { unit: "s", description: "Duration of a completed agent turn." },
59
+ "loom.turns": { unit: "{turn}", description: "Agent turns that reached completion, by outcome." },
60
+ "loom.cost.usd": { unit: "USD", description: "Money an adapter reported spending on a turn." },
61
+ "loom.agents.active": { unit: "{agent}", description: "Agents currently executing a turn." },
62
+ "loom.handoffs": { unit: "{handoff}", description: "Baton handoffs between agents." },
63
+ };
64
+ export class LoomMetrics {
65
+ cfg;
66
+ series = new Map();
67
+ timer = null;
68
+ metricsUrl;
69
+ resource;
70
+ /**
71
+ * Start of the current delta window. A delta datapoint's
72
+ * `startTimeUnixNano`..`timeUnixNano` must bracket exactly the interval the
73
+ * value accumulated over, and consecutive windows must not overlap — so this
74
+ * advances to the previous flush's end rather than being re-read from the
75
+ * clock each time.
76
+ */
77
+ windowStartNs;
78
+ constructor(cfg) {
79
+ this.cfg = cfg;
80
+ this.metricsUrl = `${cfg.endpoint}/v1/metrics`;
81
+ this.resource = resourceAttributes(cfg);
82
+ this.windowStartNs = nowNs();
83
+ }
84
+ get enabled() {
85
+ return this.cfg.metricsEnabled && typeof globalThis.fetch === "function";
86
+ }
87
+ /** Add to a monotonic counter. Negative or non-finite deltas are ignored. */
88
+ addCount(name, value, attributes, isInt = true) {
89
+ if (!this.enabled || !Number.isFinite(value) || value < 0)
90
+ return;
91
+ const bag = this.bag(name);
92
+ const attrs = encodeAttributes(attributes);
93
+ const key = seriesKey(attrs);
94
+ const existing = bag.get(key);
95
+ if (existing?.kind === "sum")
96
+ existing.value += value;
97
+ else if (this.guardCardinality(bag))
98
+ bag.set(key, { kind: "sum", attributes: attrs, value, isInt });
99
+ this.ensureTimer();
100
+ }
101
+ /** Record one observation into a histogram. */
102
+ record(name, value, attributes, bounds = DURATION_BUCKETS_S) {
103
+ if (!this.enabled || !Number.isFinite(value) || value < 0)
104
+ return;
105
+ const bag = this.bag(name);
106
+ const attrs = encodeAttributes(attributes);
107
+ const key = seriesKey(attrs);
108
+ let s = bag.get(key);
109
+ if (s?.kind !== "histogram") {
110
+ if (!this.guardCardinality(bag))
111
+ return;
112
+ s = {
113
+ kind: "histogram",
114
+ attributes: attrs,
115
+ bounds,
116
+ // One bucket per boundary plus the +Inf overflow bucket — OTLP requires
117
+ // bucketCounts.length === explicitBounds.length + 1.
118
+ counts: new Array(bounds.length + 1).fill(0),
119
+ count: 0,
120
+ sum: 0,
121
+ min: value,
122
+ max: value,
123
+ };
124
+ bag.set(key, s);
125
+ }
126
+ let i = 0;
127
+ while (i < s.bounds.length && value > s.bounds[i])
128
+ i++;
129
+ s.counts[i] += 1;
130
+ s.count += 1;
131
+ s.sum += value;
132
+ s.min = Math.min(s.min, value);
133
+ s.max = Math.max(s.max, value);
134
+ this.ensureTimer();
135
+ }
136
+ /** Observe a gauge's current value, as of now. */
137
+ setGauge(name, value, attributes = {}) {
138
+ if (!this.enabled || !Number.isFinite(value))
139
+ return;
140
+ const bag = this.bag(name);
141
+ const attrs = encodeAttributes(attributes);
142
+ const key = seriesKey(attrs);
143
+ if (!bag.has(key) && !this.guardCardinality(bag))
144
+ return;
145
+ bag.set(key, { kind: "gauge", attributes: attrs, value, observedNs: nowNs() });
146
+ this.ensureTimer();
147
+ }
148
+ flush() {
149
+ if (this.timer) {
150
+ clearTimeout(this.timer);
151
+ this.timer = null;
152
+ }
153
+ this.drain();
154
+ }
155
+ /**
156
+ * Build the OTLP payload for everything accumulated so far and clear the
157
+ * window. Exported for tests: asserting on the JSON we would send beats
158
+ * asserting on a mocked fetch, and the payload shape is the part a collector
159
+ * actually rejects.
160
+ */
161
+ collect(nowOverrideNs) {
162
+ if (this.series.size === 0)
163
+ return null;
164
+ const endNs = nowOverrideNs ?? nowNs();
165
+ const startNs = this.windowStartNs;
166
+ const metrics = [];
167
+ for (const [name, bag] of this.series) {
168
+ if (bag.size === 0)
169
+ continue;
170
+ const meta = META[name] ?? { unit: "1", description: "" };
171
+ metrics.push({
172
+ name,
173
+ unit: meta.unit,
174
+ description: meta.description,
175
+ ...body([...bag.values()], startNs, endNs),
176
+ });
177
+ }
178
+ this.series.clear();
179
+ this.windowStartNs = endNs;
180
+ if (metrics.length === 0)
181
+ return null;
182
+ return {
183
+ resourceMetrics: [
184
+ {
185
+ resource: { attributes: this.resource },
186
+ scopeMetrics: [{ scope: { name: "loom" }, metrics }],
187
+ },
188
+ ],
189
+ };
190
+ }
191
+ bag(name) {
192
+ let b = this.series.get(name);
193
+ if (!b)
194
+ this.series.set(name, (b = new Map()));
195
+ return b;
196
+ }
197
+ /**
198
+ * Refuse to open a new series past the cap.
199
+ *
200
+ * Metric attributes come from agent ids and model names, which are bounded in
201
+ * practice — but a misbehaving adapter reporting a unique model string per
202
+ * turn would otherwise turn this map into an unbounded leak and the backend's
203
+ * index into a mess. Dropping the *new* series keeps the ones already being
204
+ * measured intact and correct; the alternative (evicting) would silently
205
+ * discard counts that were already recorded.
206
+ */
207
+ guardCardinality(bag) {
208
+ return bag.size < MAX_SERIES;
209
+ }
210
+ ensureTimer() {
211
+ if (this.timer)
212
+ return;
213
+ this.timer = setTimeout(() => {
214
+ this.timer = null;
215
+ this.drain();
216
+ }, FLUSH_MS);
217
+ if (typeof this.timer.unref === "function")
218
+ this.timer.unref();
219
+ }
220
+ drain() {
221
+ const payload = this.collect();
222
+ if (!payload)
223
+ return;
224
+ postOtlp(this.metricsUrl, this.cfg.headers, payload);
225
+ }
226
+ }
227
+ /** The `sum` / `histogram` / `gauge` body for one metric's worth of series. */
228
+ function body(all, startNs, endNs) {
229
+ const start = startNs.toString();
230
+ const time = endNs.toString();
231
+ const first = all[0];
232
+ if (first.kind === "gauge") {
233
+ // No temporality and no start time on a gauge: it is a reading, not an
234
+ // interval. The timestamp is when we actually observed it.
235
+ return {
236
+ gauge: {
237
+ dataPoints: all
238
+ .filter((s) => s.kind === "gauge")
239
+ .map((s) => ({
240
+ attributes: s.attributes,
241
+ timeUnixNano: s.observedNs.toString(),
242
+ ...numeric(s.value, true),
243
+ })),
244
+ },
245
+ };
246
+ }
247
+ if (first.kind === "histogram") {
248
+ return {
249
+ histogram: {
250
+ aggregationTemporality: TEMPORALITY_DELTA,
251
+ dataPoints: all
252
+ .filter((s) => s.kind === "histogram")
253
+ .map((s) => ({
254
+ attributes: s.attributes,
255
+ startTimeUnixNano: start,
256
+ timeUnixNano: time,
257
+ count: String(s.count),
258
+ sum: s.sum,
259
+ bucketCounts: s.counts.map(String),
260
+ explicitBounds: s.bounds,
261
+ min: s.min,
262
+ max: s.max,
263
+ })),
264
+ },
265
+ };
266
+ }
267
+ return {
268
+ sum: {
269
+ aggregationTemporality: TEMPORALITY_DELTA,
270
+ // Every counter Loom emits only ever goes up within a window — turns
271
+ // completed, tokens spent, dollars burned, batons passed. None of them
272
+ // can be undone, so isMonotonic is the truth and lets a backend offer rate().
273
+ isMonotonic: true,
274
+ dataPoints: all
275
+ .filter((s) => s.kind === "sum")
276
+ .map((s) => ({
277
+ attributes: s.attributes,
278
+ startTimeUnixNano: start,
279
+ timeUnixNano: time,
280
+ ...numeric(s.value, s.isInt),
281
+ })),
282
+ },
283
+ };
284
+ }
285
+ /**
286
+ * `asInt` (int64, string-encoded per the proto3 JSON mapping) or `asDouble`.
287
+ * Counts are integers; money and seconds are not, and rounding either to an int
288
+ * would report $0 for a real three-cent turn.
289
+ */
290
+ function numeric(value, isInt) {
291
+ return isInt ? { asInt: String(Math.round(value)) } : { asDouble: value };
292
+ }
293
+ /** Stable identity for an attribute set, so repeat observations coalesce. */
294
+ function seriesKey(attrs) {
295
+ return attrs
296
+ .map((a) => `${a.key}=${Object.values(a.value)[0]}`)
297
+ .sort()
298
+ .join("");
299
+ }
300
+ function nowNs() {
301
+ return BigInt(Date.now()) * 1000000n;
302
+ }
303
+ //# sourceMappingURL=metrics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.js","sourceRoot":"","sources":["../../src/observability/metrics.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Loom → OpenTelemetry.
3
+ *
4
+ * A self-contained OTLP/HTTP (JSON) trace exporter — no OpenTelemetry SDK
5
+ * dependency, just `fetch`. Loom's agent lifecycle is already a stream of
6
+ * LoomEvents (turns, routes, baton handoffs, memory folds); this maps the
7
+ * notable ones to spans using the OpenTelemetry GenAI semantic conventions
8
+ * (`gen_ai.*`) so they land in the LLM/GenAI views every OTLP backend builds
9
+ * on those conventions.
10
+ *
11
+ * Backend-agnostic on purpose: this speaks OTLP/HTTP and nothing else, so it
12
+ * works against a plain OpenTelemetry Collector, Grafana/Tempo, Jaeger, SigNoz,
13
+ * Honeycomb — anything with a `/v1/traces` endpoint. Point
14
+ * OTEL_EXPORTER_OTLP_ENDPOINT at yours.
15
+ *
16
+ * This module owns traces plus the pieces every signal shares: the resolved
17
+ * config, the resource attributes, and the OTLP attribute encoding. The
18
+ * metrics (`./metrics.ts`) and logs (`./logs.ts`) exporters import those rather
19
+ * than growing their own copy, so a service.name change lands on all three at
20
+ * once and a span, a datapoint and a log line always agree about who emitted
21
+ * them.
22
+ *
23
+ * Egress is best-effort and consent-gated: if no collector is reachable the
24
+ * POST fails silently and never surfaces in the instrumented operation. Turn
25
+ * it off with DO_NOT_TRACK=1 or LOOM_TELEMETRY_DISABLED=1.
26
+ */
27
+ export type KeyValue = {
28
+ key: string;
29
+ value: {
30
+ stringValue: string;
31
+ } | {
32
+ intValue: string;
33
+ } | {
34
+ doubleValue: number;
35
+ } | {
36
+ boolValue: boolean;
37
+ };
38
+ };
39
+ /** Anything we know how to put on an OTLP attribute. */
40
+ export type AttrValue = string | number | boolean | undefined | null;
41
+ export type TelemetryConfig = {
42
+ /** Collector base URL, e.g. http://localhost:4318. */
43
+ endpoint: string;
44
+ /** Logical service name reported to the backend. */
45
+ serviceName: string;
46
+ /** Outgoing headers, including any ingestion key the backend wants. */
47
+ headers: Record<string, string>;
48
+ enabled: boolean;
49
+ /**
50
+ * Per-signal switches, ANDed with `enabled`.
51
+ *
52
+ * The consent opt-outs (DO_NOT_TRACK, LOOM_TELEMETRY_DISABLED, LOOM_OTEL=0)
53
+ * kill everything — consent is not per-signal. These two are a different
54
+ * thing: volume control for someone who wants traces but not the log firehose
55
+ * (Loom ships every agent message as a log record, which is the point, and
56
+ * also a lot of bytes). Off means off for that signal only.
57
+ */
58
+ metricsEnabled: boolean;
59
+ logsEnabled: boolean;
60
+ };
61
+ /** Resolve config from the environment; disabled under standard opt-outs. */
62
+ export declare function resolveTelemetryConfig(env?: NodeJS.ProcessEnv): TelemetryConfig;
63
+ /**
64
+ * The `resource` block every signal carries. Identical across traces, metrics
65
+ * and logs on purpose: a backend keys a service off `service.name`, so a
66
+ * mismatch here is what makes a trace and its own logs look like two different
67
+ * apps.
68
+ */
69
+ export declare function resourceAttributes(cfg: TelemetryConfig): KeyValue[];
70
+ /**
71
+ * Encode an attribute bag, dropping empties.
72
+ *
73
+ * Absent is not the same as zero or "": a datapoint tagged
74
+ * `gen_ai.request.model=""` claims we know the model and it is the empty
75
+ * string. Leaving the key off says we don't know, which is the truth for
76
+ * adapters that never report one.
77
+ */
78
+ export declare function encodeAttributes(attributes: Record<string, AttrValue>): KeyValue[];
79
+ /** 16-byte trace id / 8-byte span id as lowercase hex (no crypto dep needed). */
80
+ export declare function hexId(bytes: number): string;
81
+ export type SpanInput = {
82
+ name: string;
83
+ startNs: bigint;
84
+ endNs: bigint;
85
+ attributes: Record<string, string | number | boolean | undefined | null>;
86
+ error?: string;
87
+ /** Correlate related spans (a turn + its tool calls) under one trace. */
88
+ traceId?: string;
89
+ };
90
+ /** A fresh 16-byte trace id — used to group a turn's spans into one trace. */
91
+ export declare function newTraceId(): string;
92
+ /**
93
+ * The exporter. Batches spans in a short window and POSTs OTLP/HTTP JSON.
94
+ * Failures are swallowed by design so tracing never breaks the app.
95
+ */
96
+ export declare class LoomTelemetry {
97
+ private readonly cfg;
98
+ private buffer;
99
+ private timer;
100
+ private readonly tracesUrl;
101
+ private readonly resourceAttributes;
102
+ constructor(cfg: TelemetryConfig);
103
+ get enabled(): boolean;
104
+ /**
105
+ * Emit one span, returning the span id it was given — or undefined when
106
+ * telemetry is off and no span exists.
107
+ *
108
+ * The id goes back to the caller so a log record covering the same event can
109
+ * carry the same `spanId` and be clickable from that exact span, rather than
110
+ * only from the trace as a whole. Undefined rather than "" for the same
111
+ * reason turnTraceId() is: an empty span id is a link to nothing.
112
+ */
113
+ span(input: SpanInput): string | undefined;
114
+ private ensureTimer;
115
+ private drain;
116
+ flush(): void;
117
+ }
118
+ /**
119
+ * Fire one OTLP/HTTP JSON payload at the collector and forget about it.
120
+ *
121
+ * Deliberately not awaited and deliberately swallowing everything: telemetry is
122
+ * a side channel, and a collector that is down, slow or returning 400 must not
123
+ * become an unhandled rejection in an agent turn. Shared by all three signals
124
+ * so they fail the same silent way.
125
+ */
126
+ export declare function postOtlp(url: string, headers: Record<string, string>, payload: unknown): void;
127
+ export declare function kv(key: string, v: string | number | boolean): KeyValue;
@@ -0,0 +1,242 @@
1
+ /**
2
+ * Loom → OpenTelemetry.
3
+ *
4
+ * A self-contained OTLP/HTTP (JSON) trace exporter — no OpenTelemetry SDK
5
+ * dependency, just `fetch`. Loom's agent lifecycle is already a stream of
6
+ * LoomEvents (turns, routes, baton handoffs, memory folds); this maps the
7
+ * notable ones to spans using the OpenTelemetry GenAI semantic conventions
8
+ * (`gen_ai.*`) so they land in the LLM/GenAI views every OTLP backend builds
9
+ * on those conventions.
10
+ *
11
+ * Backend-agnostic on purpose: this speaks OTLP/HTTP and nothing else, so it
12
+ * works against a plain OpenTelemetry Collector, Grafana/Tempo, Jaeger, SigNoz,
13
+ * Honeycomb — anything with a `/v1/traces` endpoint. Point
14
+ * OTEL_EXPORTER_OTLP_ENDPOINT at yours.
15
+ *
16
+ * This module owns traces plus the pieces every signal shares: the resolved
17
+ * config, the resource attributes, and the OTLP attribute encoding. The
18
+ * metrics (`./metrics.ts`) and logs (`./logs.ts`) exporters import those rather
19
+ * than growing their own copy, so a service.name change lands on all three at
20
+ * once and a span, a datapoint and a log line always agree about who emitted
21
+ * them.
22
+ *
23
+ * Egress is best-effort and consent-gated: if no collector is reachable the
24
+ * POST fails silently and never surfaces in the instrumented operation. Turn
25
+ * it off with DO_NOT_TRACK=1 or LOOM_TELEMETRY_DISABLED=1.
26
+ */
27
+ const SPAN_KIND_INTERNAL = 1;
28
+ const STATUS_OK = 1;
29
+ const STATUS_ERROR = 2;
30
+ /**
31
+ * Parse OTEL_EXPORTER_OTLP_HEADERS — the standard `k=v,k2=v2` form.
32
+ *
33
+ * This is how a backend's ingestion key reaches us without Loom knowing which
34
+ * backend it is. Hardcoding one vendor's header name would have meant a code
35
+ * change per destination.
36
+ */
37
+ function parseOtlpHeaders(raw) {
38
+ const out = {};
39
+ if (!raw)
40
+ return out;
41
+ for (const pair of raw.split(",")) {
42
+ const eq = pair.indexOf("=");
43
+ if (eq <= 0)
44
+ continue;
45
+ const k = pair.slice(0, eq).trim();
46
+ const v = pair.slice(eq + 1).trim();
47
+ if (k && v)
48
+ out[k.toLowerCase()] = decodeURIComponent(v);
49
+ }
50
+ return out;
51
+ }
52
+ /** Resolve config from the environment; disabled under standard opt-outs. */
53
+ export function resolveTelemetryConfig(env = process.env) {
54
+ const optedOut = truthy(env.DO_NOT_TRACK) ||
55
+ truthy(env.LOOM_TELEMETRY_DISABLED) ||
56
+ env.LOOM_OTEL === "0";
57
+ const endpoint = (env.LOOM_OTEL_ENDPOINT ||
58
+ env.OTEL_EXPORTER_OTLP_ENDPOINT ||
59
+ "http://localhost:4318").replace(/\/+$/, "");
60
+ const headers = {
61
+ "content-type": "application/json",
62
+ ...parseOtlpHeaders(env.OTEL_EXPORTER_OTLP_HEADERS),
63
+ };
64
+ const enabled = !optedOut;
65
+ return {
66
+ endpoint,
67
+ serviceName: env.LOOM_SERVICE_NAME || env.OTEL_SERVICE_NAME || "loom",
68
+ headers,
69
+ enabled,
70
+ metricsEnabled: enabled && env.LOOM_OTEL_METRICS !== "0",
71
+ logsEnabled: enabled && env.LOOM_OTEL_LOGS !== "0",
72
+ };
73
+ }
74
+ /**
75
+ * The `resource` block every signal carries. Identical across traces, metrics
76
+ * and logs on purpose: a backend keys a service off `service.name`, so a
77
+ * mismatch here is what makes a trace and its own logs look like two different
78
+ * apps.
79
+ */
80
+ export function resourceAttributes(cfg) {
81
+ return [
82
+ kv("service.name", cfg.serviceName),
83
+ kv("service.namespace", "loom"),
84
+ kv("telemetry.sdk.name", "loom-otlp"),
85
+ kv("telemetry.sdk.language", "nodejs"),
86
+ ];
87
+ }
88
+ /**
89
+ * Encode an attribute bag, dropping empties.
90
+ *
91
+ * Absent is not the same as zero or "": a datapoint tagged
92
+ * `gen_ai.request.model=""` claims we know the model and it is the empty
93
+ * string. Leaving the key off says we don't know, which is the truth for
94
+ * adapters that never report one.
95
+ */
96
+ export function encodeAttributes(attributes) {
97
+ const out = [];
98
+ for (const [k, v] of Object.entries(attributes)) {
99
+ if (v === undefined || v === null || v === "")
100
+ continue;
101
+ out.push(kv(k, v));
102
+ }
103
+ return out;
104
+ }
105
+ function truthy(v) {
106
+ return v === "1" || v === "true" || v === "yes";
107
+ }
108
+ /** 16-byte trace id / 8-byte span id as lowercase hex (no crypto dep needed). */
109
+ export function hexId(bytes) {
110
+ let s = "";
111
+ for (let i = 0; i < bytes; i++)
112
+ s += ((Math.random() * 256) | 0).toString(16).padStart(2, "0");
113
+ return s;
114
+ }
115
+ function str(v) {
116
+ return typeof v === "string" ? v : v == null ? "" : JSON.stringify(v);
117
+ }
118
+ /** A fresh 16-byte trace id — used to group a turn's spans into one trace. */
119
+ export function newTraceId() {
120
+ return hexId(16);
121
+ }
122
+ /**
123
+ * The exporter. Batches spans in a short window and POSTs OTLP/HTTP JSON.
124
+ * Failures are swallowed by design so tracing never breaks the app.
125
+ */
126
+ export class LoomTelemetry {
127
+ cfg;
128
+ buffer = [];
129
+ timer = null;
130
+ tracesUrl;
131
+ resourceAttributes;
132
+ constructor(cfg) {
133
+ this.cfg = cfg;
134
+ this.tracesUrl = `${cfg.endpoint}/v1/traces`;
135
+ this.resourceAttributes = resourceAttributes(cfg);
136
+ }
137
+ get enabled() {
138
+ return this.cfg.enabled && typeof globalThis.fetch === "function";
139
+ }
140
+ /**
141
+ * Emit one span, returning the span id it was given — or undefined when
142
+ * telemetry is off and no span exists.
143
+ *
144
+ * The id goes back to the caller so a log record covering the same event can
145
+ * carry the same `spanId` and be clickable from that exact span, rather than
146
+ * only from the trace as a whole. Undefined rather than "" for the same
147
+ * reason turnTraceId() is: an empty span id is a link to nothing.
148
+ */
149
+ span(input) {
150
+ if (!this.enabled)
151
+ return undefined;
152
+ const spanId = hexId(8);
153
+ this.buffer.push({
154
+ traceId: input.traceId ?? hexId(16),
155
+ spanId,
156
+ name: input.name,
157
+ kind: SPAN_KIND_INTERNAL,
158
+ startTimeUnixNano: input.startNs.toString(),
159
+ endTimeUnixNano: input.endNs.toString(),
160
+ attributes: encodeAttributes(input.attributes),
161
+ status: input.error
162
+ ? { code: STATUS_ERROR, message: input.error }
163
+ : { code: STATUS_OK },
164
+ });
165
+ if (this.buffer.length >= 64)
166
+ this.drain();
167
+ else
168
+ this.ensureTimer();
169
+ return spanId;
170
+ }
171
+ ensureTimer() {
172
+ if (this.timer)
173
+ return;
174
+ this.timer = setTimeout(() => {
175
+ this.timer = null;
176
+ this.drain();
177
+ }, 1500);
178
+ if (typeof this.timer.unref === "function")
179
+ this.timer.unref();
180
+ }
181
+ drain() {
182
+ if (this.buffer.length === 0)
183
+ return;
184
+ const spans = this.buffer;
185
+ this.buffer = [];
186
+ const payload = {
187
+ resourceSpans: [
188
+ {
189
+ resource: { attributes: this.resourceAttributes },
190
+ scopeSpans: [{ scope: { name: "loom" }, spans }],
191
+ },
192
+ ],
193
+ };
194
+ postOtlp(this.tracesUrl, this.cfg.headers, payload);
195
+ }
196
+ flush() {
197
+ if (this.timer) {
198
+ clearTimeout(this.timer);
199
+ this.timer = null;
200
+ }
201
+ this.drain();
202
+ }
203
+ }
204
+ /**
205
+ * Fire one OTLP/HTTP JSON payload at the collector and forget about it.
206
+ *
207
+ * Deliberately not awaited and deliberately swallowing everything: telemetry is
208
+ * a side channel, and a collector that is down, slow or returning 400 must not
209
+ * become an unhandled rejection in an agent turn. Shared by all three signals
210
+ * so they fail the same silent way.
211
+ */
212
+ export function postOtlp(url, headers, payload) {
213
+ void Promise.resolve()
214
+ .then(async () => {
215
+ const res = await globalThis.fetch(url, {
216
+ method: "POST",
217
+ headers,
218
+ body: JSON.stringify(payload),
219
+ // a collector that accepts and stalls would hold every payload for minutes
220
+ signal: AbortSignal.timeout(10_000),
221
+ });
222
+ // Drain the response even though we don't care what it says. undici keeps
223
+ // a pooled socket checked out until its body is consumed, so a fire-and-
224
+ // forget POST that never reads the reply holds a connection open until
225
+ // GC. One signal got away with that; three signals on a busy event stream
226
+ // exhaust the pool and start queueing — which shows up as the *agent*
227
+ // getting slower, i.e. exactly the thing telemetry must never do.
228
+ await res.arrayBuffer().catch(() => undefined);
229
+ })
230
+ .catch(() => {
231
+ /* best-effort: an unreachable collector must never surface */
232
+ });
233
+ }
234
+ export function kv(key, v) {
235
+ if (typeof v === "number") {
236
+ return Number.isInteger(v) ? { key, value: { intValue: String(v) } } : { key, value: { doubleValue: v } };
237
+ }
238
+ if (typeof v === "boolean")
239
+ return { key, value: { boolValue: v } };
240
+ return { key, value: { stringValue: str(v) } };
241
+ }
242
+ //# sourceMappingURL=otlp.js.map