@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,67 @@
1
+ /**
2
+ * lib/org/cohort-client.mjs — RETIRED shim.
3
+ *
4
+ * This module USED to speak a defunct contract (PUT `${apiUrl}/{orgId}/agents/{id}`,
5
+ * POST `${base}/agents/{id}/status`, GET `${base}/agents`) that hq's /api/v1 RPC
6
+ * surface NEVER exposed. It is now a thin re-export of the aligned client in
7
+ * lib/org/client.mjs so the published export `@cohortapp/agent-sdk/org/cohort-client`
8
+ * keeps resolving for any external importer, while the wire behaviour is the
9
+ * correct one:
10
+ * - publishEntry → registry.register (POST /api/v1/registry.register)
11
+ * - fetchDirectory → GET /api/v1/directory
12
+ * - isEnabled → the client's enablement check
13
+ * - heartbeat → presence.beat liveness (the old per-agent status POST is
14
+ * fully superseded; presence.beat rides the status snapshot,
15
+ * driven by lib/org/mesh.mjs through the same client).
16
+ *
17
+ * Fail-open is preserved end-to-end by client.mjs (benign value on error/disabled,
18
+ * never a throw). `fetchImpl` stays injectable for tests. New code SHOULD import
19
+ * from "./client.mjs" directly; this shim exists only for back-compat.
20
+ *
21
+ * @module lib/org/cohort-client
22
+ */
23
+
24
+ "use strict";
25
+
26
+ export { isEnabled, publishEntry, fetchDirectory } from "./client.mjs";
27
+
28
+ import { configFromAgent, isEnabled, presenceBeat } from "./client.mjs";
29
+
30
+ /**
31
+ * Heartbeat this agent's liveness. The legacy per-agent status POST
32
+ * (`${base}/agents/{id}/status`) has NO equivalent on hq; liveness is the
33
+ * presence.beat snapshot. This shim maps the old `{cfg,id,status,focus,presence}`
34
+ * call onto presence.beat (strict status snapshot subset) so external callers of
35
+ * the old export keep working. New code should use lib/org/mesh.mjs (which beats
36
+ * the full telemetry snapshot) or client.presenceBeat directly. Fail-open.
37
+ *
38
+ * @param {object} o - { cfg, id?, status?, focus?, presence?, fetchImpl? }
39
+ * @returns {Promise<{ok:boolean, status?:number, skipped?:boolean, reason?:string}>}
40
+ */
41
+ export async function heartbeat(o = {}) {
42
+ const { cfg } = o;
43
+ if (!isEnabled(cfg)) return { ok: false, skipped: true };
44
+ const c = configFromAgent(cfg);
45
+ // Build a minimal, strict-schema-safe status snapshot. presence.beat's status
46
+ // is z.object().strict() over {state,activity,subAgentsRunning,machine,
47
+ // claudeAuth,alerts,...}; keep this a conservative subset.
48
+ const state = typeof o.status === "string" && o.status ? o.status : "active";
49
+ const params = {
50
+ focus: o.focus || [],
51
+ status: {
52
+ state,
53
+ activity: "",
54
+ subAgentsRunning: 0,
55
+ machine: "",
56
+ claudeAuth: "unknown",
57
+ alerts: [],
58
+ },
59
+ };
60
+ const frame = await presenceBeat(params, {
61
+ base: c.base,
62
+ token: c.token,
63
+ orgId: c.orgId,
64
+ fetchImpl: o.fetchImpl,
65
+ });
66
+ return { ok: !!(frame && frame.ok), status: frame && frame.ok ? 200 : 0 };
67
+ }
@@ -0,0 +1,126 @@
1
+ /**
2
+ * cohort-client.test.mjs — RETIRED shim re-exporting the aligned client.mjs.
3
+ *
4
+ * The old module spoke a defunct contract (PUT `${apiUrl}/{orgId}/agents/{id}`,
5
+ * POST `${base}/agents/{id}/status`, GET `${base}/agents`). It is now a thin
6
+ * re-export of lib/org/client.mjs, so these tests assert the NEW wire shape:
7
+ * - publishEntry → POST /v1/registry.register (Bearer + x-idempotency-key)
8
+ * - fetchDirectory → GET /v1/directory (parses members/agents/array shapes)
9
+ * - heartbeat → POST /v1/presence.beat (liveness snapshot)
10
+ * and that the OLD `/{orgId}/agents` endpoints are NEVER hit.
11
+ * Run: node --test lib/org/cohort-client.test.mjs
12
+ */
13
+ "use strict";
14
+
15
+ import { test } from "node:test";
16
+ import assert from "node:assert/strict";
17
+
18
+ import { isEnabled, publishEntry, heartbeat, fetchDirectory } from "./cohort-client.mjs";
19
+ import { PROTOCOL_VERSION } from "./protocol.mjs";
20
+
21
+ // Ambient COHORT_* creds would leak into header assertions; clear them so the
22
+ // tests are hermetic regardless of host env.
23
+ delete process.env.COHORT_API_TOKEN;
24
+ delete process.env.COHORT_TOKEN;
25
+ delete process.env.COHORT_API_KEY;
26
+ delete process.env.COHORT_ORG_ID;
27
+
28
+ const BASE = "https://org.cohortapp.com";
29
+ // New enablement contract: enabled + (base || apiUrl+orgId).
30
+ const ON = { org: { cohort: { enabled: true, base: BASE, token: "tok-xyz" } } };
31
+ const OFF = { org: { cohort: { enabled: false, base: BASE } } };
32
+
33
+ /** A fake fetch that records calls and returns a canned res frame. */
34
+ function fakeFetch(responder) {
35
+ const calls = [];
36
+ const impl = async (url, init) => {
37
+ calls.push({ url, init, headers: (init && init.headers) || {} });
38
+ const r = responder ? responder(url, init, calls.length - 1) : null;
39
+ const status = r && typeof r.status === "number" ? r.status : 200;
40
+ const ok = r && typeof r.ok === "boolean" ? r.ok : status >= 200 && status < 300;
41
+ const body = r ? r.body : { ok: true, result: null };
42
+ return { ok, status, headers: { get: () => undefined }, json: async () => body };
43
+ };
44
+ impl.calls = calls;
45
+ return impl;
46
+ }
47
+
48
+ test("isEnabled: requires enabled + (base or apiUrl+orgId)", () => {
49
+ assert.equal(isEnabled(ON), true);
50
+ assert.equal(isEnabled(OFF), false);
51
+ assert.equal(isEnabled({ org: { cohort: { enabled: true, apiUrl: "x", orgId: "o" } } }), true);
52
+ assert.equal(isEnabled(null), false);
53
+ });
54
+
55
+ test("publishEntry: no-op disabled; POSTs registry.register (NOT /agents) when enabled", async () => {
56
+ assert.deepEqual(await publishEntry({ cfg: OFF, entry: { id: "a" } }), { ok: false, skipped: true });
57
+ const f = fakeFetch(() => ({ body: { ok: true, result: null } }));
58
+ const r = await publishEntry({ cfg: ON, entry: { id: "alex-ai", name: "Alex" }, fetchImpl: f });
59
+ assert.equal(r.ok, true);
60
+ assert.equal(f.calls[0].url, `${BASE}/api/v1/registry.register`);
61
+ assert.equal(f.calls[0].init.method, "POST");
62
+ assert.equal(f.calls[0].headers.authorization, "Bearer tok-xyz");
63
+ assert.equal(f.calls[0].headers["x-org-protocol"], String(PROTOCOL_VERSION));
64
+ assert.equal(f.calls[0].headers["x-idempotency-key"], "registry.register:alex-ai");
65
+ // the OLD /{orgId}/agents endpoint must NEVER be hit
66
+ assert.doesNotMatch(f.calls[0].url, /\/agents\//);
67
+ });
68
+
69
+ test("publishEntry: trims a rich entry to the strict registerSchema (archetype object → string)", async () => {
70
+ const f = fakeFetch(() => ({ body: { ok: true, result: null } }));
71
+ const entry = {
72
+ id: "robin-ai",
73
+ name: "Robin",
74
+ fullName: "Robin Vega",
75
+ archetype: { function: "engineering", altitude: "vp", label: "VP Eng" },
76
+ principal: { fullName: "Sam Human", title: "CEO" },
77
+ towers: [{ id: "t1", name: "Platform" }],
78
+ focus: [{ title: "ship the thing" }],
79
+ };
80
+ await publishEntry({ cfg: ON, entry, fetchImpl: f });
81
+ const sent = JSON.parse(f.calls[0].init.body);
82
+ assert.equal(sent.displayName, "Robin Vega");
83
+ assert.equal(sent.archetype, "VP Eng");
84
+ assert.equal(sent.humanSponsor, "Sam Human");
85
+ // the full rich entry rides under `card` (server chain-redacts to hasCard)
86
+ assert.equal(sent.card.id, "robin-ai");
87
+ assert.deepEqual(sent.card.archetype, entry.archetype);
88
+ });
89
+
90
+ test("publishEntry: server error frame → {ok:false} (fail-open shape preserved)", async () => {
91
+ const f = fakeFetch(() => ({ ok: false, status: 500, body: { ok: false, error: { code: "INTERNAL", message: "boom" } } }));
92
+ const r = await publishEntry({ cfg: ON, entry: { id: "x" }, fetchImpl: f });
93
+ assert.equal(r.ok, false);
94
+ });
95
+
96
+ test("heartbeat: no-op disabled; POSTs presence.beat (NOT /agents/{id}/status) when enabled", async () => {
97
+ assert.deepEqual(await heartbeat({ cfg: OFF, id: "a" }), { ok: false, skipped: true });
98
+ const f = fakeFetch(() => ({ body: { ok: true, result: { directives: [] } } }));
99
+ const r = await heartbeat({ cfg: ON, id: "alex-ai", status: "active", fetchImpl: f });
100
+ assert.equal(r.ok, true);
101
+ assert.equal(f.calls[0].url, `${BASE}/api/v1/presence.beat`);
102
+ assert.equal(f.calls[0].init.method, "POST");
103
+ assert.doesNotMatch(f.calls[0].url, /\/agents\//);
104
+ const sent = JSON.parse(f.calls[0].init.body);
105
+ assert.equal(sent.status.state, "active");
106
+ });
107
+
108
+ test("fetchDirectory: [] disabled; parses array + {members:[]} + {agents:[]} shapes", async () => {
109
+ assert.deepEqual(await fetchDirectory({ cfg: OFF }), []);
110
+ const arr = await fetchDirectory({ cfg: ON, fetchImpl: fakeFetch(() => ({ body: [{ id: "x" }] })) });
111
+ assert.equal(arr.length, 1);
112
+ const members = await fetchDirectory({ cfg: ON, fetchImpl: fakeFetch(() => ({ body: { members: [{ id: "m1" }, { id: "m2" }] } })) });
113
+ assert.equal(members.length, 2);
114
+ const agents = await fetchDirectory({ cfg: ON, fetchImpl: fakeFetch(() => ({ body: { agents: [{ id: "y" }] } })) });
115
+ assert.equal(agents.length, 1);
116
+ // GET /v1/directory, never the old /{orgId}/agents
117
+ const f = fakeFetch(() => ({ body: { members: [] } }));
118
+ await fetchDirectory({ cfg: ON, fetchImpl: f });
119
+ assert.equal(f.calls[0].url, `${BASE}/api/v1/directory`);
120
+ assert.equal(f.calls[0].init.method, "GET");
121
+ });
122
+
123
+ test("fetchDirectory: network error → [] (fail-open)", async () => {
124
+ const arr = await fetchDirectory({ cfg: ON, fetchImpl: async () => { throw new Error("network down"); } });
125
+ assert.deepEqual(arr, []);
126
+ });
@@ -0,0 +1,227 @@
1
+ /**
2
+ * lib/org/cost-sync.mjs — ship the maestro router ledger to Cohort cost.report.
3
+ *
4
+ * The agent reports its per-session model spend into the org-wide rollup (SPEC
5
+ * §4.8 cost telemetry). The source of truth is the local router ledger written by
6
+ * lib/model-router/ledger.mjs to `state/cost-tracking/<date>.jsonl` (one JSON row
7
+ * per session: agent, model, input_tokens, output_tokens, estimated_usd,
8
+ * decision_id, cadence/source). This module reads today's rows and POSTs them to
9
+ * `cost.report`, batched and IDEMPOTENT on each row's id so a re-run never
10
+ * double-counts — the cadence (`org-sync`) can fire this every tick safely.
11
+ *
12
+ * Reconciliation: the ledger is local-first and remains the budget-guard SoT
13
+ * (budget-guard sums `estimated_usd` from the SAME file). cost.report only
14
+ * MIRRORS those rows into the org rollup; it never mutates the local ledger, so
15
+ * the local and org views always reconcile by construction. `reportLedger`
16
+ * returns the local total alongside the per-row push results so a caller can
17
+ * assert ledger.localUSD == rollup contribution.
18
+ *
19
+ * G16 (zero-LLM-in-the-coordination-loop): this is a pure file-read + HTTP push.
20
+ * It makes ZERO LLM calls. Critically, it preserves each row's `source`/`cadence`
21
+ * field on the wire so the org rollup can attribute LLM spend to a coordination
22
+ * cadence — that attribution is exactly what makes a G16 violation VISIBLE in
23
+ * `cost.rollup`. Do not strip `source`/`cadence` from the reported row.
24
+ *
25
+ * Idempotency key: derived per row from a stable identity (row.id ||
26
+ * `${decision_id}:${session_id}:${ts}` || a content hash). The server is
27
+ * idempotent on cost.report, so the same row reported twice is a no-op replay.
28
+ *
29
+ * Node builtins only. Fail-open (a down/disabled server reports nothing rather
30
+ * than throwing). `fetchImpl`, `now`, and `ledgerDir`/`agentRoot` are injectable
31
+ * for deterministic tests. ESM.
32
+ *
33
+ * @module lib/org/cost-sync
34
+ */
35
+
36
+ "use strict";
37
+
38
+ import { createHash } from "node:crypto";
39
+ import { existsSync, readFileSync } from "node:fs";
40
+ import { join, resolve } from "node:path";
41
+
42
+ import { isEnabled, configFromAgent, costReport } from "./client.mjs";
43
+
44
+ const REPORT_BATCH = 100;
45
+
46
+ // ---------------------------------------------------------------------------
47
+ // Ledger location (mirror of lib/model-router/ledger.mjs)
48
+ // ---------------------------------------------------------------------------
49
+
50
+ function ledgerDir(deps = {}) {
51
+ if (deps.ledgerDir) return resolve(deps.ledgerDir);
52
+ const root = deps.agentRoot || process.env.AGENT_ROOT || process.env.AGENT_DIR || process.cwd();
53
+ return join(resolve(root), "state", "cost-tracking");
54
+ }
55
+
56
+ function dayStr(deps = {}) {
57
+ if (deps.day) return String(deps.day);
58
+ const now = (typeof deps.now === "function" ? deps.now : Date.now)();
59
+ return new Date(now).toISOString().slice(0, 10);
60
+ }
61
+
62
+ function ledgerFile(deps = {}) {
63
+ return join(ledgerDir(deps), `${dayStr(deps)}.jsonl`);
64
+ }
65
+
66
+ // ---------------------------------------------------------------------------
67
+ // Read + normalise ledger rows
68
+ // ---------------------------------------------------------------------------
69
+
70
+ /**
71
+ * Read the JSONL ledger rows for a day. NEVER throws — a missing/corrupt file or
72
+ * an unparseable line yields fewer rows, never an error. Pure over the fs.
73
+ *
74
+ * @param {object} deps - { ledgerDir?, agentRoot?, day?, now? }
75
+ * @returns {object[]} parsed ledger rows (in file order)
76
+ */
77
+ export function readLedgerRows(deps = {}) {
78
+ const file = ledgerFile(deps);
79
+ if (!existsSync(file)) return [];
80
+ let body;
81
+ try { body = readFileSync(file, "utf-8"); } catch { return []; }
82
+ const rows = [];
83
+ for (const line of body.split("\n")) {
84
+ if (!line.trim()) continue;
85
+ try { rows.push(JSON.parse(line)); } catch { /* skip a corrupt line */ }
86
+ }
87
+ return rows;
88
+ }
89
+
90
+ /**
91
+ * Derive a stable idempotency identity for a ledger row: row.id when present,
92
+ * else a composite of the natural keys, else a content hash. Two reports of the
93
+ * SAME logical session collapse to one row in the rollup. Pure.
94
+ *
95
+ * @param {object} row
96
+ * @returns {string}
97
+ */
98
+ export function rowId(row) {
99
+ if (!row || typeof row !== "object") return "";
100
+ if (row.id) return String(row.id);
101
+ if (row.row_id) return String(row.row_id);
102
+ const natural = [row.decision_id, row.session_id, row.ts].filter(Boolean).join(":");
103
+ if (natural) return natural;
104
+ // Last resort: hash the content so identical rows dedupe and distinct rows don't.
105
+ return "h:" + createHash("sha256").update(stableJson(row)).digest("hex").slice(0, 24);
106
+ }
107
+
108
+ function stableJson(value) {
109
+ const sortDeep = (v) => {
110
+ if (Array.isArray(v)) return v.map(sortDeep);
111
+ if (v && typeof v === "object") {
112
+ const out = {};
113
+ for (const k of Object.keys(v).sort()) out[k] = sortDeep(v[k]);
114
+ return out;
115
+ }
116
+ return v;
117
+ };
118
+ return JSON.stringify(sortDeep(value));
119
+ }
120
+
121
+ /**
122
+ * Project a ledger row onto the cost.report wire shape. Preserves the
123
+ * router-ledger fields the rollup aggregates by (agent / model / cadence|source /
124
+ * day) and the budget figure (estimated_usd). KEEPS `source`/`cadence` for the
125
+ * G16 attribution line. Pure.
126
+ *
127
+ * @param {object} row
128
+ * @param {object} deps - { day? }
129
+ * @returns {object} the wire row (with a stable `id`)
130
+ */
131
+ export function toReportRow(row, deps = {}) {
132
+ const id = rowId(row);
133
+ const day = (row && row.ts && String(row.ts).slice(0, 10)) || dayStr(deps);
134
+ return {
135
+ id,
136
+ agent: row.agent ?? null,
137
+ model: row.model ?? null,
138
+ backend: row.backend ?? null,
139
+ input_tokens: numOr0(row.input_tokens),
140
+ output_tokens: numOr0(row.output_tokens),
141
+ cache_read_tokens: numOr0(row.cache_read_tokens),
142
+ cache_creation_tokens: numOr0(row.cache_creation_tokens),
143
+ estimated_usd: numOr0(row.estimated_usd),
144
+ total_cost_usd: row.total_cost_usd ?? null,
145
+ decision_id: row.decision_id ?? null,
146
+ session_id: row.session_id ?? null,
147
+ // G16: attribution fields — DO NOT STRIP. cadence/source surface coordination
148
+ // spend in the rollup so an LLM-in-the-loop violation is visible.
149
+ source: row.source ?? null,
150
+ cadence: row.cadence ?? row.lane ?? null,
151
+ task_class: row.task_class ?? null,
152
+ ts: row.ts ?? null,
153
+ day,
154
+ };
155
+ }
156
+
157
+ function numOr0(v) {
158
+ const n = Number(v);
159
+ return Number.isFinite(n) ? n : 0;
160
+ }
161
+
162
+ // ---------------------------------------------------------------------------
163
+ // Report — batched, idempotent push to cost.report
164
+ // ---------------------------------------------------------------------------
165
+
166
+ /**
167
+ * Read today's router ledger and report its rows to Cohort via cost.report,
168
+ * batched and idempotent. Cadence-friendly: safe to call every org-sync tick —
169
+ * the server's cost.report idempotency on each row's id collapses replays.
170
+ *
171
+ * Reconciles by returning the LOCAL ledger total alongside the push outcome so a
172
+ * caller can assert the org rollup contribution matches the local budget figure.
173
+ *
174
+ * Always resolves (fail-open): a disabled/unreachable server reports 0 rows.
175
+ *
176
+ * @param {object} o - {
177
+ * cfg, agentRoot?, ledgerDir?, day?, now?, batchSize?, fetchImpl?
178
+ * }
179
+ * @returns {Promise<{
180
+ * enabled:boolean, rowsRead:number, reported:number, failed:number,
181
+ * localUSD:number, batches:Array<{count:number, ok:boolean, code?:string}>
182
+ * }>}
183
+ */
184
+ export async function reportLedger(o = {}) {
185
+ const rows = readLedgerRows(o);
186
+ const localUSD = round6(rows.reduce((s, r) => s + numOr0(r.estimated_usd), 0));
187
+
188
+ if (!isEnabled(o.cfg)) {
189
+ return { enabled: false, rowsRead: rows.length, reported: 0, failed: 0, localUSD, batches: [] };
190
+ }
191
+ if (rows.length === 0) {
192
+ return { enabled: true, rowsRead: 0, reported: 0, failed: 0, localUSD, batches: [] };
193
+ }
194
+
195
+ const c = configFromAgent(o.cfg);
196
+ const batchSize = Number.isFinite(o.batchSize) && o.batchSize > 0 ? Math.floor(o.batchSize) : REPORT_BATCH;
197
+ const wireRows = rows.map((r) => toReportRow(r, o));
198
+
199
+ let reported = 0;
200
+ let failed = 0;
201
+ const batches = [];
202
+ for (let i = 0; i < wireRows.length; i += batchSize) {
203
+ const slice = wireRows.slice(i, i + batchSize);
204
+ // Stable idempotency key over the batch's row ids so a re-pushed identical
205
+ // batch is a no-op replay (the server is also per-row idempotent on id).
206
+ const key = "cost.report:" + createHash("sha256").update(slice.map((r) => r.id).join("|")).digest("hex").slice(0, 24);
207
+ const frame = await costReport({ rows: slice }, {
208
+ base: c.base,
209
+ token: c.token,
210
+ idempotencyKey: key,
211
+ fetchImpl: o.fetchImpl,
212
+ });
213
+ const ok = !!(frame && frame.ok);
214
+ if (ok) reported += slice.length; else failed += slice.length;
215
+ batches.push({ count: slice.length, ok, code: ok ? undefined : (frame && frame.error && frame.error.code) || "error" });
216
+ }
217
+
218
+ return { enabled: true, rowsRead: rows.length, reported, failed, localUSD, batches };
219
+ }
220
+
221
+ function round6(n) {
222
+ return Number(Number(n).toFixed(6));
223
+ }
224
+
225
+ export const _internals = { ledgerFile, ledgerDir, dayStr, stableJson, REPORT_BATCH };
226
+
227
+ export default { readLedgerRows, rowId, toReportRow, reportLedger };
@@ -0,0 +1,153 @@
1
+ /**
2
+ * cost-sync.test.mjs — read today's router ledger and report rows to Cohort via
3
+ * cost.report, idempotently and batched, preserving the G16 attribution fields,
4
+ * reconciling with the local budget total. Tmp ledger dir + injected fetchImpl.
5
+ * Run: node --test lib/org/cost-sync.test.mjs
6
+ */
7
+ "use strict";
8
+
9
+ import { test } from "node:test";
10
+ import assert from "node:assert/strict";
11
+ import { mkdtempSync, mkdirSync, writeFileSync } from "node:fs";
12
+ import { tmpdir } from "node:os";
13
+ import { join } from "node:path";
14
+
15
+ import { readLedgerRows, rowId, toReportRow, reportLedger } from "./cost-sync.mjs";
16
+
17
+ const BASE = "https://org.cohortapp.com";
18
+ const CFG = { org: { cohort: { enabled: true, base: BASE, token: "tok", orgId: "alex-ai" } } };
19
+ const OFF = { org: { cohort: { enabled: false } } };
20
+ const DAY = "2026-06-11";
21
+
22
+ delete process.env.COHORT_API_TOKEN;
23
+ delete process.env.COHORT_TOKEN;
24
+
25
+ /** Write a JSONL ledger file for a day under a fresh tmp ledger dir. Returns dir. */
26
+ function tmpLedger(rows, day = DAY) {
27
+ const dir = mkdtempSync(join(tmpdir(), "cost-sync-"));
28
+ mkdirSync(dir, { recursive: true });
29
+ const body = rows.map((r) => JSON.stringify(r)).join("\n") + "\n";
30
+ writeFileSync(join(dir, `${day}.jsonl`), body);
31
+ return dir;
32
+ }
33
+
34
+ function fakeFetch(responder) {
35
+ const calls = [];
36
+ const impl = async (url, init) => {
37
+ calls.push({ url, init, headers: (init && init.headers) || {}, body: init && init.body ? JSON.parse(init.body) : null });
38
+ const r = responder ? responder(url, init, calls.length - 1) : null;
39
+ const status = r && typeof r.status === "number" ? r.status : 200;
40
+ const ok = r && typeof r.ok === "boolean" ? r.ok : status >= 200 && status < 300;
41
+ const body = r ? r.body : { ok: true, result: null };
42
+ return { ok, status, headers: { get: () => undefined }, json: async () => body };
43
+ };
44
+ impl.calls = calls;
45
+ return impl;
46
+ }
47
+
48
+ const SAMPLE_ROWS = [
49
+ { ts: `${DAY}T01:00:00.000Z`, agent: "alex", model: "claude-opus-4", input_tokens: 1000, output_tokens: 200, estimated_usd: 0.05, decision_id: "d-1", session_id: "s-1", source: "router", cadence: null },
50
+ { ts: `${DAY}T02:00:00.000Z`, agent: "alex", model: "claude-haiku", input_tokens: 500, output_tokens: 50, estimated_usd: 0.001, session_id: "s-2", source: "cadence", lane: "org-sync" },
51
+ ];
52
+
53
+ // --- read + normalise ------------------------------------------------------
54
+
55
+ test("readLedgerRows: parses JSONL, skips blank/corrupt lines, [] when missing", () => {
56
+ const dir = tmpLedger(SAMPLE_ROWS);
57
+ const rows = readLedgerRows({ ledgerDir: dir, day: DAY });
58
+ assert.equal(rows.length, 2);
59
+ assert.equal(rows[0].decision_id, "d-1");
60
+ // missing file → []
61
+ assert.deepEqual(readLedgerRows({ ledgerDir: dir, day: "1999-01-01" }), []);
62
+ // corrupt line is skipped
63
+ const dir2 = mkdtempSync(join(tmpdir(), "cost-sync-corrupt-"));
64
+ writeFileSync(join(dir2, `${DAY}.jsonl`), '{"agent":"a","estimated_usd":1}\nnot json\n\n{"agent":"b","estimated_usd":2}\n');
65
+ assert.equal(readLedgerRows({ ledgerDir: dir2, day: DAY }).length, 2);
66
+ });
67
+
68
+ test("rowId: stable identity (id > natural keys > content hash)", () => {
69
+ assert.equal(rowId({ id: "explicit" }), "explicit");
70
+ assert.equal(rowId({ decision_id: "d", session_id: "s", ts: "t" }), "d:s:t");
71
+ const h1 = rowId({ agent: "a", estimated_usd: 1 });
72
+ const h2 = rowId({ estimated_usd: 1, agent: "a" }); // key order independent
73
+ assert.equal(h1, h2);
74
+ assert.notEqual(rowId({ agent: "a" }), rowId({ agent: "b" }));
75
+ });
76
+
77
+ test("toReportRow: preserves the G16 attribution fields (source/cadence) + budget figure", () => {
78
+ const wire = toReportRow(SAMPLE_ROWS[1], { day: DAY });
79
+ assert.equal(wire.source, "cadence");
80
+ assert.equal(wire.cadence, "org-sync"); // falls back to `lane`
81
+ assert.equal(wire.estimated_usd, 0.001);
82
+ assert.equal(wire.day, DAY);
83
+ assert.ok(wire.id);
84
+ });
85
+
86
+ // --- reportLedger: idempotent batched push ---------------------------------
87
+
88
+ test("reportLedger: posts rows to cost.report, reconciles local USD total", async () => {
89
+ const dir = tmpLedger(SAMPLE_ROWS);
90
+ const f = fakeFetch((url) => (url.endsWith("/v1/cost.report") ? { body: { ok: true, result: { ingested: 2 } } } : null));
91
+ const out = await reportLedger({ cfg: CFG, ledgerDir: dir, day: DAY, fetchImpl: f });
92
+ assert.equal(out.enabled, true);
93
+ assert.equal(out.rowsRead, 2);
94
+ assert.equal(out.reported, 2);
95
+ assert.equal(out.failed, 0);
96
+ // reconciles with budget-guard's view (sum of estimated_usd)
97
+ assert.equal(out.localUSD, 0.051);
98
+ // one batch (default batch size), routed to cost.report
99
+ const reportCalls = f.calls.filter((c) => c.url.endsWith("/v1/cost.report"));
100
+ assert.equal(reportCalls.length, 1);
101
+ assert.equal(reportCalls[0].body.rows.length, 2);
102
+ // each wire row carried its stable id + attribution
103
+ assert.ok(reportCalls[0].body.rows[0].id);
104
+ assert.equal(reportCalls[0].body.rows[1].source, "cadence");
105
+ });
106
+
107
+ test("reportLedger: idempotency key is stable across re-runs of the same rows", async () => {
108
+ const dir = tmpLedger(SAMPLE_ROWS);
109
+ const f1 = fakeFetch(() => ({ body: { ok: true, result: null } }));
110
+ const f2 = fakeFetch(() => ({ body: { ok: true, result: null } }));
111
+ await reportLedger({ cfg: CFG, ledgerDir: dir, day: DAY, fetchImpl: f1 });
112
+ await reportLedger({ cfg: CFG, ledgerDir: dir, day: DAY, fetchImpl: f2 });
113
+ const k1 = f1.calls.find((c) => c.url.endsWith("/v1/cost.report")).headers["x-idempotency-key"];
114
+ const k2 = f2.calls.find((c) => c.url.endsWith("/v1/cost.report")).headers["x-idempotency-key"];
115
+ assert.ok(k1);
116
+ assert.equal(k1, k2); // same rows → same key → server collapses the replay
117
+ });
118
+
119
+ test("reportLedger: batches by batchSize", async () => {
120
+ const many = Array.from({ length: 5 }, (_, i) => ({ id: `r-${i}`, agent: "a", estimated_usd: 0.01 }));
121
+ const dir = tmpLedger(many);
122
+ const f = fakeFetch(() => ({ body: { ok: true, result: null } }));
123
+ const out = await reportLedger({ cfg: CFG, ledgerDir: dir, day: DAY, batchSize: 2, fetchImpl: f });
124
+ assert.equal(out.reported, 5);
125
+ assert.equal(out.batches.length, 3); // 2 + 2 + 1
126
+ assert.deepEqual(out.batches.map((b) => b.count), [2, 2, 1]);
127
+ });
128
+
129
+ test("reportLedger: disabled org → reports nothing but still reconciles local total", async () => {
130
+ const dir = tmpLedger(SAMPLE_ROWS);
131
+ const out = await reportLedger({ cfg: OFF, ledgerDir: dir, day: DAY });
132
+ assert.equal(out.enabled, false);
133
+ assert.equal(out.reported, 0);
134
+ assert.equal(out.localUSD, 0.051);
135
+ });
136
+
137
+ test("reportLedger: server error → rows counted as failed, never throws", async () => {
138
+ const dir = tmpLedger(SAMPLE_ROWS);
139
+ const f = fakeFetch(() => ({ ok: false, status: 500, body: { ok: false, error: { code: "INTERNAL", message: "boom" } } }));
140
+ const out = await reportLedger({ cfg: CFG, ledgerDir: dir, day: DAY, fetchImpl: f });
141
+ assert.equal(out.reported, 0);
142
+ assert.equal(out.failed, 2);
143
+ assert.equal(out.batches[0].code, "INTERNAL");
144
+ });
145
+
146
+ test("reportLedger: empty ledger → no call, zero totals", async () => {
147
+ const dir = mkdtempSync(join(tmpdir(), "cost-sync-empty-"));
148
+ const f = fakeFetch(() => ({ body: { ok: true, result: null } }));
149
+ const out = await reportLedger({ cfg: CFG, ledgerDir: dir, day: DAY, fetchImpl: f });
150
+ assert.equal(out.rowsRead, 0);
151
+ assert.equal(out.reported, 0);
152
+ assert.equal(f.calls.length, 0);
153
+ });