@cohortapp/agent-sdk 2.3.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 (731) hide show
  1. package/.claude/commands/init-agent.md +104 -0
  2. package/.claude/commands/init-maestro.md +1187 -0
  3. package/.claude/settings.json +161 -0
  4. package/.env.example +216 -0
  5. package/README.md +632 -0
  6. package/agents/browser-operator/agent.md +52 -0
  7. package/agents/calendar-ops/agent.md +50 -0
  8. package/agents/communications/agent.md +96 -0
  9. package/agents/decision-log/agent.md +65 -0
  10. package/agents/desktop-operator/agent.md +59 -0
  11. package/agents/gmail-operator/agent.md +62 -0
  12. package/agents/inbound-dispatcher/agent.md +66 -0
  13. package/agents/inbox-processor/agent.md +39 -0
  14. package/agents/pmo-execution/agent.md +60 -0
  15. package/agents/session-spawner/agent.md +64 -0
  16. package/agents/slack-operator/agent.md +60 -0
  17. package/agents/whatsapp-operator/agent.md +60 -0
  18. package/agents/workflow-automation/agent.md +61 -0
  19. package/archetypes/altitudes/c-suite.yaml +58 -0
  20. package/archetypes/altitudes/founder.yaml +68 -0
  21. package/archetypes/altitudes/senior-manager.yaml +63 -0
  22. package/archetypes/altitudes/svp.yaml +60 -0
  23. package/archetypes/altitudes/vp.yaml +50 -0
  24. package/archetypes/archetype.schema.json +77 -0
  25. package/archetypes/base.yaml +47 -0
  26. package/archetypes/capabilities/commercial-leader.yaml +159 -0
  27. package/archetypes/capabilities/compliance-officer.yaml +159 -0
  28. package/archetypes/capabilities/executive-operator.yaml +169 -0
  29. package/archetypes/capabilities/finance-leader.yaml +162 -0
  30. package/archetypes/capabilities/operations-leader.yaml +154 -0
  31. package/archetypes/capabilities/product-leader.yaml +148 -0
  32. package/archetypes/capabilities/technical-leader.yaml +146 -0
  33. package/archetypes/functions/commercial-leader.yaml +62 -0
  34. package/archetypes/functions/compliance-officer.yaml +64 -0
  35. package/archetypes/functions/executive-operator.yaml +70 -0
  36. package/archetypes/functions/finance-leader.yaml +70 -0
  37. package/archetypes/functions/operations-leader.yaml +62 -0
  38. package/archetypes/functions/product-leader.yaml +61 -0
  39. package/archetypes/functions/technical-leader.yaml +57 -0
  40. package/bin/cohort-mcp.mjs +81 -0
  41. package/bin/maestro.mjs +3516 -0
  42. package/bin/maestro.test.mjs +1015 -0
  43. package/desktop-control/README.md +56 -0
  44. package/desktop-control/app-profiles/gmail.yaml +120 -0
  45. package/desktop-control/app-profiles/slack.yaml +315 -0
  46. package/desktop-control/app-profiles/whatsapp.yaml +107 -0
  47. package/docs/architecture/agent-topology.md +2239 -0
  48. package/docs/architecture/archetype-agent-factory.md +110 -0
  49. package/docs/architecture/collective-memory-and-org-mesh.md +115 -0
  50. package/docs/architecture/continuous-monitoring.md +221 -0
  51. package/docs/architecture/mcp-capability-map.md +585 -0
  52. package/docs/architecture/system-architecture.md +1272 -0
  53. package/docs/company-context/README.md +40 -0
  54. package/docs/guides/agent-persona-setup.md +600 -0
  55. package/docs/guides/agents-observe-setup.md +64 -0
  56. package/docs/guides/billing-console-keys.md +88 -0
  57. package/docs/guides/ccxray-diagnostics.md +65 -0
  58. package/docs/guides/channel-bus.md +127 -0
  59. package/docs/guides/claude-mem-setup.md +79 -0
  60. package/docs/guides/claude-pace-setup.md +56 -0
  61. package/docs/guides/claudraband-sessions.md +98 -0
  62. package/docs/guides/clawteam-swarm.md +116 -0
  63. package/docs/guides/code-review-graph-setup.md +86 -0
  64. package/docs/guides/email-setup.md +431 -0
  65. package/docs/guides/mac-mini.md +119 -0
  66. package/docs/guides/media-generation-setup.md +349 -0
  67. package/docs/guides/model-routing.md +162 -0
  68. package/docs/guides/observability-otel.md +265 -0
  69. package/docs/guides/org-onboarding.md +132 -0
  70. package/docs/guides/outbound-governance-setup.md +437 -0
  71. package/docs/guides/pdf-generation-setup.md +315 -0
  72. package/docs/guides/poller-daemon-setup.md +563 -0
  73. package/docs/guides/rag-context-setup.md +459 -0
  74. package/docs/guides/self-optimization-pattern.md +82 -0
  75. package/docs/guides/setup-wizard.md +178 -0
  76. package/docs/guides/slack-setup.md +350 -0
  77. package/docs/guides/telegram-setup.md +227 -0
  78. package/docs/guides/twilio-subaccounts-setup.md +223 -0
  79. package/docs/guides/verification.md +128 -0
  80. package/docs/guides/voice-mode.md +188 -0
  81. package/docs/guides/voice-sms-setup.md +698 -0
  82. package/docs/guides/webhook-relay-setup.md +349 -0
  83. package/docs/guides/whatsapp-setup.md +288 -0
  84. package/docs/prompts/board-pack-cover-template.md +36 -0
  85. package/docs/prompts/decision-recommendation-template.md +88 -0
  86. package/docs/prompts/followup-message-template.md +141 -0
  87. package/docs/prompts/investor-letter-template.md +52 -0
  88. package/docs/prompts/morning-brief-template.md +82 -0
  89. package/docs/prompts/presentation-template.md +58 -0
  90. package/docs/prompts/weekly-strategic-memo-template.md +104 -0
  91. package/docs/research/hallucinated-tool-output-investigation.md +151 -0
  92. package/docs/runbooks/backup-restore.md +205 -0
  93. package/docs/runbooks/cohort-cutover.md +129 -0
  94. package/docs/runbooks/fleet-operations.md +200 -0
  95. package/docs/runbooks/incident-response.md +226 -0
  96. package/docs/runbooks/mac-mini-bootstrap.md +431 -0
  97. package/docs/runbooks/perpetual-operations.md +509 -0
  98. package/docs/runbooks/recovery-and-failover.md +260 -0
  99. package/framework-features.json +267 -0
  100. package/ingest/README.md +87 -0
  101. package/lib/action-executor.js +689 -0
  102. package/lib/action-executor.test.mjs +871 -0
  103. package/lib/agent-root.mjs +37 -0
  104. package/lib/archetype.mjs +236 -0
  105. package/lib/archetype.test.mjs +132 -0
  106. package/lib/autonomy.mjs +114 -0
  107. package/lib/autonomy.test.mjs +66 -0
  108. package/lib/backlog.mjs +358 -0
  109. package/lib/backlog.test.mjs +266 -0
  110. package/lib/budget-guard.mjs +279 -0
  111. package/lib/budget-guard.test.mjs +291 -0
  112. package/lib/cadence-bus-schedule.test.mjs +194 -0
  113. package/lib/cadence-bus.mjs +1120 -0
  114. package/lib/cadence-bus.test.mjs +720 -0
  115. package/lib/cadences.mjs +205 -0
  116. package/lib/cadences.test.mjs +125 -0
  117. package/lib/capability.mjs +154 -0
  118. package/lib/capability.test.mjs +78 -0
  119. package/lib/channels/base-adapter.mjs +719 -0
  120. package/lib/channels/base-adapter.test.mjs +590 -0
  121. package/lib/channels/channel.mjs +128 -0
  122. package/lib/channels/channels.test.mjs +371 -0
  123. package/lib/channels/contract.mjs +215 -0
  124. package/lib/channels/contract.test.mjs +137 -0
  125. package/lib/channels/conversation-resolver.mjs +95 -0
  126. package/lib/channels/gmail/adapter.mjs +87 -0
  127. package/lib/channels/inbox-item.mjs +255 -0
  128. package/lib/channels/inbox-item.test.mjs +335 -0
  129. package/lib/channels/index.mjs +94 -0
  130. package/lib/channels/orgmail/adapter.mjs +353 -0
  131. package/lib/channels/orgmail/adapter.test.mjs +311 -0
  132. package/lib/channels/pairing.mjs +363 -0
  133. package/lib/channels/pairing.test.mjs +270 -0
  134. package/lib/channels/registry.mjs +164 -0
  135. package/lib/channels/slack/adapter.mjs +317 -0
  136. package/lib/channels/slack-adapter.test.mjs +212 -0
  137. package/lib/channels/sms/adapter.mjs +43 -0
  138. package/lib/channels/telegram/adapter.mjs +432 -0
  139. package/lib/channels/telegram-adapter.test.mjs +306 -0
  140. package/lib/channels/voice/adapter.mjs +301 -0
  141. package/lib/channels/voice/adapter.test.mjs +278 -0
  142. package/lib/channels/whatsapp/adapter-baileys.mjs +587 -0
  143. package/lib/channels/whatsapp/adapter-baileys.test.mjs +359 -0
  144. package/lib/channels/whatsapp/adapter-twilio.mjs +65 -0
  145. package/lib/channels/whatsapp/baileys-typing.test.mjs +154 -0
  146. package/lib/charter.mjs +256 -0
  147. package/lib/charter.test.mjs +89 -0
  148. package/lib/claude-bin.mjs +134 -0
  149. package/lib/claude-bin.test.mjs +75 -0
  150. package/lib/collective/capture.mjs +185 -0
  151. package/lib/collective/capture.test.mjs +121 -0
  152. package/lib/collective/cards.mjs +201 -0
  153. package/lib/collective/cards.test.mjs +114 -0
  154. package/lib/collective/config.mjs +186 -0
  155. package/lib/collective/config.test.mjs +123 -0
  156. package/lib/collective/global-config.mjs +113 -0
  157. package/lib/collective/global-config.test.mjs +75 -0
  158. package/lib/collective/presence.mjs +201 -0
  159. package/lib/collective/presence.test.mjs +95 -0
  160. package/lib/collective/recall.mjs +215 -0
  161. package/lib/collective/recall.test.mjs +116 -0
  162. package/lib/comms/send-gate.mjs +554 -0
  163. package/lib/comms/send-gate.test.mjs +577 -0
  164. package/lib/comms.mjs +67 -0
  165. package/lib/comms.test.mjs +41 -0
  166. package/lib/diagnostics/alerts.mjs +424 -0
  167. package/lib/diagnostics/alerts.test.mjs +318 -0
  168. package/lib/diagnostics/backup-freshness.mjs +188 -0
  169. package/lib/diagnostics/backup-freshness.test.mjs +185 -0
  170. package/lib/diagnostics/counters.mjs +269 -0
  171. package/lib/diagnostics/counters.test.mjs +206 -0
  172. package/lib/diagnostics/events.mjs +188 -0
  173. package/lib/diagnostics/events.test.mjs +290 -0
  174. package/lib/diagnostics/otel.mjs +237 -0
  175. package/lib/diagnostics/otel.test.mjs +196 -0
  176. package/lib/diagnostics/trace.mjs +216 -0
  177. package/lib/diagnostics/trace.test.mjs +251 -0
  178. package/lib/env-compat.mjs +74 -0
  179. package/lib/env-compat.test.mjs +104 -0
  180. package/lib/feature-init.mjs +331 -0
  181. package/lib/fs-atomic.mjs +112 -0
  182. package/lib/fs-atomic.test.mjs +72 -0
  183. package/lib/fs-ownership.mjs +111 -0
  184. package/lib/fs-ownership.test.mjs +158 -0
  185. package/lib/hooks/bus.mjs +347 -0
  186. package/lib/hooks/bus.test.mjs +387 -0
  187. package/lib/index.js +16 -0
  188. package/lib/learning/config.mjs +106 -0
  189. package/lib/learning/config.test.mjs +75 -0
  190. package/lib/learning/counters.mjs +156 -0
  191. package/lib/learning/counters.test.mjs +69 -0
  192. package/lib/learning/curator-consolidate.test.mjs +238 -0
  193. package/lib/learning/curator.mjs +453 -0
  194. package/lib/learning/curator.test.mjs +106 -0
  195. package/lib/learning/log.mjs +40 -0
  196. package/lib/learning/reflect.mjs +534 -0
  197. package/lib/learning/reflect.test.mjs +0 -0
  198. package/lib/learning/session-index.mjs +352 -0
  199. package/lib/learning/session-index.test.mjs +125 -0
  200. package/lib/learning/skill-writer.mjs +474 -0
  201. package/lib/learning/skill-writer.test.mjs +210 -0
  202. package/lib/mcp/server.mjs +328 -0
  203. package/lib/mcp/server.test.mjs +400 -0
  204. package/lib/model-router/auth-profiles.mjs +758 -0
  205. package/lib/model-router/auth-profiles.test.mjs +580 -0
  206. package/lib/model-router/catalog/anthropic.yaml +153 -0
  207. package/lib/model-router/catalog/deepseek.yaml +86 -0
  208. package/lib/model-router/catalog/moonshot.yaml +81 -0
  209. package/lib/model-router/catalog/qwen.yaml +114 -0
  210. package/lib/model-router/catalog.mjs +925 -0
  211. package/lib/model-router/catalog.test.mjs +385 -0
  212. package/lib/model-router/economics.mjs +564 -0
  213. package/lib/model-router/economics.test.mjs +344 -0
  214. package/lib/model-router/failover.mjs +298 -0
  215. package/lib/model-router/failover.test.mjs +439 -0
  216. package/lib/model-router/health.mjs +453 -0
  217. package/lib/model-router/health.test.mjs +338 -0
  218. package/lib/model-router/integration-coverage.test.mjs +829 -0
  219. package/lib/model-router/integration.test.mjs +564 -0
  220. package/lib/model-router/ledger.mjs +402 -0
  221. package/lib/model-router/ledger.test.mjs +382 -0
  222. package/lib/model-router/llm-task.mjs +515 -0
  223. package/lib/model-router/llm-task.test.mjs +392 -0
  224. package/lib/model-router/org-credentials.mjs +260 -0
  225. package/lib/model-router/org-credentials.test.mjs +265 -0
  226. package/lib/model-router/pricing-refresh.mjs +463 -0
  227. package/lib/model-router/pricing-refresh.test.mjs +286 -0
  228. package/lib/model-router/reconcile.mjs +429 -0
  229. package/lib/model-router/reconcile.test.mjs +316 -0
  230. package/lib/model-router/repair.mjs +471 -0
  231. package/lib/model-router/repair.test.mjs +180 -0
  232. package/lib/model-router/resolve.mjs +1206 -0
  233. package/lib/model-router/spawn.mjs +497 -0
  234. package/lib/model-router/spawn.test.mjs +425 -0
  235. package/lib/model-router/taxonomy.mjs +893 -0
  236. package/lib/model-router/taxonomy.test.mjs +410 -0
  237. package/lib/model-router.mjs +677 -0
  238. package/lib/model-router.test.mjs +907 -0
  239. package/lib/org/activity.mjs +211 -0
  240. package/lib/org/activity.test.mjs +134 -0
  241. package/lib/org/approvals.mjs +448 -0
  242. package/lib/org/approvals.test.mjs +216 -0
  243. package/lib/org/awareness.mjs +222 -0
  244. package/lib/org/awareness.test.mjs +159 -0
  245. package/lib/org/board.mjs +229 -0
  246. package/lib/org/board.test.mjs +177 -0
  247. package/lib/org/bootstrap-context.mjs +169 -0
  248. package/lib/org/bootstrap-context.test.mjs +153 -0
  249. package/lib/org/client.mjs +1628 -0
  250. package/lib/org/client.test.mjs +1107 -0
  251. package/lib/org/cohort-client.mjs +67 -0
  252. package/lib/org/cohort-client.test.mjs +126 -0
  253. package/lib/org/cost-sync.mjs +227 -0
  254. package/lib/org/cost-sync.test.mjs +153 -0
  255. package/lib/org/doctor.mjs +212 -0
  256. package/lib/org/doctor.test.mjs +212 -0
  257. package/lib/org/handoff.mjs +293 -0
  258. package/lib/org/handoff.test.mjs +269 -0
  259. package/lib/org/integration-tools.mjs +182 -0
  260. package/lib/org/integration-tools.test.mjs +160 -0
  261. package/lib/org/keys.mjs +131 -0
  262. package/lib/org/keys.test.mjs +92 -0
  263. package/lib/org/knowledge.mjs +463 -0
  264. package/lib/org/knowledge.test.mjs +319 -0
  265. package/lib/org/leases.mjs +335 -0
  266. package/lib/org/leases.test.mjs +235 -0
  267. package/lib/org/mesh-integration.test.mjs +127 -0
  268. package/lib/org/mesh.mjs +459 -0
  269. package/lib/org/mesh.test.mjs +345 -0
  270. package/lib/org/messaging.mjs +503 -0
  271. package/lib/org/messaging.test.mjs +238 -0
  272. package/lib/org/policy.mjs +345 -0
  273. package/lib/org/policy.test.mjs +237 -0
  274. package/lib/org/protocol.checksum +1 -0
  275. package/lib/org/protocol.checksum.test.mjs +90 -0
  276. package/lib/org/protocol.mjs +967 -0
  277. package/lib/org/protocol.test.mjs +264 -0
  278. package/lib/org/registry.mjs +194 -0
  279. package/lib/org/registry.test.mjs +100 -0
  280. package/lib/org/tool-surface-integration.test.mjs +120 -0
  281. package/lib/org/tool-surface.mjs +2535 -0
  282. package/lib/org/tool-surface.test.mjs +589 -0
  283. package/lib/org/ui-parity.mjs +3236 -0
  284. package/lib/org/ui-parity.test.mjs +348 -0
  285. package/lib/org/verify.mjs +176 -0
  286. package/lib/org/verify.test.mjs +194 -0
  287. package/lib/rag/embed.mjs +188 -0
  288. package/lib/rag/indexer.mjs +425 -0
  289. package/lib/rag/rag.test.mjs +505 -0
  290. package/lib/rag/search.mjs +475 -0
  291. package/lib/rate-guard.mjs +246 -0
  292. package/lib/rate-guard.test.mjs +201 -0
  293. package/lib/render.mjs +112 -0
  294. package/lib/render.test.mjs +68 -0
  295. package/lib/resource-governor.mjs +297 -0
  296. package/lib/resource-governor.test.mjs +262 -0
  297. package/lib/scheduling/dynamic-jobs.mjs +675 -0
  298. package/lib/scheduling/dynamic-jobs.test.mjs +344 -0
  299. package/lib/scheduling/jitter.mjs +0 -0
  300. package/lib/scheduling/jitter.test.mjs +140 -0
  301. package/lib/secrets/broker.mjs +315 -0
  302. package/lib/secrets/broker.test.mjs +280 -0
  303. package/lib/secrets/providers.mjs +461 -0
  304. package/lib/secrets/providers.test.mjs +274 -0
  305. package/lib/security/audit-engine.mjs +684 -0
  306. package/lib/security/audit-engine.test.mjs +389 -0
  307. package/lib/security/coerce-args.mjs +552 -0
  308. package/lib/security/coerce-args.test.mjs +281 -0
  309. package/lib/security/dangerous-tools.mjs +97 -0
  310. package/lib/security/dangerous-tools.test.mjs +68 -0
  311. package/lib/security/external-content.mjs +145 -0
  312. package/lib/security/external-content.test.mjs +67 -0
  313. package/lib/security/redact.mjs +592 -0
  314. package/lib/security/redact.test.mjs +441 -0
  315. package/lib/security/secret-equal.mjs +73 -0
  316. package/lib/security/secret-equal.test.mjs +55 -0
  317. package/lib/session-permissions.mjs +101 -0
  318. package/lib/session-permissions.test.mjs +100 -0
  319. package/lib/setup/claude-probe.mjs +74 -0
  320. package/lib/setup/completeness.mjs +175 -0
  321. package/lib/setup/completeness.test.mjs +110 -0
  322. package/lib/setup/context-pack.mjs +173 -0
  323. package/lib/setup/context-pack.test.mjs +89 -0
  324. package/lib/setup/enrich.mjs +277 -0
  325. package/lib/setup/enrich.test.mjs +115 -0
  326. package/lib/setup/enroll-from-cohort.mjs +441 -0
  327. package/lib/setup/enroll-from-cohort.test.mjs +233 -0
  328. package/lib/setup/integration.test.mjs +162 -0
  329. package/lib/setup/io.mjs +360 -0
  330. package/lib/setup/io.test.mjs +77 -0
  331. package/lib/setup/run-generator.mjs +81 -0
  332. package/lib/setup/runner.mjs +244 -0
  333. package/lib/setup/runner.test.mjs +132 -0
  334. package/lib/setup/sections/comms.mjs +173 -0
  335. package/lib/setup/sections/company.mjs +120 -0
  336. package/lib/setup/sections/enrich.mjs +138 -0
  337. package/lib/setup/sections/identity.mjs +182 -0
  338. package/lib/setup/sections/identity.test.mjs +140 -0
  339. package/lib/setup/sections/learning.mjs +153 -0
  340. package/lib/setup/sections/learning.test.mjs +81 -0
  341. package/lib/setup/sections/messaging.mjs +219 -0
  342. package/lib/setup/sections/messaging.test.mjs +127 -0
  343. package/lib/setup/sections/model.mjs +102 -0
  344. package/lib/setup/sections/operating-model.mjs +78 -0
  345. package/lib/setup/sections/org.mjs +475 -0
  346. package/lib/setup/sections/org.test.mjs +313 -0
  347. package/lib/setup/sections/orgmail.mjs +173 -0
  348. package/lib/setup/sections/orgmail.test.mjs +118 -0
  349. package/lib/setup/sections/recovery.mjs +159 -0
  350. package/lib/setup/sections/recovery.test.mjs +98 -0
  351. package/lib/setup/sections/tools.mjs +132 -0
  352. package/lib/setup/sections/verify.mjs +97 -0
  353. package/lib/setup/sot.mjs +205 -0
  354. package/lib/setup/sot.test.mjs +81 -0
  355. package/lib/setup/state.mjs +151 -0
  356. package/lib/setup/state.test.mjs +92 -0
  357. package/lib/singleton.js +229 -0
  358. package/lib/singleton.test.mjs +135 -0
  359. package/lib/telemetry/alerts.mjs +216 -0
  360. package/lib/telemetry/alerts.test.mjs +109 -0
  361. package/lib/telemetry/collect.mjs +512 -0
  362. package/lib/telemetry/collect.test.mjs +202 -0
  363. package/lib/tool-definitions-integration.test.mjs +83 -0
  364. package/lib/tool-definitions.js +738 -0
  365. package/lib/tool-definitions.test.mjs +437 -0
  366. package/lib/util/fetch-timeout.mjs +136 -0
  367. package/lib/util/fetch-timeout.test.mjs +202 -0
  368. package/lib/util/reconnect.mjs +343 -0
  369. package/lib/util/reconnect.test.mjs +369 -0
  370. package/lib/util/unhandled.mjs +205 -0
  371. package/lib/util/unhandled.test.mjs +216 -0
  372. package/lib/voice/context-loader.mjs +466 -0
  373. package/lib/voice/index.mjs +100 -0
  374. package/lib/voice/openai-realtime.mjs +510 -0
  375. package/lib/voice/outbound.mjs +542 -0
  376. package/lib/voice/outbound.test.mjs +69 -0
  377. package/lib/voice/post-call-brief.mjs +428 -0
  378. package/lib/voice/provider.mjs +52 -0
  379. package/lib/voice/session-rotation.mjs +257 -0
  380. package/lib/voice/stt.mjs +161 -0
  381. package/lib/voice/stt.test.mjs +226 -0
  382. package/lib/voice/tool-bridge.mjs +370 -0
  383. package/lib/voice/tts.mjs +104 -0
  384. package/lib/voice/twilio-sip-bridge.mjs +288 -0
  385. package/lib/voice/voice.test.mjs +990 -0
  386. package/mcp/README.md +80 -0
  387. package/package.json +151 -0
  388. package/plugins/maestro-skills/plugin.json +139 -0
  389. package/plugins/maestro-skills/skills/agents-observe.md +110 -0
  390. package/plugins/maestro-skills/skills/board-deck.md +68 -0
  391. package/plugins/maestro-skills/skills/books-close.md +77 -0
  392. package/plugins/maestro-skills/skills/brand-steward.md +121 -0
  393. package/plugins/maestro-skills/skills/calendar-plan.md +57 -0
  394. package/plugins/maestro-skills/skills/call-working-sessions.md +124 -0
  395. package/plugins/maestro-skills/skills/ccxray-diagnostics.md +91 -0
  396. package/plugins/maestro-skills/skills/claude-pace.md +61 -0
  397. package/plugins/maestro-skills/skills/code-review-graph.md +99 -0
  398. package/plugins/maestro-skills/skills/crm-pipeline.md +65 -0
  399. package/plugins/maestro-skills/skills/decision-brief.md +89 -0
  400. package/plugins/maestro-skills/skills/directory-hygiene.md +125 -0
  401. package/plugins/maestro-skills/skills/draft-comms.md +84 -0
  402. package/plugins/maestro-skills/skills/evening-wrap.md +53 -0
  403. package/plugins/maestro-skills/skills/files-find.md +65 -0
  404. package/plugins/maestro-skills/skills/generative-ui.md +228 -0
  405. package/plugins/maestro-skills/skills/hiring-triage.md +74 -0
  406. package/plugins/maestro-skills/skills/inbox-triage.md +61 -0
  407. package/plugins/maestro-skills/skills/mail-triage.md +86 -0
  408. package/plugins/maestro-skills/skills/morning-brief.md +54 -0
  409. package/plugins/maestro-skills/skills/native-artifacts.md +157 -0
  410. package/plugins/maestro-skills/skills/org-board.md +133 -0
  411. package/plugins/maestro-skills/skills/org-credential.md +68 -0
  412. package/plugins/maestro-skills/skills/org-recall.md +81 -0
  413. package/plugins/maestro-skills/skills/pipeline-review.md +76 -0
  414. package/plugins/maestro-skills/skills/regulatory-status.md +81 -0
  415. package/plugins/maestro-skills/skills/router-why.md +78 -0
  416. package/plugins/maestro-skills/skills/schedule-meeting.md +91 -0
  417. package/plugins/maestro-skills/skills/session-search.md +71 -0
  418. package/plugins/maestro-skills/skills/set-reminder.md +93 -0
  419. package/plugins/maestro-skills/skills/slack-followup.md +64 -0
  420. package/plugins/maestro-skills/skills/team-activity.md +86 -0
  421. package/plugins/maestro-skills/skills/weekly-memo.md +70 -0
  422. package/policies/action-classification.yaml +114 -0
  423. package/policies/ai-disclosure.yaml +294 -0
  424. package/policies/communication-style.md +139 -0
  425. package/policies/information-barriers.yaml +118 -0
  426. package/policies/prompt-injection-defence.yaml +138 -0
  427. package/public/assets/icon-dark.png +0 -0
  428. package/public/assets/icon-dark.svg +9 -0
  429. package/public/assets/icon-light.svg +9 -0
  430. package/public/assets/logo-dark.svg +15 -0
  431. package/public/assets/logo-light.svg +15 -0
  432. package/scaffold/.mcp.json +7 -0
  433. package/scaffold/CLAUDE.md +368 -0
  434. package/scaffold/config/agent.json +55 -0
  435. package/scaffold/config/agent.ts +76 -0
  436. package/scaffold/config/agent.ts.example +89 -0
  437. package/scaffold/config/alerts.yaml +23 -0
  438. package/scaffold/config/allowlist.yaml.example +25 -0
  439. package/scaffold/config/caller-id-map.yaml +46 -0
  440. package/scaffold/config/collective.yaml +49 -0
  441. package/scaffold/config/company.json +20 -0
  442. package/scaffold/config/known-agents.json +6 -0
  443. package/scaffold/config/learning.yaml +55 -0
  444. package/scaffold/config/model-routing.yaml.example +104 -0
  445. package/scaffold/config/org.yaml +25 -0
  446. package/scaffold/config/orgmail.yaml.example +19 -0
  447. package/scaffold/config/recovery.yaml +72 -0
  448. package/scaffold/config/secrets.yaml +27 -0
  449. package/scaffold/config/slack.yaml.example +35 -0
  450. package/scaffold/config/telegram.yaml.example +38 -0
  451. package/scaffold/config/voice.yaml.example +89 -0
  452. package/scaffold/config/whatsapp.yaml.example +39 -0
  453. package/schedules/README.md +49 -0
  454. package/schedules/triggers/backlog-executor.md +102 -0
  455. package/schedules/triggers/brand-steward.md +72 -0
  456. package/schedules/triggers/daily-evening-wrap.md +159 -0
  457. package/schedules/triggers/daily-midday-sweep.md +58 -0
  458. package/schedules/triggers/daily-morning-brief.md +55 -0
  459. package/schedules/triggers/directory-hygiene.md +81 -0
  460. package/schedules/triggers/dynamic-jobs.md +40 -0
  461. package/schedules/triggers/inbox-processor.md +115 -0
  462. package/schedules/triggers/meeting-action-capture.md +60 -0
  463. package/schedules/triggers/meeting-prep.md +69 -0
  464. package/schedules/triggers/messaging-inbound.md +50 -0
  465. package/schedules/triggers/org-pulse.md +24 -0
  466. package/schedules/triggers/quarterly-self-assessment.md +54 -0
  467. package/schedules/triggers/weekly-engineering-health.md +37 -0
  468. package/schedules/triggers/weekly-execution.md +65 -0
  469. package/schedules/triggers/weekly-hiring.md +53 -0
  470. package/schedules/triggers/weekly-priorities.md +38 -0
  471. package/schedules/triggers/weekly-strategic-memo.md +124 -0
  472. package/scripts/archive-email.sh +55 -0
  473. package/scripts/cadence/cadence-status.mjs +36 -0
  474. package/scripts/cadence/enqueue-cadence-tick.mjs +174 -0
  475. package/scripts/cadence/enqueue-cadence-tick.test.mjs +187 -0
  476. package/scripts/cadence/launchd-cadence-wrapper.sh +85 -0
  477. package/scripts/cadence/launchd-cloud-relay-wrapper.sh +95 -0
  478. package/scripts/cadence/launchd-socket-mode-wrapper.sh +95 -0
  479. package/scripts/ci/check-docs-accuracy.mjs +493 -0
  480. package/scripts/ci/check-docs-accuracy.test.mjs +409 -0
  481. package/scripts/ci/check-exports-exist.mjs +140 -0
  482. package/scripts/ci/check-files-exist.mjs +107 -0
  483. package/scripts/ci/check-no-build-artifacts.mjs +111 -0
  484. package/scripts/ci/check-no-build-artifacts.test.mjs +71 -0
  485. package/scripts/ci/check-no-confidential.mjs +198 -0
  486. package/scripts/ci/check-no-conflict-markers.mjs +169 -0
  487. package/scripts/ci/check-no-residual-identity.mjs +163 -0
  488. package/scripts/ci/check-no-residual-identity.test.mjs +89 -0
  489. package/scripts/ci/check-tarball-fidelity.mjs +205 -0
  490. package/scripts/ci/check-unresolved-tokens.mjs +83 -0
  491. package/scripts/ci/check.mjs +109 -0
  492. package/scripts/ci/check.test.mjs +194 -0
  493. package/scripts/ci/run-coverage.mjs +82 -0
  494. package/scripts/ci/run-tests.mjs +71 -0
  495. package/scripts/cloud-relay/README.md +59 -0
  496. package/scripts/cloud-relay/index.mjs +233 -0
  497. package/scripts/cloud-relay/package.json +15 -0
  498. package/scripts/cloud-relay/railway.json +13 -0
  499. package/scripts/cloud-relay/voice/README.md +94 -0
  500. package/scripts/cloud-relay/voice/package-lock.json +39 -0
  501. package/scripts/cloud-relay/voice/package.json +16 -0
  502. package/scripts/cloud-relay/voice/railway.json +13 -0
  503. package/scripts/cloud-relay/voice/server.mjs +532 -0
  504. package/scripts/collective/hook-runner.mjs +211 -0
  505. package/scripts/collective/hook-runner.test.mjs +90 -0
  506. package/scripts/collective/org-pulse.mjs +72 -0
  507. package/scripts/collective/org-sync.mjs +61 -0
  508. package/scripts/collective/recall.mjs +45 -0
  509. package/scripts/collective/who.mjs +30 -0
  510. package/scripts/comms-monitor.sh +288 -0
  511. package/scripts/configure-whatsapp-sandbox.sh +201 -0
  512. package/scripts/continuous-monitor.sh +91 -0
  513. package/scripts/cost/fleet-digest.mjs +407 -0
  514. package/scripts/cost/fleet-digest.test.mjs +207 -0
  515. package/scripts/cost/track-claude-usage.mjs +169 -0
  516. package/scripts/daemon/agent-daemon.mjs +989 -0
  517. package/scripts/daemon/agent-daemon.test.mjs +525 -0
  518. package/scripts/daemon/cadence-consumer-governance.test.mjs +220 -0
  519. package/scripts/daemon/cadence-consumer.mjs +1080 -0
  520. package/scripts/daemon/cadence-consumer.test.mjs +770 -0
  521. package/scripts/daemon/cadence-handlers.mjs +1121 -0
  522. package/scripts/daemon/cadence-handlers.test.mjs +617 -0
  523. package/scripts/daemon/classifier.mjs +704 -0
  524. package/scripts/daemon/classifier.test.mjs +238 -0
  525. package/scripts/daemon/classify-kind.mjs +54 -0
  526. package/scripts/daemon/classify-kind.test.mjs +40 -0
  527. package/scripts/daemon/context-compiler.mjs +605 -0
  528. package/scripts/daemon/context-compiler.test.mjs +300 -0
  529. package/scripts/daemon/dispatcher-cooldown.test.mjs +122 -0
  530. package/scripts/daemon/dispatcher-governance.test.mjs +886 -0
  531. package/scripts/daemon/dispatcher.mjs +1516 -0
  532. package/scripts/daemon/health.mjs +72 -0
  533. package/scripts/daemon/inbox-deferral.mjs +210 -0
  534. package/scripts/daemon/inbox-deferral.test.mjs +242 -0
  535. package/scripts/daemon/integration.test.mjs +149 -0
  536. package/scripts/daemon/launchd-wrapper-generic.sh +96 -0
  537. package/scripts/daemon/launchd-wrapper-slack-events.sh +37 -0
  538. package/scripts/daemon/launchd-wrapper.sh +91 -0
  539. package/scripts/daemon/lib/session-router.mjs +274 -0
  540. package/scripts/daemon/lib/session-router.test.mjs +295 -0
  541. package/scripts/daemon/maestro-daemon.mjs +275 -0
  542. package/scripts/daemon/prompt-builder.mjs +685 -0
  543. package/scripts/daemon/prompt-builder.test.mjs +213 -0
  544. package/scripts/daemon/responder.mjs +854 -0
  545. package/scripts/daemon/session-lock.mjs +721 -0
  546. package/scripts/daemon/session-lock.test.mjs +252 -0
  547. package/scripts/daemon/session-outcomes.mjs +640 -0
  548. package/scripts/daemon/session-outcomes.test.mjs +533 -0
  549. package/scripts/daemon/typing-registry.mjs +90 -0
  550. package/scripts/daemon/typing-registry.test.mjs +77 -0
  551. package/scripts/daemon/voice-webhook-server.mjs +804 -0
  552. package/scripts/decisions/capture-decision.mjs +116 -0
  553. package/scripts/disclosure_assessment.py +873 -0
  554. package/scripts/disclosure_boundaries.py +562 -0
  555. package/scripts/email-signature-principal.html +52 -0
  556. package/scripts/email-signature.html +60 -0
  557. package/scripts/email_quote_thread.py +167 -0
  558. package/scripts/email_thread_dedup.py +362 -0
  559. package/scripts/emergency-stop.sh +81 -0
  560. package/scripts/healthcheck.sh +116 -0
  561. package/scripts/hooks/block-mcp-cohort-send.sh +15 -0
  562. package/scripts/hooks/block-mcp-slack-send.sh +7 -0
  563. package/scripts/hooks/post-action-log.sh +126 -0
  564. package/scripts/hooks/pre-send-audit.sh +174 -0
  565. package/scripts/hooks/pre-send-audit.test.mjs +215 -0
  566. package/scripts/hooks/session-end-log.sh +27 -0
  567. package/scripts/hooks/session-start-banner.sh +115 -0
  568. package/scripts/huddle/audio-bridge.mjs +664 -0
  569. package/scripts/huddle/boot-slack-cdp.sh +102 -0
  570. package/scripts/huddle/huddle-controller.mjs +942 -0
  571. package/scripts/huddle/huddle-server.mjs +1229 -0
  572. package/scripts/huddle/launch-slack.sh +232 -0
  573. package/scripts/huddle/openai-realtime-bridge.mjs +462 -0
  574. package/scripts/huddle/package-lock.json +62 -0
  575. package/scripts/huddle/package.json +22 -0
  576. package/scripts/huddle/setup-audio.sh +239 -0
  577. package/scripts/huddle/start-call.mjs +318 -0
  578. package/scripts/huddle/test-pipeline.mjs +263 -0
  579. package/scripts/learning/consolidate-skills.mjs +72 -0
  580. package/scripts/learning/session-search.mjs +125 -0
  581. package/scripts/llm_email_dedup.py +442 -0
  582. package/scripts/local-triggers/generate-plists.sh +432 -0
  583. package/scripts/local-triggers/generate-plists.test.mjs +413 -0
  584. package/scripts/local-triggers/install-all.sh +49 -0
  585. package/scripts/local-triggers/plists/.gitkeep +0 -0
  586. package/scripts/local-triggers/run-trigger.sh +63 -0
  587. package/scripts/local-triggers/templates/rag-reindex.plist.template +47 -0
  588. package/scripts/local-triggers/templates/voice-relay-poller.plist.template +54 -0
  589. package/scripts/local-triggers/templates/voice-tunnel.plist.template +55 -0
  590. package/scripts/local-triggers/templates/voice-webhook.plist.template +51 -0
  591. package/scripts/maintenance/backup-to-cloud.sh +124 -0
  592. package/scripts/maintenance/health-check.sh +377 -0
  593. package/scripts/media-generation/README.md +105 -0
  594. package/scripts/media-generation/gemini-image-client.mjs +173 -0
  595. package/scripts/media-generation/generate-assets.mjs +289 -0
  596. package/scripts/media-generation/veo-video-client.mjs +219 -0
  597. package/scripts/org/send-orgmail.mjs +227 -0
  598. package/scripts/outbound-dedup-cleanup.sh +43 -0
  599. package/scripts/outbound-dedup.sh +477 -0
  600. package/scripts/outbound_dedup.py +115 -0
  601. package/scripts/parse-voice-transcript.mjs +481 -0
  602. package/scripts/pdf-generation/README.md +63 -0
  603. package/scripts/pdf-generation/build-document.mjs +247 -0
  604. package/scripts/pdf-generation/templates/board-pack.latex +136 -0
  605. package/scripts/pdf-generation/templates/corporate-letter.latex +126 -0
  606. package/scripts/pdf-generation/templates/memo.latex +114 -0
  607. package/scripts/poll-slack-events.sh +35 -0
  608. package/scripts/poller/calendar-poller.mjs +12 -0
  609. package/scripts/poller/gmail-poller.mjs +192 -0
  610. package/scripts/poller/imap-client.mjs +289 -0
  611. package/scripts/poller/inbox-scan-poller.mjs +156 -0
  612. package/scripts/poller/inbox-scan-poller.test.mjs +231 -0
  613. package/scripts/poller/index.mjs +73 -0
  614. package/scripts/poller/intra-session-check.mjs +285 -0
  615. package/scripts/poller/lib/cloud-relay-dedup.mjs +88 -0
  616. package/scripts/poller/lib/cloud-relay-dedup.test.mjs +133 -0
  617. package/scripts/poller/lib/slash-command-handlers.mjs +177 -0
  618. package/scripts/poller/secondary-gmail-poller.mjs +132 -0
  619. package/scripts/poller/slack-cloud-relay-client.mjs +368 -0
  620. package/scripts/poller/slack-poller.mjs +854 -0
  621. package/scripts/poller/slack-socket-mode.mjs +917 -0
  622. package/scripts/poller/slack-socket-mode.test.mjs +753 -0
  623. package/scripts/poller/trigger.mjs +75 -0
  624. package/scripts/poller/utils.mjs +371 -0
  625. package/scripts/poller/voice-cloud-relay-client.mjs +179 -0
  626. package/scripts/poller/voice-poller.mjs +236 -0
  627. package/scripts/poller-launchd/install.sh +66 -0
  628. package/scripts/poller-launchd/poller.plist.template +40 -0
  629. package/scripts/poller-launchd/whatsapp-handler.plist.template +39 -0
  630. package/scripts/post-interaction-indexer.py +1598 -0
  631. package/scripts/pre-draft-context.py +994 -0
  632. package/scripts/pre_draft_lookup.py +258 -0
  633. package/scripts/rag/build-index.mjs +47 -0
  634. package/scripts/rag/ingest.mjs +111 -0
  635. package/scripts/rag/search.mjs +119 -0
  636. package/scripts/rag-indexer.py +629 -0
  637. package/scripts/restore-from-backup.sh +248 -0
  638. package/scripts/restore-from-backup.test.mjs +178 -0
  639. package/scripts/resume-operations.sh +80 -0
  640. package/scripts/search-secondary-inbox.py +181 -0
  641. package/scripts/secondary-inbox-poller.py +437 -0
  642. package/scripts/self-optimization/compute-metrics.py +398 -0
  643. package/scripts/send-email-as-principal.py +369 -0
  644. package/scripts/send-email-threaded.py +392 -0
  645. package/scripts/send-email-with-attachment.py +377 -0
  646. package/scripts/send-email.sh +131 -0
  647. package/scripts/send-sms.sh +175 -0
  648. package/scripts/send-whatsapp.sh +292 -0
  649. package/scripts/session-start.sh +106 -0
  650. package/scripts/setup/boot-claude-session.sh +94 -0
  651. package/scripts/setup/configure-macos.sh +674 -0
  652. package/scripts/setup/configure-twilio-sip-trunk.mjs +207 -0
  653. package/scripts/setup/configure-voice-tunnel.mjs +182 -0
  654. package/scripts/setup/generate-agent-env.mjs +92 -0
  655. package/scripts/setup/generate-agent-package-json.mjs +222 -0
  656. package/scripts/setup/generate-agent-package-json.test.mjs +143 -0
  657. package/scripts/setup/generate-autonomy.mjs +60 -0
  658. package/scripts/setup/generate-backlog.mjs +101 -0
  659. package/scripts/setup/generate-cadences.mjs +92 -0
  660. package/scripts/setup/generate-capability.mjs +162 -0
  661. package/scripts/setup/generate-charter.mjs +76 -0
  662. package/scripts/setup/generate-comms.mjs +59 -0
  663. package/scripts/setup/generate-company.mjs +92 -0
  664. package/scripts/setup/init-agent.sh +547 -0
  665. package/scripts/setup/init-agent.test.mjs +151 -0
  666. package/scripts/setup/init-archetype.mjs +74 -0
  667. package/scripts/setup/init-backup.mjs +54 -0
  668. package/scripts/setup/init-cadence-bus.mjs +60 -0
  669. package/scripts/setup/init-channel-bus.mjs +46 -0
  670. package/scripts/setup/init-cost-tracking.mjs +45 -0
  671. package/scripts/setup/init-decision-capture.mjs +66 -0
  672. package/scripts/setup/init-known-agents.mjs +57 -0
  673. package/scripts/setup/init-learning.mjs +70 -0
  674. package/scripts/setup/init-memory-executive.mjs +45 -0
  675. package/scripts/setup/init-model-router.mjs +124 -0
  676. package/scripts/setup/init-rag.mjs +174 -0
  677. package/scripts/setup/init-session-router.mjs +38 -0
  678. package/scripts/setup/init-slack-socket-mode.mjs +260 -0
  679. package/scripts/setup/init-telegram.mjs +165 -0
  680. package/scripts/setup/init-voice-realtime.mjs +204 -0
  681. package/scripts/setup/init-whatsapp-baileys.mjs +77 -0
  682. package/scripts/setup/install-dev-tools.sh +150 -0
  683. package/scripts/setup/lib/install-plist.mjs +95 -0
  684. package/scripts/setup/migrate-agent-to-sot.mjs +192 -0
  685. package/scripts/setup/render-environment-yaml.mjs +133 -0
  686. package/scripts/slack-events-ctl.sh +177 -0
  687. package/scripts/slack-events-server.mjs +1045 -0
  688. package/scripts/slack-react.mjs +89 -0
  689. package/scripts/slack-responded.sh +232 -0
  690. package/scripts/slack-send.sh +287 -0
  691. package/scripts/slack-typing.mjs +196 -0
  692. package/scripts/slack-upload-v2.py +95 -0
  693. package/scripts/sms-handler.mjs +450 -0
  694. package/scripts/spawn-session.sh +120 -0
  695. package/scripts/sync-protocol.mjs +217 -0
  696. package/scripts/system-verify.sh +184 -0
  697. package/scripts/test-email-thread-dedup.py +239 -0
  698. package/scripts/test-information-barriers.py +484 -0
  699. package/scripts/test-llm-email-dedup.py +251 -0
  700. package/scripts/test-pre-draft-integration.py +203 -0
  701. package/scripts/test-rag-phase2.sh +442 -0
  702. package/scripts/test-rag-search.sh +251 -0
  703. package/scripts/test-voice-parser.mjs +316 -0
  704. package/scripts/user-context-search.py +659 -0
  705. package/scripts/validate_outbound.py +1504 -0
  706. package/scripts/watchdog/ai.maestro.memory-watchdog.plist +41 -0
  707. package/scripts/watchdog/force-reboot.sh +157 -0
  708. package/scripts/watchdog/memory-watchdog.sh +473 -0
  709. package/scripts/whatsapp-handler.mjs +538 -0
  710. package/teams/desktop-operations.yaml +34 -0
  711. package/teams/executive-office.yaml +27 -0
  712. package/teams/legal-and-regulatory.yaml +24 -0
  713. package/teams/platform-and-engineering.yaml +23 -0
  714. package/teams/strategy-and-growth.yaml +29 -0
  715. package/workflows/continuous/backlog-executor.yaml +141 -0
  716. package/workflows/continuous/inbound-monitor.yaml +168 -0
  717. package/workflows/daily/applicant-triage.yaml +197 -0
  718. package/workflows/daily/comms-triage.yaml +80 -0
  719. package/workflows/daily/evening-wrap.yaml +105 -0
  720. package/workflows/daily/morning-brief.yaml +164 -0
  721. package/workflows/daily/slack-followup-sweep.yaml +87 -0
  722. package/workflows/event-driven/README.md +50 -0
  723. package/workflows/event-driven/agent-failure-investigation.yaml +137 -0
  724. package/workflows/event-driven/pr-review.yaml +107 -0
  725. package/workflows/monthly/board-readiness.yaml +76 -0
  726. package/workflows/quarterly/strategic-scenario-analysis.yaml +85 -0
  727. package/workflows/session-protocol.md +171 -0
  728. package/workflows/weekly/engineering-health.yaml +154 -0
  729. package/workflows/weekly/hiring-review.yaml +169 -0
  730. package/workflows/weekly/rollup-pipeline-review.yaml +76 -0
  731. package/workflows/weekly/strategic-memo.yaml +79 -0
@@ -0,0 +1,269 @@
1
+ /**
2
+ * lib/diagnostics/counters.mjs — loud, durable counters for guard fail-opens.
3
+ *
4
+ * The audit's silent-failure epidemic (gaps-product-quality P0-6) is not that
5
+ * the guards fail open — failing open is the deliberate, correct policy ("a
6
+ * duplicate is cheaper than a missed cadence"; "a breaker we can't persist
7
+ * degrades to in-memory only"). The bug is that they fail open *silently*: a
8
+ * rate-guard whose state dir is unwritable never opens for anyone, a budget-guard
9
+ * that reads a stale ledger as $0 spends forever, and NOTHING anywhere counts it,
10
+ * so doctor and a human stay blind to the exact failure that matters.
11
+ *
12
+ * This module gives every fail-open a place to be *counted somewhere a human or
13
+ * doctor looks*. `bump(name, attrs)` increments an in-memory tally AND appends a
14
+ * JSON line to `logs/diagnostics/counters/<YYYY-MM-DD>.jsonl` (via the shared
15
+ * atomic appender), so the count survives a restart and a crash mid-write can
16
+ * never corrupt the stream. `snapshot()` reads back today's totals for doctor.
17
+ *
18
+ * Invariant: NEITHER `bump` NOR `snapshot` EVER throws. A counter that can take
19
+ * down the path it observes is worse than no counter at all — so a bad name, an
20
+ * un-serializable attr, an unwritable disk all degrade to a return value, never
21
+ * an exception. (This mirrors lib/diagnostics/events.mjs and the fail-open idiom
22
+ * in lib/cadence-bus.mjs.)
23
+ *
24
+ * The in-memory tally is process-local and is the fast path doctor/tests read
25
+ * when they share the process; the JSONL stream is the cross-process / cross-
26
+ * restart source of truth that `snapshot()` reconstructs from disk when asked.
27
+ *
28
+ * Constraints (CLAUDE.md): ESM, Node built-ins + lib/fs-atomic only, injectable
29
+ * clock + log dir + append + read so tests are hermetic and need no real disk.
30
+ *
31
+ * @module lib/diagnostics/counters
32
+ */
33
+
34
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
35
+ import { join, resolve } from "node:path";
36
+
37
+ import { appendJsonl } from "../fs-atomic.mjs";
38
+
39
+ /**
40
+ * Process-local running tally, keyed by counter name. This is a best-effort
41
+ * accelerator: it lets a long-lived daemon (and same-process tests) read totals
42
+ * without re-parsing the JSONL, but it is NOT the source of truth — `snapshot()`
43
+ * reconstructs from disk so counts survive a restart and span processes.
44
+ * @type {Map<string, number>}
45
+ */
46
+ const MEM = new Map();
47
+
48
+ /**
49
+ * Resolve the counters log directory: `<agentRoot>/logs/diagnostics/counters`.
50
+ * An explicit `deps.dir` wins (tests point it at a tmp dir); otherwise we derive
51
+ * it from the agent root the same way every other maestro module does.
52
+ * @param {object} [deps]
53
+ * @returns {string}
54
+ */
55
+ function countersDir(deps = {}) {
56
+ if (deps.dir) return resolve(deps.dir);
57
+ const root = resolve(
58
+ deps.agentRoot ||
59
+ process.env.AGENT_ROOT ||
60
+ process.env.AGENT_DIR ||
61
+ process.cwd()
62
+ );
63
+ return join(root, "logs", "diagnostics", "counters");
64
+ }
65
+
66
+ /**
67
+ * UTC date stamp (YYYY-MM-DD) for an epoch-ms value. The stream rotates by UTC
68
+ * day to match the diagnostics events / cost ledger / budget files, so "today"
69
+ * means the same thing across every accounting and observability path.
70
+ * @param {number} ms
71
+ * @returns {string}
72
+ */
73
+ function dateStamp(ms) {
74
+ return new Date(ms).toISOString().slice(0, 10);
75
+ }
76
+
77
+ /**
78
+ * Coerce a counter name to a stable, non-empty string. A nameless bump would
79
+ * make the stream un-queryable, so we fold the empty/odd cases to "unknown"
80
+ * rather than dropping the row (a dropped fail-open is the bug we're fixing).
81
+ * @param {unknown} name
82
+ * @returns {string}
83
+ */
84
+ function safeName(name) {
85
+ if (typeof name === "string") {
86
+ const trimmed = name.trim();
87
+ if (trimmed) return trimmed;
88
+ }
89
+ if (name != null) {
90
+ const s = String(name).trim();
91
+ if (s) return s;
92
+ }
93
+ return "unknown";
94
+ }
95
+
96
+ /**
97
+ * Best-effort plain-object coercion of `attrs` — never let a caller's odd value
98
+ * (a BigInt, a circular object, a function) make the append throw and violate
99
+ * the never-throw contract. Non-objects are wrapped under `{value}`.
100
+ * @param {unknown} attrs
101
+ * @returns {Record<string, unknown>}
102
+ */
103
+ function safeAttrs(attrs) {
104
+ if (attrs == null) return {};
105
+ if (typeof attrs !== "object" || Array.isArray(attrs)) {
106
+ return { value: typeof attrs === "bigint" ? attrs.toString() : attrs };
107
+ }
108
+ return attrs;
109
+ }
110
+
111
+ /**
112
+ * Increment counter `name` by `by` (default 1), recording the bump both in the
113
+ * in-memory tally and on the date-stamped JSONL stream so a fail-open is counted
114
+ * somewhere durable. Returns the new in-memory total, or `null` if anything went
115
+ * wrong (this function NEVER throws).
116
+ *
117
+ * The persisted row is `{ ts, name, by, attrs }`; `snapshot()` sums `by` per
118
+ * name. We persist each bump as its own row rather than a rolling total so two
119
+ * processes appending concurrently can never clobber each other's count — whole
120
+ * lines interleave (the appendJsonl atomic-append contract) and the read side
121
+ * sums them.
122
+ *
123
+ * @param {string} name counter name (e.g. "rate_guard.persist_failed")
124
+ * @param {Record<string, unknown>} [attrs] structured context (provider, reason)
125
+ * @param {object} [deps]
126
+ * @param {number} [deps.by] increment amount (default 1; non-finite → 1)
127
+ * @param {() => number} [deps.now] clock (default Date.now)
128
+ * @param {string} [deps.dir] explicit counters dir (overrides agentRoot)
129
+ * @param {string} [deps.agentRoot] agent root used to derive the counters dir
130
+ * @param {(path: string, record: unknown) => boolean} [deps.append] writer
131
+ * (default lib/fs-atomic appendJsonl) — injectable for tests
132
+ * @returns {number|null} new in-memory total for `name`, or null on failure
133
+ */
134
+ export function bump(name, attrs = {}, deps = {}) {
135
+ try {
136
+ const key = safeName(name);
137
+ const byRaw = Number(deps.by);
138
+ const by = Number.isFinite(byRaw) && byRaw !== 0 ? byRaw : 1;
139
+
140
+ // In-memory tally first — cheap, can't fail, and is the fast path for a
141
+ // same-process reader even if the disk append below degrades.
142
+ const total = (MEM.get(key) || 0) + by;
143
+ MEM.set(key, total);
144
+
145
+ // Persist as its own JSONL row. The append is best-effort and INDEPENDENTLY
146
+ // guarded: even if an injected/odd writer throws (not just returns false),
147
+ // the in-memory tally has already moved, so the count isn't lost within this
148
+ // process and we still return the durable-intent total.
149
+ try {
150
+ const now = typeof deps.now === "function" ? deps.now : Date.now;
151
+ const append = typeof deps.append === "function" ? deps.append : appendJsonl;
152
+ const ms = now();
153
+ const row = {
154
+ ts: new Date(ms).toISOString(),
155
+ name: key,
156
+ by,
157
+ attrs: safeAttrs(attrs),
158
+ };
159
+ const path = join(countersDir(deps), `${dateStamp(ms)}.jsonl`);
160
+ append(path, row);
161
+ } catch {
162
+ /* disk/append failure must not lose the in-memory count or throw */
163
+ }
164
+ return total;
165
+ } catch {
166
+ // A counter must never crash the observed path.
167
+ return null;
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Read today's persisted counter totals, summed per name from the date-stamped
173
+ * JSONL stream. This is the cross-process / cross-restart view doctor reports;
174
+ * it does NOT read the in-memory tally so it reflects what actually landed on
175
+ * disk (the durable record a second process or a fresh boot can see).
176
+ *
177
+ * NEVER throws: a missing dir, an unreadable file, or a malformed line all
178
+ * degrade — a bad line is skipped, a bad file yields {} — so doctor can always
179
+ * call it.
180
+ *
181
+ * @param {object} [deps]
182
+ * @param {() => number} [deps.now] clock (default Date.now) — selects the day
183
+ * @param {string} [deps.dir] explicit counters dir (overrides agentRoot)
184
+ * @param {string} [deps.agentRoot] agent root used to derive the counters dir
185
+ * @param {(path: string) => string} [deps.read] reader (default readFileSync)
186
+ * @returns {Record<string, number>} { [name]: total } for today; {} if none
187
+ */
188
+ export function snapshot(deps = {}) {
189
+ const totals = {};
190
+ try {
191
+ const now = typeof deps.now === "function" ? deps.now : Date.now;
192
+ const path = join(countersDir(deps), `${dateStamp(now())}.jsonl`);
193
+ const read =
194
+ typeof deps.read === "function"
195
+ ? deps.read
196
+ : (p) => readFileSync(p, "utf-8");
197
+ if (typeof deps.read !== "function" && !existsSync(path)) return totals;
198
+ let raw;
199
+ try {
200
+ raw = read(path);
201
+ } catch {
202
+ return totals; // file vanished / unreadable → no counts, never throw
203
+ }
204
+ if (typeof raw !== "string" || raw.length === 0) return totals;
205
+ for (const line of raw.split("\n")) {
206
+ if (!line) continue;
207
+ let rec;
208
+ try {
209
+ rec = JSON.parse(line);
210
+ } catch {
211
+ continue; // skip a corrupt/partial line, keep summing the rest
212
+ }
213
+ if (!rec || typeof rec.name !== "string") continue;
214
+ const by = Number(rec.by);
215
+ totals[rec.name] = (totals[rec.name] || 0) + (Number.isFinite(by) ? by : 1);
216
+ }
217
+ return totals;
218
+ } catch {
219
+ return totals;
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Read the process-local in-memory tally — the fast, exact view for a reader
225
+ * that shares this process (doctor running in the daemon, a test). Returns a
226
+ * copy so callers can't mutate the live map. Never throws.
227
+ * @returns {Record<string, number>}
228
+ */
229
+ export function memSnapshot() {
230
+ try {
231
+ return Object.fromEntries(MEM.entries());
232
+ } catch {
233
+ return {};
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Reset the in-memory tally. For tests only — the durable JSONL stream is the
239
+ * cross-restart source of truth and is never touched here. Never throws.
240
+ * @returns {void}
241
+ */
242
+ export function resetMem() {
243
+ try {
244
+ MEM.clear();
245
+ } catch {
246
+ /* never throws */
247
+ }
248
+ }
249
+
250
+ /**
251
+ * List the JSONL stream paths under the counters dir (most-recent day last),
252
+ * for doctor / retention to enumerate. Best-effort: returns [] on any error.
253
+ * @param {object} [deps] @param {string} [deps.dir] @param {string} [deps.agentRoot]
254
+ * @returns {string[]} absolute file paths
255
+ */
256
+ export function listStreams(deps = {}) {
257
+ try {
258
+ const dir = countersDir(deps);
259
+ if (!existsSync(dir)) return [];
260
+ return readdirSync(dir)
261
+ .filter((n) => /^\d{4}-\d{2}-\d{2}\.jsonl$/.test(n))
262
+ .sort()
263
+ .map((n) => join(dir, n));
264
+ } catch {
265
+ return [];
266
+ }
267
+ }
268
+
269
+ export default { bump, snapshot, memSnapshot, resetMem, listStreams };
@@ -0,0 +1,206 @@
1
+ /**
2
+ * lib/diagnostics/counters.test.mjs
3
+ *
4
+ * node:test coverage for the loud guard-fail-open counters:
5
+ * - bump increments the in-memory tally and persists a JSONL row
6
+ * - bump persists to the date-stamped path under the counters dir
7
+ * - snapshot sums today's rows per name from disk (cross-restart view)
8
+ * - snapshot is isolated to "today" by the injected clock
9
+ * - bump never throws on an un-serializable attr / bad name / failing append
10
+ * - snapshot never throws on a missing dir / corrupt line / unreadable file
11
+ * - concurrent-style appends (multiple rows) sum correctly
12
+ *
13
+ * Run: cd /Users/layla/maestro && node --test lib/diagnostics/counters.test.mjs
14
+ */
15
+
16
+ import test from "node:test";
17
+ import assert from "node:assert/strict";
18
+ import { mkdtempSync, rmSync, readdirSync, readFileSync } from "node:fs";
19
+ import { tmpdir } from "node:os";
20
+ import { join } from "node:path";
21
+
22
+ import {
23
+ bump,
24
+ snapshot,
25
+ memSnapshot,
26
+ resetMem,
27
+ listStreams,
28
+ } from "./counters.mjs";
29
+
30
+ function tmpDir() {
31
+ return mkdtempSync(join(tmpdir(), "maestro-counters-"));
32
+ }
33
+
34
+ /** Fixed clock at 2026-06-11T12:00:00Z. */
35
+ const FIXED = Date.parse("2026-06-11T12:00:00.000Z");
36
+ const fixedNow = () => FIXED;
37
+
38
+ test("bump increments the in-memory tally and returns the new total", () => {
39
+ resetMem();
40
+ const captured = [];
41
+ const append = (p, row) => { captured.push({ p, row }); return true; };
42
+
43
+ const t1 = bump("rate_guard.persist_failed", { provider: "anthropic" }, { append, now: fixedNow });
44
+ const t2 = bump("rate_guard.persist_failed", { provider: "anthropic" }, { append, now: fixedNow });
45
+ assert.equal(t1, 1);
46
+ assert.equal(t2, 2);
47
+ assert.equal(memSnapshot()["rate_guard.persist_failed"], 2);
48
+ assert.equal(captured.length, 2);
49
+ assert.equal(captured[0].row.name, "rate_guard.persist_failed");
50
+ assert.equal(captured[0].row.by, 1);
51
+ assert.equal(captured[0].row.attrs.provider, "anthropic");
52
+ assert.equal(typeof captured[0].row.ts, "string");
53
+ });
54
+
55
+ test("bump honours an explicit increment amount", () => {
56
+ resetMem();
57
+ const append = () => true;
58
+ assert.equal(bump("budget.stale_ledger", {}, { append, by: 5, now: fixedNow }), 5);
59
+ assert.equal(bump("budget.stale_ledger", {}, { append, by: 3, now: fixedNow }), 8);
60
+ });
61
+
62
+ test("bump persists to a date-stamped path under the counters dir", () => {
63
+ resetMem();
64
+ const dir = tmpDir();
65
+ try {
66
+ bump("g.fail", { reason: "x" }, { dir, now: fixedNow });
67
+ const files = readdirSync(dir);
68
+ assert.deepEqual(files, ["2026-06-11.jsonl"]);
69
+ const raw = readFileSync(join(dir, "2026-06-11.jsonl"), "utf-8").trim();
70
+ const row = JSON.parse(raw);
71
+ assert.equal(row.name, "g.fail");
72
+ assert.equal(row.by, 1);
73
+ assert.equal(row.attrs.reason, "x");
74
+ } finally {
75
+ rmSync(dir, { recursive: true, force: true });
76
+ }
77
+ });
78
+
79
+ test("snapshot sums today's rows per name from disk", () => {
80
+ resetMem();
81
+ const dir = tmpDir();
82
+ try {
83
+ bump("a", {}, { dir, now: fixedNow });
84
+ bump("a", {}, { dir, now: fixedNow });
85
+ bump("b", {}, { dir, now: fixedNow, by: 4 });
86
+ const snap = snapshot({ dir, now: fixedNow });
87
+ assert.equal(snap.a, 2);
88
+ assert.equal(snap.b, 4);
89
+ } finally {
90
+ rmSync(dir, { recursive: true, force: true });
91
+ }
92
+ });
93
+
94
+ test("snapshot is isolated to the clock's day", () => {
95
+ resetMem();
96
+ const dir = tmpDir();
97
+ try {
98
+ const day1 = () => Date.parse("2026-06-11T01:00:00.000Z");
99
+ const day2 = () => Date.parse("2026-06-12T01:00:00.000Z");
100
+ bump("x", {}, { dir, now: day1 });
101
+ bump("x", {}, { dir, now: day1 });
102
+ bump("x", {}, { dir, now: day2 });
103
+ // Two distinct day files exist.
104
+ assert.deepEqual(readdirSync(dir).sort(), ["2026-06-11.jsonl", "2026-06-12.jsonl"]);
105
+ // Each day's snapshot only sees its own rows.
106
+ assert.equal(snapshot({ dir, now: day1 }).x, 2);
107
+ assert.equal(snapshot({ dir, now: day2 }).x, 1);
108
+ } finally {
109
+ rmSync(dir, { recursive: true, force: true });
110
+ }
111
+ });
112
+
113
+ test("snapshot survives across a simulated restart (reads disk, not memory)", () => {
114
+ resetMem();
115
+ const dir = tmpDir();
116
+ try {
117
+ bump("survives", {}, { dir, now: fixedNow });
118
+ bump("survives", {}, { dir, now: fixedNow });
119
+ // Simulate a process restart: the in-memory tally is gone, the disk isn't.
120
+ resetMem();
121
+ assert.deepEqual(memSnapshot(), {});
122
+ assert.equal(snapshot({ dir, now: fixedNow }).survives, 2);
123
+ } finally {
124
+ rmSync(dir, { recursive: true, force: true });
125
+ }
126
+ });
127
+
128
+ test("bump never throws and still moves the in-memory tally when append fails", () => {
129
+ resetMem();
130
+ const failingAppend = () => { throw new Error("disk full"); };
131
+ // append throwing must NOT propagate; the in-memory tally must still advance.
132
+ const total = bump("resilient", {}, { append: failingAppend, now: fixedNow });
133
+ // append threw before returning, but the in-memory bump happened first.
134
+ assert.equal(total, 1);
135
+ assert.equal(memSnapshot().resilient, 1);
136
+ });
137
+
138
+ test("bump tolerates an un-serializable attr and a non-string name", () => {
139
+ resetMem();
140
+ const captured = [];
141
+ const append = (p, row) => { captured.push(row); return true; };
142
+ const circular = {};
143
+ circular.self = circular;
144
+ // A circular attrs object would break naive JSON.stringify; bump must not throw.
145
+ assert.doesNotThrow(() => bump("circ", circular, { append, now: fixedNow }));
146
+ // A non-string name folds to a stable string rather than dropping the row.
147
+ const total = bump(12345, {}, { append, now: fixedNow });
148
+ assert.equal(total, 1);
149
+ assert.equal(captured.at(-1).name, "12345");
150
+ });
151
+
152
+ test("an empty/whitespace name folds to 'unknown'", () => {
153
+ resetMem();
154
+ const captured = [];
155
+ const append = (p, row) => { captured.push(row); return true; };
156
+ bump(" ", {}, { append, now: fixedNow });
157
+ assert.equal(captured.at(-1).name, "unknown");
158
+ });
159
+
160
+ test("snapshot returns {} for a missing dir and never throws", () => {
161
+ resetMem();
162
+ const dir = join(tmpDir(), "does-not-exist");
163
+ assert.deepEqual(snapshot({ dir, now: fixedNow }), {});
164
+ });
165
+
166
+ test("snapshot skips corrupt lines but sums the valid ones", () => {
167
+ resetMem();
168
+ const good = JSON.stringify({ name: "ok", by: 2 });
169
+ const good2 = JSON.stringify({ name: "ok", by: 3 });
170
+ const raw = `${good}\nnot json at all\n${good2}\n{"name":123}\n`;
171
+ const read = () => raw;
172
+ const snap = snapshot({ now: fixedNow, read });
173
+ assert.equal(snap.ok, 5); // 2 + 3; the garbage line and the bad-name line skip
174
+ });
175
+
176
+ test("snapshot treats a row with a missing/NaN by as 1", () => {
177
+ resetMem();
178
+ const raw = `${JSON.stringify({ name: "n" })}\n${JSON.stringify({ name: "n", by: "x" })}\n`;
179
+ const snap = snapshot({ now: fixedNow, read: () => raw });
180
+ assert.equal(snap.n, 2); // both default to +1
181
+ });
182
+
183
+ test("snapshot returns {} when the reader throws (unreadable file)", () => {
184
+ resetMem();
185
+ const read = () => { throw new Error("EACCES"); };
186
+ assert.deepEqual(snapshot({ now: fixedNow, read }), {});
187
+ });
188
+
189
+ test("listStreams enumerates the date-stamped streams in order", () => {
190
+ resetMem();
191
+ const dir = tmpDir();
192
+ try {
193
+ bump("s", {}, { dir, now: () => Date.parse("2026-06-11T00:00:00Z") });
194
+ bump("s", {}, { dir, now: () => Date.parse("2026-06-09T00:00:00Z") });
195
+ const streams = listStreams({ dir });
196
+ assert.equal(streams.length, 2);
197
+ assert.ok(streams[0].endsWith("2026-06-09.jsonl"));
198
+ assert.ok(streams[1].endsWith("2026-06-11.jsonl"));
199
+ } finally {
200
+ rmSync(dir, { recursive: true, force: true });
201
+ }
202
+ });
203
+
204
+ test("listStreams returns [] for a missing dir", () => {
205
+ assert.deepEqual(listStreams({ dir: join(tmpdir(), "nope-counters-xyz") }), []);
206
+ });
@@ -0,0 +1,188 @@
1
+ /**
2
+ * lib/diagnostics/events.mjs — the typed diagnostic-event spine.
3
+ *
4
+ * One in-process event contract that every hot path can emit onto, so a single
5
+ * user interaction is reconstructable end-to-end (gaps-enterprise-ops G7). Each
6
+ * event is appended as one JSON line to a date-stamped stream at
7
+ * `logs/diagnostics/<YYYY-MM-DD>.jsonl` via the shared atomic appender
8
+ * (lib/fs-atomic `appendJsonl`), matching maestro's logging idiom (date-stamped
9
+ * JSONL, fail-open append that can never crash the daemon).
10
+ *
11
+ * The event-type set is CLOSED — an unknown type is coerced to `error` (with the
12
+ * offending type preserved under `attrs._invalid_type`) rather than silently
13
+ * widening the schema. The eight types trace the canonical interaction hops:
14
+ *
15
+ * item_received inbound message/tick admitted (interaction start)
16
+ * classified classifier verdict produced
17
+ * dispatched session spawn decided/launched
18
+ * session_opened claude sub-session started
19
+ * session_closed claude sub-session ended
20
+ * sent an outbound message left the agent (interaction end)
21
+ * guard_fail a governor/breaker/budget/permission gate refused work
22
+ * error an unexpected failure on the path
23
+ *
24
+ * Every row carries `{ts, type, trace_id, span, attrs}`. The `trace_id` is the
25
+ * correlation key (mint with lib/diagnostics/trace.mjs); when omitted, the
26
+ * ambient `withTrace` context fills it in, so an instrumented call site need not
27
+ * thread the id by hand.
28
+ *
29
+ * Invariant: `emitEvent` NEVER throws. A bad directory, an unwritable disk, a
30
+ * non-serializable attr — all degrade to a `false` return, never an exception.
31
+ * Diagnostics must not be able to take down the path they observe.
32
+ *
33
+ * Constraints (CLAUDE.md): ESM, Node built-ins + lib/fs-atomic only, injectable
34
+ * clock + log dir so tests are hermetic.
35
+ *
36
+ * @module lib/diagnostics/events
37
+ */
38
+
39
+ import { join, resolve } from "node:path";
40
+
41
+ import { appendJsonl } from "../fs-atomic.mjs";
42
+ import { currentTrace } from "./trace.mjs";
43
+
44
+ /**
45
+ * The closed set of diagnostic event types. Frozen so callers can reference it
46
+ * (e.g. a dashboard legend) without being able to mutate the contract.
47
+ * @type {Readonly<Record<string, string>>}
48
+ */
49
+ export const EVENT_TYPES = Object.freeze({
50
+ ITEM_RECEIVED: "item_received",
51
+ CLASSIFIED: "classified",
52
+ DISPATCHED: "dispatched",
53
+ SESSION_OPENED: "session_opened",
54
+ SESSION_CLOSED: "session_closed",
55
+ SENT: "sent",
56
+ GUARD_FAIL: "guard_fail",
57
+ ERROR: "error",
58
+ });
59
+
60
+ /** Fast membership set over the closed type vocabulary. */
61
+ const VALID_TYPES = new Set(Object.values(EVENT_TYPES));
62
+
63
+ /**
64
+ * True if `type` is one of the eight contract event types.
65
+ * @param {unknown} type
66
+ * @returns {boolean}
67
+ */
68
+ export function isValidEventType(type) {
69
+ return typeof type === "string" && VALID_TYPES.has(type);
70
+ }
71
+
72
+ /**
73
+ * Resolve the diagnostics log directory for the current agent.
74
+ * @param {object} deps
75
+ * @returns {string}
76
+ */
77
+ function diagnosticsDir(deps = {}) {
78
+ if (deps.logDir) return resolve(deps.logDir);
79
+ const root = resolve(
80
+ deps.agentRoot ||
81
+ process.env.AGENT_ROOT ||
82
+ process.env.AGENT_DIR ||
83
+ process.cwd()
84
+ );
85
+ return join(root, "logs", "diagnostics");
86
+ }
87
+
88
+ /**
89
+ * UTC date stamp (YYYY-MM-DD) for the given epoch ms — the stream is rotated by
90
+ * UTC day, matching the cost ledger and budget files so "today" is consistent
91
+ * across the diagnostics and accounting paths.
92
+ * @param {number} ms
93
+ * @returns {string}
94
+ */
95
+ function dateStamp(ms) {
96
+ return new Date(ms).toISOString().slice(0, 10);
97
+ }
98
+
99
+ /**
100
+ * Best-effort plain-object coercion of `attrs`. We never let a caller's odd
101
+ * value (a BigInt, a circular object, a function) make the append throw — that
102
+ * would violate the never-throw contract. Non-objects are wrapped under
103
+ * `{value}`; an un-serializable object is replaced by a marker.
104
+ * @param {unknown} attrs
105
+ * @returns {Record<string, unknown>}
106
+ */
107
+ function safeAttrs(attrs) {
108
+ if (attrs == null) return {};
109
+ if (typeof attrs !== "object" || Array.isArray(attrs)) {
110
+ return { value: stringifySafe(attrs) };
111
+ }
112
+ return attrs;
113
+ }
114
+
115
+ /**
116
+ * Stringify a scalar defensively (BigInt → string, etc.). Used only for the
117
+ * wrapped-value fallback above; the row as a whole is serialized by appendJsonl.
118
+ * @param {unknown} v
119
+ * @returns {unknown}
120
+ */
121
+ function stringifySafe(v) {
122
+ if (typeof v === "bigint") return v.toString();
123
+ return v;
124
+ }
125
+
126
+ /**
127
+ * Emit one diagnostic event onto the date-stamped JSONL stream.
128
+ *
129
+ * The row shape is `{ ts, type, trace_id, span, attrs }`:
130
+ * - `ts` ISO-8601 timestamp (from the injected/default clock)
131
+ * - `type` one of EVENT_TYPES; an unknown type is coerced to `error`
132
+ * and the original preserved at `attrs._invalid_type`
133
+ * - `trace_id` the supplied id, else the ambient `withTrace` id, else null
134
+ * - `span` the supplied span, else the ambient span, else "root"
135
+ * - `attrs` caller payload (defensively coerced to a plain object)
136
+ *
137
+ * @param {object} event
138
+ * @param {string} event.type one of EVENT_TYPES
139
+ * @param {string} [event.trace_id] correlation id (defaults to ambient trace)
140
+ * @param {string} [event.span] span path (defaults to ambient span / "root")
141
+ * @param {Record<string, unknown>} [event.attrs] structured payload
142
+ * @param {object} [deps]
143
+ * @param {() => number} [deps.now] clock (default Date.now)
144
+ * @param {string} [deps.logDir] explicit diagnostics dir (overrides agentRoot)
145
+ * @param {string} [deps.agentRoot] agent root used to derive logs/diagnostics
146
+ * @param {(path: string, record: unknown) => boolean} [deps.append] writer
147
+ * (default lib/fs-atomic appendJsonl) — injectable for tests
148
+ * @returns {boolean} true if the line was written, false on any failure
149
+ */
150
+ export function emitEvent(event, deps = {}) {
151
+ try {
152
+ const now = typeof deps.now === "function" ? deps.now : Date.now;
153
+ const append = typeof deps.append === "function" ? deps.append : appendJsonl;
154
+
155
+ const ev = event && typeof event === "object" ? event : {};
156
+ const ambient = currentTrace();
157
+
158
+ const validType = isValidEventType(ev.type);
159
+ const type = validType ? ev.type : EVENT_TYPES.ERROR;
160
+
161
+ const trace_id =
162
+ (typeof ev.trace_id === "string" && ev.trace_id) ||
163
+ (ambient && ambient.trace_id) ||
164
+ null;
165
+ const span =
166
+ (typeof ev.span === "string" && ev.span) ||
167
+ (ambient && ambient.span) ||
168
+ "root";
169
+
170
+ const attrs = { ...safeAttrs(ev.attrs) };
171
+ if (!validType) attrs._invalid_type = ev.type === undefined ? null : ev.type;
172
+
173
+ const ms = now();
174
+ const row = {
175
+ ts: new Date(ms).toISOString(),
176
+ type,
177
+ trace_id,
178
+ span,
179
+ attrs,
180
+ };
181
+
182
+ const path = join(diagnosticsDir(deps), `${dateStamp(ms)}.jsonl`);
183
+ return append(path, row) === true;
184
+ } catch {
185
+ // Diagnostics must never crash the observed path.
186
+ return false;
187
+ }
188
+ }