@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,1628 @@
1
+ /**
2
+ * lib/org/client.mjs — maestro-side Cohort org client over the /v1 HTTP binding.
3
+ *
4
+ * The agent half of the Cohort org protocol (SPEC-org-server §2.4, phase-1 HTTP
5
+ * binding). Grows out of lib/org/cohort-client.mjs — it keeps the original
6
+ * fail-open contract (`isEnabled/publishEntry/heartbeat/fetchDirectory` are
7
+ * re-exported with their old signatures so existing callers don't break) but
8
+ * routes everything through the frozen wire contract in lib/org/protocol.mjs.
9
+ *
10
+ * Wire shape (must match the server byte-for-byte — see protocol.mjs):
11
+ * - RPC: POST {base}/v1/{method} body=params JSON, res frame {ok,result|error}
12
+ * headers: authorization: Bearer, x-org-protocol: 1, content-type,
13
+ * and x-idempotency-key for side-effecting methods.
14
+ * - Reads: GET {base}/v1/{snapshot|directory|events|hierarchy|board.ready},
15
+ * Bearer auth, bare payload JSON on 200, error frame + status on fail.
16
+ * - Pairing (pre-auth): POST /v1/pairing.request, POST /v1/pairing.approve.
17
+ *
18
+ * Fail-open by design: a network/parse error or a disabled integration yields a
19
+ * benign value (an error res frame, [] for directory, etc.) — never a throw —
20
+ * so the org binding can never take down the agent's main path. `fetchImpl` is
21
+ * injectable everywhere for deterministic tests.
22
+ *
23
+ * Node builtins only (uses global fetch on Node 20). ESM.
24
+ *
25
+ * @module lib/org/client
26
+ */
27
+
28
+ "use strict";
29
+
30
+ import { existsSync, readFileSync } from "node:fs";
31
+ import { join } from "node:path";
32
+ import { createRequire } from "node:module";
33
+ import {
34
+ PROTOCOL_VERSION,
35
+ METHODS,
36
+ methodDef,
37
+ READS,
38
+ okFrame,
39
+ errFrame,
40
+ } from "./protocol.mjs";
41
+ import { applyBrandEnvCompat } from "../env-compat.mjs";
42
+
43
+ // SDK self-bridge: agent instances import this module directly from their own
44
+ // bins — they never pass through maestro's process entrypoints — so the client
45
+ // itself must alias NEOLITH_* ⇄ COHORT_* before the env-first token/org
46
+ // resolution below. Idempotent and never clobbers an explicitly-set name.
47
+ applyBrandEnvCompat();
48
+
49
+ const DEFAULT_TIMEOUT_MS = 8000;
50
+
51
+ // ---------------------------------------------------------------------------
52
+ // Config
53
+ // ---------------------------------------------------------------------------
54
+
55
+ /**
56
+ * Load the agent's org config from disk — `config/org.yaml` (the canonical file
57
+ * `lib/setup/sections/org.mjs` writes and the wizard captures). Returns the
58
+ * parsed object ({ org: { cohort: {...} } }) or {} when absent/unparseable.
59
+ * THE single disk loader for org config, so the daemon and any caller resolve
60
+ * org enrollment from the same place (fixes the daemon reading collective.yaml
61
+ * by mistake). Fail-open: never throws.
62
+ * @param {string} agentRoot
63
+ * @returns {object}
64
+ */
65
+ export function loadOrgConfig(agentRoot) {
66
+ try {
67
+ const p = join(agentRoot, "config", "org.yaml");
68
+ if (!existsSync(p)) return {};
69
+ const require = createRequire(import.meta.url);
70
+ const yaml = require("js-yaml");
71
+ return yaml.load(readFileSync(p, "utf8")) || {};
72
+ } catch {
73
+ return {};
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Is the cohort integration configured + enabled? Back-compat shape: requires
79
+ * `org.cohort.enabled` plus a reachable endpoint (apiUrl or base).
80
+ * @param {object} cfg
81
+ * @returns {boolean}
82
+ */
83
+ export function isEnabled(cfg) {
84
+ const n = cfg && cfg.org && cfg.org.cohort;
85
+ if (!n || !n.enabled) return false;
86
+ return !!(n.base || (n.apiUrl && n.orgId));
87
+ }
88
+
89
+ /**
90
+ * Distil the org client config from an agent config object. Resolves:
91
+ * - base: the server origin for the /v1 binding (explicit `org.cohort.base`,
92
+ * else derived from `apiUrl` [+ `orgId` path] for back-compat).
93
+ * - orgId: the org slug — config `org.cohort.orgId` else COHORT_ORG_ID env
94
+ * (hq's documented var). Used by the legacy directory-path derivation
95
+ * and, when known, sent as the `x-org-id` pin header (defence-in-depth).
96
+ * - token: bearer credential — the RAW OrgApiKey. Resolution order:
97
+ * config `token` → COHORT_API_TOKEN → COHORT_TOKEN → COHORT_API_KEY.
98
+ * hq's auth doc names the agent var COHORT_API_KEY; the older
99
+ * COHORT_API_TOKEN/COHORT_TOKEN are still accepted (Bearer) so an
100
+ * existing agent env keeps working.
101
+ * - trustedSignersPem: PINNED snapshot signer PEMs for the detective layer.
102
+ * Never throws; returns a fully-populated object with empty strings for missing
103
+ * fields so callers can null-check uniformly.
104
+ * @param {object} cfg
105
+ * @returns {{enabled:boolean, base:string, orgId:string, token:string, trustedSignersPem:string[]}}
106
+ */
107
+ export function configFromAgent(cfg) {
108
+ const n = (cfg && cfg.org && cfg.org.cohort) || {};
109
+ const apiUrl = n.apiUrl ? String(n.apiUrl).replace(/\/+$/, "") : "";
110
+ const orgId = (n.orgId ? String(n.orgId) : "") || process.env.COHORT_ORG_ID || "";
111
+ let base = n.base ? String(n.base).replace(/\/+$/, "") : "";
112
+ if (!base && apiUrl) {
113
+ // Legacy back-compat: apiUrl[/orgId] is the server origin for the binding.
114
+ base = orgId ? `${apiUrl}/${encodeURIComponent(orgId)}` : apiUrl;
115
+ }
116
+ const token =
117
+ (n.token && String(n.token)) ||
118
+ process.env.COHORT_API_TOKEN ||
119
+ process.env.COHORT_TOKEN ||
120
+ process.env.COHORT_API_KEY ||
121
+ "";
122
+ const signers = Array.isArray(n.trustedSignersPem)
123
+ ? n.trustedSignersPem.filter((s) => typeof s === "string" && s)
124
+ : [];
125
+ return { enabled: !!(n.enabled && base), base, orgId, token, trustedSignersPem: signers };
126
+ }
127
+
128
+ // ---------------------------------------------------------------------------
129
+ // Low-level transport
130
+ // ---------------------------------------------------------------------------
131
+
132
+ function pickFetch(fetchImpl) {
133
+ if (typeof fetchImpl === "function") return fetchImpl;
134
+ if (typeof fetch === "function") return fetch;
135
+ return null;
136
+ }
137
+
138
+ function baseHeaders(token, orgId) {
139
+ const h = {
140
+ "content-type": "application/json",
141
+ "x-org-protocol": String(PROTOCOL_VERSION),
142
+ };
143
+ if (token) h.authorization = `Bearer ${token}`;
144
+ // Defence-in-depth: pin the org when we know it. hq treats a mismatch as 401
145
+ // and a match as harmless, so this only ever fences a misrouted key.
146
+ if (orgId) h["x-org-id"] = String(orgId);
147
+ return h;
148
+ }
149
+
150
+ /**
151
+ * Perform a single HTTP request with timeout + JSON parse. Returns the raw
152
+ * outcome ({ok,status,body}); never throws.
153
+ */
154
+ async function httpRequest(fetchImpl, url, init, timeoutMs = DEFAULT_TIMEOUT_MS) {
155
+ const f = pickFetch(fetchImpl);
156
+ if (!f) return { ok: false, status: 0, body: null };
157
+ const ctrl = typeof AbortController === "function" ? new AbortController() : null;
158
+ const timer = ctrl ? setTimeout(() => ctrl.abort(), timeoutMs) : null;
159
+ try {
160
+ const res = await f(url, { ...init, signal: ctrl ? ctrl.signal : undefined });
161
+ let body = null;
162
+ try { body = await res.json(); } catch { body = null; }
163
+ return { ok: !!res.ok, status: typeof res.status === "number" ? res.status : 0, body };
164
+ } catch {
165
+ return { ok: false, status: 0, body: null };
166
+ } finally {
167
+ if (timer) clearTimeout(timer);
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Join an org server ORIGIN + a /v1 RPC/read path.
173
+ *
174
+ * hq serves the whole agent binding at `/api/v1/<path>` (the catch-all route
175
+ * `src/app/api/v1/[...method]/route.ts` — there is NO bare `/v1` mount), and the
176
+ * org-data DTO reads (fetchSelfProfile/orgDataGet) already build `/api/v1/org/…`.
177
+ * So the ONE canonical convention is `${origin}/api/v1/${path}` — this keeps the
178
+ * RPC/read binding and the org-data reads pointed at the same base.
179
+ *
180
+ * Back-compat: a `base` that ALREADY carries the mount path baked in (ends in
181
+ * `/api/v1` or a legacy bare `/v1`) is respected as-is rather than double-
182
+ * prefixed, so a service configured the old way keeps working.
183
+ */
184
+ function v1Url(base, path) {
185
+ const b = String(base).replace(/\/+$/, "");
186
+ if (/\/(?:api\/)?v1$/.test(b)) return `${b}/${path}`;
187
+ return `${b}/api/v1/${path}`;
188
+ }
189
+
190
+ /** Normalise a server body into a res frame ({ok,result} | {ok,error}). */
191
+ function asResFrame(r) {
192
+ const b = r.body;
193
+ if (b && typeof b === "object" && typeof b.ok === "boolean") return b; // already a frame
194
+ if (r.ok) return okFrame(b);
195
+ return errFrame(httpStatusToCode(r.status), `http ${r.status}`);
196
+ }
197
+
198
+ function httpStatusToCode(status) {
199
+ switch (status) {
200
+ case 400: return "BAD_REQUEST";
201
+ case 401: return "UNAUTHORIZED";
202
+ case 403: return "FORBIDDEN_SCOPE";
203
+ case 404: return "NOT_FOUND";
204
+ case 409: return "CONFLICT";
205
+ case 423: return "GOVERNANCE_NOT_READY";
206
+ case 429: return "RATE_LIMITED";
207
+ default: return "INTERNAL";
208
+ }
209
+ }
210
+
211
+ // ---------------------------------------------------------------------------
212
+ // Pairing handshake (pre-auth)
213
+ // ---------------------------------------------------------------------------
214
+
215
+ /**
216
+ * Pre-auth pairing handshake. POST /v1/pairing.request — no bearer token (the
217
+ * agent has none yet). The operator picks a `code` and shares it with an org
218
+ * admin, who resolves the handshake via pairing.approve; hq keys the
219
+ * PairingRequest row on `(orgId, code)` (server contract: `code` is required,
220
+ * `agentId` optional). We ALSO send the device `pubkey` + `displayName` so a
221
+ * server that pins the device key on approval has it (hq currently ignores the
222
+ * extra fields — zod strips unknown keys — so this stays forward-compatible).
223
+ * Returns the res frame; on transport failure returns a fail-open error frame.
224
+ * @param {object} o - { base, agentId, pubkey, code?, displayName?, fetchImpl? }
225
+ * @returns {Promise<object>} res frame {ok:true,result:{status,code}}|{ok:false,error}
226
+ */
227
+ export async function pairRequest(o = {}) {
228
+ const { base, agentId, displayName, pubkey, code } = o;
229
+ if (!base) return errFrame("BAD_REQUEST", "missing base");
230
+ if (!agentId || !pubkey) return errFrame("BAD_REQUEST", "missing agentId or pubkey");
231
+ const body = { agentId, displayName: displayName || agentId, pubkey };
232
+ // `code` is the operator-chosen handshake token hq keys the row on. Include it
233
+ // whenever supplied so a real end-to-end pairing.request/approve flow works.
234
+ if (code != null && String(code).trim()) body.code = String(code).trim();
235
+ const r = await httpRequest(o.fetchImpl, v1Url(base, "pairing.request"), {
236
+ method: "POST",
237
+ headers: { "content-type": "application/json", "x-org-protocol": String(PROTOCOL_VERSION) },
238
+ body: JSON.stringify(body),
239
+ });
240
+ return asResFrame(r);
241
+ }
242
+
243
+ // ---------------------------------------------------------------------------
244
+ // RPC (POST /v1/{method})
245
+ // ---------------------------------------------------------------------------
246
+
247
+ /**
248
+ * Invoke an RPC method over the /v1 binding. Builds the correct headers
249
+ * (Bearer, x-org-protocol, and x-idempotency-key for side-effecting methods),
250
+ * posts the params object, and returns the parsed res frame. Fail-open: a
251
+ * transport error yields an INTERNAL error frame rather than throwing.
252
+ *
253
+ * @param {string} method full dotted method name (e.g. "board.claim")
254
+ * @param {object} params the params object (request body)
255
+ * @param {object} o - { base, token, orgId?, idempotencyKey?, fetchImpl? }
256
+ * @returns {Promise<object>} res frame
257
+ */
258
+ export async function call(method, params, o = {}) {
259
+ const { base, token, orgId } = o;
260
+ if (!base) return errFrame("BAD_REQUEST", "missing base");
261
+ const def = methodDef(method);
262
+ if (!def) return errFrame("NOT_FOUND", `unknown method ${method}`);
263
+
264
+ const headers = baseHeaders(def.auth === false ? "" : token, def.auth === false ? "" : orgId);
265
+ if (def.sideEffecting) {
266
+ const key = o.idempotencyKey || (def.idempotent ? defaultIdempotencyKey(method, params) : "");
267
+ if (key) headers["x-idempotency-key"] = String(key);
268
+ }
269
+ const r = await httpRequest(o.fetchImpl, v1Url(base, method), {
270
+ method: "POST",
271
+ headers,
272
+ body: JSON.stringify(params || {}),
273
+ });
274
+ return asResFrame(r);
275
+ }
276
+
277
+ /** Derive a stable idempotency key for an idempotent method (best-effort). */
278
+ function defaultIdempotencyKey(method, params) {
279
+ const id = params && (params.id || params.entityId || params.agentId);
280
+ return id ? `${method}:${id}` : "";
281
+ }
282
+
283
+ // ---------------------------------------------------------------------------
284
+ // Reads (GET)
285
+ // ---------------------------------------------------------------------------
286
+
287
+ /**
288
+ * GET a read endpoint. Reads return the BARE payload JSON on 200 (not wrapped in
289
+ * a frame); on failure the server returns an error frame. We normalise: success
290
+ * → { ok:true, payload }, failure → { ok:false, error, status }. Fail-open.
291
+ *
292
+ * @param {string} path the read path under /v1 (e.g. "snapshot", "events?cursor=0")
293
+ * @param {object} o - { base, token, orgId?, etag?, fetchImpl? }
294
+ * @returns {Promise<{ok:boolean, payload?:any, error?:object, status:number, etag?:string, notModified?:boolean}>}
295
+ */
296
+ export async function read(path, o = {}) {
297
+ const { base, token, orgId } = o;
298
+ if (!base) return { ok: false, error: errFrame("BAD_REQUEST", "missing base").error, status: 0 };
299
+ const headers = baseHeaders(token, orgId);
300
+ delete headers["content-type"]; // GET has no body
301
+ if (o.etag) headers["if-none-match"] = o.etag;
302
+
303
+ const f = pickFetch(o.fetchImpl);
304
+ if (!f) return { ok: false, error: errFrame("INTERNAL", "no fetch").error, status: 0 };
305
+ const ctrl = typeof AbortController === "function" ? new AbortController() : null;
306
+ const readTimeoutMs = Number.isFinite(o.timeoutMs) && o.timeoutMs > 0 ? o.timeoutMs : DEFAULT_TIMEOUT_MS;
307
+ const timer = ctrl ? setTimeout(() => ctrl.abort(), readTimeoutMs) : null;
308
+ try {
309
+ const res = await f(v1Url(base, path), { method: "GET", headers, signal: ctrl ? ctrl.signal : undefined });
310
+ const status = typeof res.status === "number" ? res.status : 0;
311
+ const respEtag = readHeader(res, "etag");
312
+ if (status === 304) return { ok: true, notModified: true, status, etag: o.etag };
313
+ let body = null;
314
+ try { body = await res.json(); } catch { body = null; }
315
+ if (res.ok) return { ok: true, payload: body, status, etag: respEtag };
316
+ const error = body && body.error ? body.error : errFrame(httpStatusToCode(status), `http ${status}`).error;
317
+ return { ok: false, error, status };
318
+ } catch {
319
+ return { ok: false, error: errFrame("INTERNAL", "transport error").error, status: 0 };
320
+ } finally {
321
+ if (timer) clearTimeout(timer);
322
+ }
323
+ }
324
+
325
+ // ---------------------------------------------------------------------------
326
+ // Org-data API (GET /api/v1/org/...) — the directory/profile read surface
327
+ // ---------------------------------------------------------------------------
328
+ //
329
+ // DISTINCT from the /v1 RPC binding above: the org-data API is the human-facing
330
+ // directory surface (`/api/v1/org/whoami`, `/api/v1/org/members/<slug>`) that
331
+ // returns a MemberProfileDTO. `maestro setup`/`sync` PULL from here to
332
+ // auto-populate config/agent.json so an admin describes each agent ONCE in
333
+ // Cohort. Auth is the org API KEY (Bearer) + the org slug; the agent id (slug)
334
+ // selects a specific member, its absence selects whoami (sole-agent / email hint).
335
+ //
336
+ // Fail-open like everything else: a missing key / unreachable server / non-200
337
+ // yields an error frame, never a throw, so the wizard simply falls back to the
338
+ // manual prompts.
339
+
340
+ /**
341
+ * Pull THIS agent's record from the Cohort org-data API as a MemberProfileDTO.
342
+ * Selects `GET /api/v1/org/members/<agentId>` when an agentId (slug) is supplied,
343
+ * else `GET /api/v1/org/whoami?orgId=<orgId>[&slug=<memberHint>][&email=<hint>]`
344
+ * (sole-agent / member-hinted resolution — orgId is the tenant PIN, not a member
345
+ * hint; `slug`/`email` are member hints). Returns the standard res frame
346
+ * ({ok:true,result:DTO}|{ok:false,error}); fail-open (never throws).
347
+ *
348
+ * @param {object} o
349
+ * @param {string} o.base Cohort server origin (e.g. https://cohort.internal)
350
+ * @param {string} o.apiKey org API key (Bearer)
351
+ * @param {string} o.orgId org slug — sent as the `?orgId=`/`x-org-id` tenant pin
352
+ * @param {string} [o.agentId] member slug — when set, hits /org/members/<slug>
353
+ * @param {string} [o.memberSlug] member-slug hint for whoami resolution (?slug=)
354
+ * @param {string} [o.emailHint] email hint for whoami resolution
355
+ * @param {Function} [o.fetchImpl] injectable fetch for tests
356
+ * @returns {Promise<object>} res frame
357
+ */
358
+ export async function fetchSelfProfile(o = {}) {
359
+ const base = o.base ? String(o.base).replace(/\/+$/, "") : "";
360
+ const apiKey = o.apiKey ? String(o.apiKey) : "";
361
+ const orgId = o.orgId ? String(o.orgId) : "";
362
+ if (!base) return errFrame("BAD_REQUEST", "missing base");
363
+ if (!apiKey) return errFrame("UNAUTHORIZED", "missing api key");
364
+ if (!orgId) return errFrame("BAD_REQUEST", "missing orgId");
365
+
366
+ // The org is resolved server-side from the API KEY, not from a query param.
367
+ // Pin it defence-in-depth via `?orgId=` + the `x-org-id` header (hq treats a
368
+ // mismatch as 401, a match as harmless). whoami's `slug`/`email` params are
369
+ // reserved for genuine MEMBER hints — never the org slug — so we send a member
370
+ // slug hint (o.memberSlug) only when one is explicitly supplied.
371
+ const memberHint = o.memberSlug ? String(o.memberSlug) : "";
372
+ const path = o.agentId
373
+ ? `org/members/${encodeURIComponent(String(o.agentId))}?orgId=${encodeURIComponent(orgId)}`
374
+ : `org/whoami?orgId=${encodeURIComponent(orgId)}${memberHint ? `&slug=${encodeURIComponent(memberHint)}` : ""}${o.emailHint ? `&email=${encodeURIComponent(String(o.emailHint))}` : ""}`;
375
+ const url = `${base}/api/v1/${path}`;
376
+ const headers = { authorization: `Bearer ${apiKey}`, accept: "application/json", "x-org-id": orgId };
377
+
378
+ const r = await httpRequest(o.fetchImpl, url, { method: "GET", headers });
379
+ if (!r.ok) {
380
+ const b = r.body;
381
+ if (b && typeof b === "object" && b.error) return errFrame(b.error.code || httpStatusToCode(r.status), b.error.message || `http ${r.status}`);
382
+ return errFrame(httpStatusToCode(r.status), `http ${r.status}`);
383
+ }
384
+ // The org-data API returns the BARE MemberProfileDTO on 200 (it may also wrap
385
+ // it under {member}/{profile}/{result}); normalise to a res frame.
386
+ const dto = unwrapProfile(r.body);
387
+ if (!dto) return errFrame("NOT_FOUND", "no profile in response");
388
+ return okFrame(dto);
389
+ }
390
+
391
+ /** Normalise the org-data API payload to the bare MemberProfileDTO (or null). */
392
+ function unwrapProfile(body) {
393
+ if (!body || typeof body !== "object") return null;
394
+ if (body.slug || body.displayName || body.agentEmail) return body;
395
+ for (const key of ["member", "profile", "result", "data"]) {
396
+ const v = body[key];
397
+ if (v && typeof v === "object" && (v.slug || v.displayName || v.agentEmail)) return v;
398
+ }
399
+ return null;
400
+ }
401
+
402
+ /** Read a response header across fetch Response and plain-object fakes. */
403
+ function readHeader(res, name) {
404
+ try {
405
+ if (res && res.headers && typeof res.headers.get === "function") return res.headers.get(name) || undefined;
406
+ if (res && res.headers && typeof res.headers === "object") return res.headers[name] || res.headers[name.toLowerCase()];
407
+ } catch { /* ignore */ }
408
+ return undefined;
409
+ }
410
+
411
+ // ---------------------------------------------------------------------------
412
+ // Org-data API: structural reads (chart / relationships / strategy)
413
+ // ---------------------------------------------------------------------------
414
+ //
415
+ // The SAME human-facing directory surface as fetchSelfProfile (GET
416
+ // /api/v1/org/*, Bearer org API key + `?slug=<org>`), extended to the org's
417
+ // STRUCTURE: the chart (nodes + reporting/governance edges), a single member's
418
+ // relationships, and the org strategy doc. A provisioned agent consumes these as
419
+ // its source of truth so it knows who owns what, who it escalates to, and the
420
+ // org's north star — instead of inferring everything from its local archetype.
421
+ //
422
+ // hq returns these as `{ ok:true, ...DTO }` (chart/relationships) or
423
+ // `{ ok:true, strategy:DTO }`. We normalise to the same res frame fetchSelfProfile
424
+ // uses ({ok:true,result:DTO}|{ok:false,error}) so callers null-check uniformly.
425
+ // Fail-open like everything else: a missing key / unreachable server / non-200
426
+ // yields an error frame, never a throw.
427
+
428
+ /**
429
+ * Shared org-data GET. Builds `GET {base}/api/v1/org/{path}?slug=<orgId>[&…]`
430
+ * with Bearer auth, runs it fail-open, and projects the 200 body through `pick`
431
+ * (body → meaningful DTO) into a res frame. Mirrors fetchSelfProfile's contract.
432
+ * @param {object} o - { base, apiKey, orgId, fetchImpl? }
433
+ * @param {string} path the sub-path under /api/v1/org/ (e.g. "chart")
434
+ * @param {object} extraParams extra query params (merged after slug)
435
+ * @param {(body:any)=>any} pick projects the 200 body to the DTO (null ⇒ NOT_FOUND)
436
+ * @returns {Promise<object>} res frame
437
+ */
438
+ async function orgDataGet(o, path, extraParams, pick) {
439
+ const base = o && o.base ? String(o.base).replace(/\/+$/, "") : "";
440
+ const apiKey = o && o.apiKey ? String(o.apiKey) : "";
441
+ const orgId = o && o.orgId ? String(o.orgId) : "";
442
+ if (!base) return errFrame("BAD_REQUEST", "missing base");
443
+ if (!apiKey) return errFrame("UNAUTHORIZED", "missing api key");
444
+ if (!orgId) return errFrame("BAD_REQUEST", "missing orgId");
445
+
446
+ // Org is resolved from the API key server-side; `orgId` is the defence-in-depth
447
+ // pin (`?orgId=` + `x-org-id` header), NOT a `slug` (which hq reads as a member
448
+ // hint). `member=<slug>` and other genuine params ride in via extraParams.
449
+ const qs = new URLSearchParams({ orgId, ...(extraParams || {}) }).toString();
450
+ const url = `${base}/api/v1/org/${path}?${qs}`;
451
+ const headers = { authorization: `Bearer ${apiKey}`, accept: "application/json", "x-org-id": orgId };
452
+
453
+ const r = await httpRequest(o.fetchImpl, url, { method: "GET", headers });
454
+ if (!r.ok) {
455
+ const b = r.body;
456
+ if (b && typeof b === "object" && b.error) return errFrame(b.error.code || httpStatusToCode(r.status), b.error.message || `http ${r.status}`);
457
+ return errFrame(httpStatusToCode(r.status), `http ${r.status}`);
458
+ }
459
+ const result = pick(r.body);
460
+ if (result == null) return errFrame("NOT_FOUND", `no ${path} payload`);
461
+ return okFrame(result);
462
+ }
463
+
464
+ /** Project the chart body ({ok,nodes,edges} | {chart:{…}}) → { nodes, edges }. */
465
+ function pickChart(body) {
466
+ if (!body || typeof body !== "object") return null;
467
+ const src = Array.isArray(body.nodes) || Array.isArray(body.edges) ? body : (body.chart || body.result);
468
+ if (!src || typeof src !== "object") return null;
469
+ return { nodes: Array.isArray(src.nodes) ? src.nodes : [], edges: Array.isArray(src.edges) ? src.edges : [] };
470
+ }
471
+
472
+ /** Project the relationships body → { member, reportingLine, reports, governance }. */
473
+ function pickRelationships(body) {
474
+ if (!body || typeof body !== "object") return null;
475
+ const has = body.member || body.reportingLine || Array.isArray(body.reports) || Array.isArray(body.governance);
476
+ const src = has ? body : (body.relationships || body.result);
477
+ if (!src || typeof src !== "object") return null;
478
+ return {
479
+ member: src.member || null,
480
+ reportingLine: src.reportingLine || null,
481
+ reports: Array.isArray(src.reports) ? src.reports : [],
482
+ governance: Array.isArray(src.governance) ? src.governance : [],
483
+ };
484
+ }
485
+
486
+ /** Project the strategy body ({ok,strategy:{…}} | bare StrategyDTO) → StrategyDTO. */
487
+ function pickStrategy(body) {
488
+ if (!body || typeof body !== "object") return null;
489
+ if (body.strategy && typeof body.strategy === "object") return body.strategy;
490
+ if (body.northStar !== undefined || Array.isArray(body.phases) || Array.isArray(body.streams)) return body;
491
+ if (body.result && typeof body.result === "object") return body.result;
492
+ return null;
493
+ }
494
+
495
+ /**
496
+ * Fetch the org chart (GET /api/v1/org/chart): active-member nodes + reporting/
497
+ * governance edges (keyed by member slug). Org-scoped via the API key. Returns a
498
+ * res frame whose result is `{ nodes, edges }`; fail-open (never throws).
499
+ * @param {object} o - { base, apiKey, orgId, fetchImpl? }
500
+ * @returns {Promise<object>} res frame {ok:true,result:{nodes,edges}}|{ok:false,error}
501
+ */
502
+ export async function fetchOrgChart(o = {}) {
503
+ return orgDataGet(o, "chart", undefined, pickChart);
504
+ }
505
+
506
+ /**
507
+ * Fetch ONE member's relationships (GET /api/v1/org/relationships?member=<slug>):
508
+ * reporting line, reports, and governance edges. The member slug is `o.member`
509
+ * (falls back to `o.agentId`). Returns a res frame whose result is
510
+ * `{ member, reportingLine, reports, governance }`; fail-open.
511
+ * @param {object} o - { base, apiKey, orgId, member|agentId, fetchImpl? }
512
+ * @returns {Promise<object>} res frame
513
+ */
514
+ export async function fetchRelationships(o = {}) {
515
+ const member = (o && (o.member || o.agentId)) ? String(o.member || o.agentId) : "";
516
+ if (!member) return errFrame("BAD_REQUEST", "missing member slug");
517
+ return orgDataGet(o, "relationships", { member }, pickRelationships);
518
+ }
519
+
520
+ /**
521
+ * Fetch the org strategy doc (GET /api/v1/org/strategy): north star, levers,
522
+ * phases and streams with their RACI assignments. Org-scoped via the API key.
523
+ * Returns a res frame whose result is the StrategyDTO; fail-open. A tenant with
524
+ * no strategy doc yet yields a NOT_FOUND error frame (the agent runs without it).
525
+ * @param {object} o - { base, apiKey, orgId, fetchImpl? }
526
+ * @returns {Promise<object>} res frame
527
+ */
528
+ export async function fetchStrategy(o = {}) {
529
+ return orgDataGet(o, "strategy", undefined, pickStrategy);
530
+ }
531
+
532
+ /**
533
+ * Fetch the org hierarchy over the /v1 read surface (GET /v1/hierarchy). Unlike
534
+ * the org-data DTO reads above this is the protocol read binding (Bearer token,
535
+ * BARE payload on 200 per the read contract). Returns the bare
536
+ * `{ members, reporting }` payload (normalised), or null on any failure
537
+ * (fail-open). The agent uses it as the authoritative reporting graph.
538
+ * @param {object} o - { base, token, orgId?, fetchImpl? }
539
+ * @returns {Promise<{members:object[], reporting:object[]}|null>}
540
+ */
541
+ export async function fetchHierarchy(o = {}) {
542
+ const r = await read("hierarchy", o);
543
+ if (!r.ok || !r.payload) return null;
544
+ const p = r.payload;
545
+ const members = Array.isArray(p) ? p : (Array.isArray(p.members) ? p.members : []);
546
+ const reporting = Array.isArray(p.reporting) ? p.reporting : [];
547
+ return { members, reporting };
548
+ }
549
+
550
+ // ---------------------------------------------------------------------------
551
+ // Typed RPC helpers
552
+ // ---------------------------------------------------------------------------
553
+
554
+ /**
555
+ * register self with the org (registry.register). Idempotent.
556
+ * @param {object} entry directory entry (id, displayName, role, focus, pubkey…)
557
+ * @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
558
+ */
559
+ export function register(entry, o = {}) {
560
+ return call("registry.register", entry || {}, o);
561
+ }
562
+
563
+ /** Heartbeat the registry liveness (registry.heartbeat). */
564
+ export function heartbeat(params, o = {}) {
565
+ return call("registry.heartbeat", params || {}, o);
566
+ }
567
+
568
+ /**
569
+ * Presence beat (presence.beat) — the result carries directives[] (subset of
570
+ * DIRECTIVES) incl. the "halt" kill switch when this agent was deactivated.
571
+ */
572
+ export function presenceBeat(params, o = {}) {
573
+ return call("presence.beat", params || {}, o);
574
+ }
575
+
576
+ /** Create a board work-item (board.create). Idempotent. */
577
+ export function createItem(item, o = {}) {
578
+ return call("board.create", item || {}, o);
579
+ }
580
+
581
+ /**
582
+ * Claim a board item (board.claim). The server resolves the race atomically; the
583
+ * loser gets a CONFLICT error frame. Caller MUST pass a stable idempotencyKey
584
+ * for safe retries.
585
+ */
586
+ export function claimItem(params, o = {}) {
587
+ return call("board.claim", params || {}, o);
588
+ }
589
+
590
+ /** Complete a board item (board.complete). */
591
+ export function completeItem(params, o = {}) {
592
+ return call("board.complete", params || {}, o);
593
+ }
594
+
595
+ /**
596
+ * List the ready feed (GET /v1/board.ready). Returns the items array, or [] on
597
+ * any failure (fail-open).
598
+ * @param {object} o - { base, token, fetchImpl? }
599
+ * @returns {Promise<object[]>}
600
+ */
601
+ export async function listReady(o = {}) {
602
+ const r = await read("board.ready", o);
603
+ if (!r.ok || !r.payload) return [];
604
+ if (Array.isArray(r.payload)) return r.payload;
605
+ if (Array.isArray(r.payload.items)) return r.payload.items;
606
+ return [];
607
+ }
608
+
609
+ /**
610
+ * Fetch the event slice (GET /v1/events?cursor=N&limit=500). Returns the events
611
+ * array (or [] on failure). The caller pipes these to verify.verifyEvents.
612
+ * @param {number} cursor seq to read from (exclusive)
613
+ * @param {object} o - { base, token, limit?, fetchImpl? }
614
+ * @returns {Promise<object[]>}
615
+ */
616
+ export async function fetchEvents(cursor = 0, o = {}) {
617
+ const limit = Number.isFinite(o.limit) ? o.limit : 500;
618
+ const path = `events?cursor=${encodeURIComponent(cursor)}&limit=${encodeURIComponent(limit)}`;
619
+ const r = await read(path, o);
620
+ if (!r.ok || !r.payload) return [];
621
+ if (Array.isArray(r.payload)) return r.payload;
622
+ if (Array.isArray(r.payload.events)) return r.payload.events;
623
+ return [];
624
+ }
625
+
626
+ /**
627
+ * Fetch the signed snapshot (GET /v1/snapshot). Returns the raw snapshot payload
628
+ * (with its `sig` envelope) for the detective layer to verify, or null on
629
+ * failure. Verification is the CALLER's job (verify.verifySnapshot with PINNED
630
+ * signers) — this function does not trust the server.
631
+ * @param {object} o - { base, token, fetchImpl? }
632
+ * @returns {Promise<object|null>}
633
+ */
634
+ export async function fetchSnapshot(o = {}) {
635
+ const r = await read("snapshot", o);
636
+ if (!r.ok || !r.payload) return null;
637
+ return r.payload;
638
+ }
639
+
640
+ // ---------------------------------------------------------------------------
641
+ // Phase-2 typed RPC helpers — leases, handoffs, board DAG, governance
642
+ // ---------------------------------------------------------------------------
643
+ //
644
+ // Every helper below is a thin wrapper over call() over the /v1 binding: Bearer
645
+ // auth + x-org-protocol on every request, x-idempotency-key on the side-effecting
646
+ // ones (lease/handoff/board.* are all sideEffecting in the registry, so the
647
+ // caller SHOULD pass a stable `o.idempotencyKey` for safe retries; lease.claim /
648
+ // board.* are non-idempotent in the registry so no key is auto-synthesised — the
649
+ // server resolves the race atomically and the loser gets LEASE_HELD/CONFLICT).
650
+ // All fail open: a disabled/unreachable server yields an error frame, never a
651
+ // throw, so the lease/handoff/board paths can never take down the agent.
652
+
653
+ // --- leases (one primitive, scope ∈ LEASE_SCOPES) --------------------------
654
+
655
+ /**
656
+ * Claim a generic lease (lease.claim). Exactly one holder per (scope,resourceId)
657
+ * until expiry/release; the loser gets a CONFLICT/LEASE_HELD error frame. The
658
+ * winning frame carries `{ token, expiresAt, ttlMs }` (token = the bearer for
659
+ * heartbeat/release; redacted everywhere else).
660
+ * @param {object} params - { scope, resourceId, holder?, ttlMs? }
661
+ * @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
662
+ */
663
+ export function leaseClaim(params, o = {}) {
664
+ return call("lease.claim", params || {}, o);
665
+ }
666
+
667
+ /**
668
+ * Extend a held lease (lease.heartbeat). Slides expiry by the original TTL.
669
+ * @param {object} params - { scope, resourceId, leaseToken }
670
+ * @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
671
+ */
672
+ export function leaseHeartbeat(params, o = {}) {
673
+ return call("lease.heartbeat", params || {}, o);
674
+ }
675
+
676
+ /**
677
+ * Release a held lease (lease.release). Frees (scope,resourceId) immediately.
678
+ * @param {object} params - { scope, resourceId, leaseToken }
679
+ * @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
680
+ */
681
+ export function leaseRelease(params, o = {}) {
682
+ return call("lease.release", params || {}, o);
683
+ }
684
+
685
+ // --- handoffs (signed delegation lifecycle — ALL governance-gated §0.4) -----
686
+
687
+ /**
688
+ * Offer a handoff (handoff.offer). The delegator posts the signed envelope; the
689
+ * server records a handoff row in state `offered`. governanceGated — returns a
690
+ * GOVERNANCE_NOT_READY error frame until the server is governance_ready.
691
+ * @param {object} params - { handoffId, itemId?, to, envelope, envelopeSig, deadlineAt? }
692
+ * @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
693
+ */
694
+ export function handoffOffer(params, o = {}) {
695
+ return call("handoff.offer", params || {}, o);
696
+ }
697
+
698
+ /**
699
+ * Accept an offered handoff (handoff.accept). The delegatee claims the work.
700
+ * governanceGated.
701
+ * @param {object} params - { handoffId }
702
+ * @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
703
+ */
704
+ export function handoffAccept(params, o = {}) {
705
+ return call("handoff.accept", params || {}, o);
706
+ }
707
+
708
+ /**
709
+ * Decline an offered handoff (handoff.decline). governanceGated.
710
+ * @param {object} params - { handoffId, reason? }
711
+ * @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
712
+ */
713
+ export function handoffDecline(params, o = {}) {
714
+ return call("handoff.decline", params || {}, o);
715
+ }
716
+
717
+ /**
718
+ * Cancel a handoff the caller offered (handoff.cancel). governanceGated.
719
+ * @param {object} params - { handoffId, reason? }
720
+ * @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
721
+ */
722
+ export function handoffCancel(params, o = {}) {
723
+ return call("handoff.cancel", params || {}, o);
724
+ }
725
+
726
+ // --- board DAG (decompose/assign governance-gated; link not) ----------------
727
+
728
+ /**
729
+ * Decompose a board item into children + blocking links (board.decompose).
730
+ * governanceGated. Server creates the child items and the typed edges in one
731
+ * transaction (DFS cycle check on insert).
732
+ * @param {object} params - { itemId, children:[{title,…}], linkType? }
733
+ * @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
734
+ */
735
+ export function boardDecompose(params, o = {}) {
736
+ return call("board.decompose", params || {}, o);
737
+ }
738
+
739
+ /**
740
+ * Assign an owner to a board item (board.assign). governanceGated (lead-agent +
741
+ * decision_rights checked server-side).
742
+ * @param {object} params - { itemId, assignee }
743
+ * @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
744
+ */
745
+ export function boardAssign(params, o = {}) {
746
+ return call("board.assign", params || {}, o);
747
+ }
748
+
749
+ /**
750
+ * Link two board items with a typed edge (board.link). NOT governance-gated.
751
+ * @param {object} params - { fromItem, toItem, linkType? } (linkType default "blocks"; server contract)
752
+ * @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
753
+ */
754
+ export function boardLink(params, o = {}) {
755
+ return call("board.link", params || {}, o);
756
+ }
757
+
758
+ // --- governance ------------------------------------------------------------
759
+
760
+ /**
761
+ * Read the governance readiness status (GET-style RPC governance.status).
762
+ * Non-side-effecting; returns the res frame whose result carries
763
+ * `{ governanceReady, decisionRightsCount, signingLive, verifiedCards }` (server
764
+ * shape). Used by the daemon/doctor to know whether handoffs/assignment will flow.
765
+ * @param {object} o - { base, token, fetchImpl? }
766
+ */
767
+ export function governanceStatus(o = {}) {
768
+ return call("governance.status", {}, o);
769
+ }
770
+
771
+ // ---------------------------------------------------------------------------
772
+ // Phase-3 typed RPC helpers — approvals, decisions, cost, policy
773
+ // ---------------------------------------------------------------------------
774
+ //
775
+ // Same contract as the phase-1/2 helpers: a thin wrapper over call() / read()
776
+ // over the /v1 binding, Bearer + x-org-protocol on every request, and the
777
+ // idempotent side-effecting methods (approval.request / decision.propose /
778
+ // cost.report) get an x-idempotency-key (auto-derived from a stable id when the
779
+ // caller doesn't supply one). All fail open — a disabled/unreachable server
780
+ // yields an error frame, never a throw — so the coordination loop (G16: ZERO LLM
781
+ // calls in any of these paths) can never take down the agent.
782
+
783
+ // --- approvals (G5 requester != approver; sha256 payload binding) -----------
784
+
785
+ /**
786
+ * Request approval for a classified action (approval.request). Idempotent — the
787
+ * caller MUST pass a stable `o.idempotencyKey` (or `params.id`) so a retried
788
+ * request reuses the same approval row rather than spawning a duplicate ask.
789
+ *
790
+ * The params carry the sha256 `payload_hash` ("the human approved exactly this")
791
+ * — the server binds the approval to it and rejects a resolve whose executing
792
+ * payload hash differs. lib/org/approvals.requestApproval computes the hash and
793
+ * fills this in; call this directly only when you already have the hash.
794
+ *
795
+ * @param {object} params - { id?, kind, subject, payload_hash, itemId?, expiresInMs? }
796
+ * @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
797
+ */
798
+ export function approvalRequest(params, o = {}) {
799
+ return call("approval.request", params || {}, o);
800
+ }
801
+
802
+ /**
803
+ * Resolve (approve/deny) an approval (approval.resolve). Requires the
804
+ * `approval.decide` scope — approvers/humans only; an ordinary agent token is
805
+ * rejected with FORBIDDEN_SCOPE. The server ALSO enforces requester != approver
806
+ * (G5: DB CHECK + method-layer) and re-checks the bound `payload_hash`.
807
+ * NOT idempotent in the registry; pass a stable `o.idempotencyKey` for retries.
808
+ *
809
+ * @param {object} params - { id, decision:'approved'|'denied', reason?, payload_hash? }
810
+ * @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
811
+ */
812
+ export function approvalResolve(params, o = {}) {
813
+ return call("approval.resolve", params || {}, o);
814
+ }
815
+
816
+ /**
817
+ * Fetch a single approval's current state (approval.get). Non-side-effecting.
818
+ * @param {object} params - { id }
819
+ * @param {object} o - { base, token, fetchImpl? }
820
+ */
821
+ export function approvalGet(params, o = {}) {
822
+ return call("approval.get", params || {}, o);
823
+ }
824
+
825
+ /**
826
+ * List approvals visible to the actor (approval.list). Non-side-effecting.
827
+ * @param {object} params - { status?, requestedBy?, limit? }
828
+ * @param {object} o - { base, token, fetchImpl? }
829
+ */
830
+ export function approvalList(params, o = {}) {
831
+ return call("approval.list", params || {}, o);
832
+ }
833
+
834
+ /**
835
+ * Bounded long-poll for an approval outcome (GET /v1/approval.wait?id=&timeoutMs=).
836
+ * Resolves when the approval is decided OR the timeout elapses (returning the
837
+ * CURRENT state either way). `timeoutMs` is clamped to <= 55000 server-side, so
838
+ * we clamp here too and budget the client abort timeout to outlast it. The
839
+ * server polls storage on an interval and never holds a DB write lock.
840
+ *
841
+ * Returns the standard read shape ({ ok, payload, error, status }); the payload
842
+ * carries `{ id, status, resolved_by_agent?, resolved_at?, ... }`. Fail-open.
843
+ *
844
+ * @param {object} params - { id, timeoutMs? }
845
+ * @param {object} o - { base, token, fetchImpl? }
846
+ * @returns {Promise<{ok:boolean, payload?:any, error?:object, status:number}>}
847
+ */
848
+ export function approvalWait(params, o = {}) {
849
+ const id = params && params.id;
850
+ if (!id) return Promise.resolve({ ok: false, error: errFrame("BAD_REQUEST", "missing id").error, status: 0 });
851
+ const timeoutMs = clampWaitMs(params && params.timeoutMs);
852
+ const path = `approval.wait?id=${encodeURIComponent(id)}&timeoutMs=${encodeURIComponent(timeoutMs)}`;
853
+ // The long-poll deliberately outlives the default RPC timeout: give the client
854
+ // abort a margin over the server's bound so the server gets to return the
855
+ // timed-out state rather than us aborting first.
856
+ return read(path, { ...o, timeoutMs: timeoutMs + 5000 });
857
+ }
858
+
859
+ /** Clamp a long-poll timeout to the protocol-bounded [0, 55000] ms window. */
860
+ function clampWaitMs(ms) {
861
+ const n = Number(ms);
862
+ if (!Number.isFinite(n) || n <= 0) return 55000;
863
+ return Math.min(55000, Math.floor(n));
864
+ }
865
+
866
+ // --- decisions registry (adopt/supersede governance-gated) ------------------
867
+
868
+ /**
869
+ * Propose a decision into the registry (decision.propose). Idempotent — pass a
870
+ * stable `o.idempotencyKey` (or `params.id`) so a retry reuses the row.
871
+ * @param {object} params - { id?, title, rationale?, scope?, links? }
872
+ * @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
873
+ */
874
+ export function decisionPropose(params, o = {}) {
875
+ return call("decision.propose", params || {}, o);
876
+ }
877
+
878
+ /**
879
+ * Adopt a proposed decision (decision.adopt). governanceGated — returns
880
+ * GOVERNANCE_NOT_READY until the server is governance_ready.
881
+ * @param {object} params - { id }
882
+ * @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
883
+ */
884
+ export function decisionAdopt(params, o = {}) {
885
+ return call("decision.adopt", params || {}, o);
886
+ }
887
+
888
+ /**
889
+ * Supersede an adopted decision with a newer one (decision.supersede).
890
+ * governanceGated.
891
+ * @param {object} params - { id, supersededBy }
892
+ * @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
893
+ */
894
+ export function decisionSupersede(params, o = {}) {
895
+ return call("decision.supersede", params || {}, o);
896
+ }
897
+
898
+ /**
899
+ * List adopted decisions (GET /v1/decision.list). Returns the decisions array,
900
+ * or [] on any failure (fail-open).
901
+ * @param {object} o - { base, token, status?, fetchImpl? }
902
+ * @returns {Promise<object[]>}
903
+ */
904
+ export async function decisionList(o = {}) {
905
+ const qs = o && o.status ? `?status=${encodeURIComponent(o.status)}` : "";
906
+ const r = await read(`decision.list${qs}`, o);
907
+ if (!r.ok || !r.payload) return [];
908
+ if (Array.isArray(r.payload)) return r.payload;
909
+ if (Array.isArray(r.payload.decisions)) return r.payload.decisions;
910
+ return [];
911
+ }
912
+
913
+ // --- cost rollups (agents report usage; rollups read org-wide) --------------
914
+
915
+ /**
916
+ * Report per-session usage into the org rollup (cost.report). Idempotent on the
917
+ * row id / decision_id — pass a stable `o.idempotencyKey` so a re-pushed batch
918
+ * never double-counts. The params shape mirrors the maestro router ledger row
919
+ * (agent, model, input_tokens, output_tokens, estimated_usd, decision_id,
920
+ * cadence/source) — see lib/org/cost-sync.mjs.
921
+ * @param {object} params - { rows:[…] } or a single row
922
+ * @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
923
+ */
924
+ export function costReport(params, o = {}) {
925
+ return call("cost.report", params || {}, o);
926
+ }
927
+
928
+ /**
929
+ * Read the org-wide cost rollup (GET /v1/cost.rollup). Aggregated by
930
+ * agent/model/cadence/day server-side. Returns the bare payload, or null on
931
+ * failure (fail-open). The payload includes the G16 honesty line: any LLM spend
932
+ * attributed to a coordination cadence is surfaced here so a violation is visible.
933
+ * @param {object} o - { base, token, day?, agent?, fetchImpl? }
934
+ * @returns {Promise<object|null>}
935
+ */
936
+ export async function costRollup(o = {}) {
937
+ const params = [];
938
+ if (o && o.day) params.push(`day=${encodeURIComponent(o.day)}`);
939
+ if (o && o.agent) params.push(`agent=${encodeURIComponent(o.agent)}`);
940
+ const qs = params.length ? `?${params.join("&")}` : "";
941
+ const r = await read(`cost.rollup${qs}`, o);
942
+ if (!r.ok || !r.payload) return null;
943
+ return r.payload;
944
+ }
945
+
946
+ // --- policy distribution (publish admin; fetch via the read surface) --------
947
+
948
+ /**
949
+ * Publish a signed policy bundle (policy.publish). ADMIN — reserved namespace,
950
+ * always requires the `admin` scope; an ordinary agent token is rejected with
951
+ * FORBIDDEN_SCOPE. Agents FETCH via policyFetch; they never publish.
952
+ * @param {object} params - { bundleId, version, kind, content, content_sig, source_commit, reload_kind?, rollout? }
953
+ * @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
954
+ */
955
+ export function policyPublish(params, o = {}) {
956
+ return call("policy.publish", params || {}, o);
957
+ }
958
+
959
+ /**
960
+ * Fetch the current signed policy bundle(s) (GET /v1/policy). Returns the raw
961
+ * bundle payload (with its `content_sig` envelope) for lib/org/policy.mjs to
962
+ * verify against the PINNED trust root — this function does NOT trust the server.
963
+ * Returns null on failure (fail-open).
964
+ * @param {object} o - { base, token, kind?, fetchImpl? }
965
+ * @returns {Promise<object|null>}
966
+ */
967
+ export async function policyFetch(o = {}) {
968
+ const qs = o && o.kind ? `?kind=${encodeURIComponent(o.kind)}` : "";
969
+ const r = await read(`policy${qs}`, o);
970
+ if (!r.ok || !r.payload) return null;
971
+ return r.payload;
972
+ }
973
+
974
+ // ---------------------------------------------------------------------------
975
+ // Org credential broker — the single source of truth for org third-party creds
976
+ // ---------------------------------------------------------------------------
977
+ //
978
+ // The payoff of the org plane: an org runs 50-100 agents on separate machines
979
+ // and must NOT provision a DeepSeek/Kimi/OpenAI key onto every one (unmanageable
980
+ // + a breach surface). Instead an admin sets each provider key ONCE in Cohort
981
+ // (credential.put); an agent LEASES it short-lived, role-scoped, and audited
982
+ // (credential.lease) — the org key never persists on the agent machine.
983
+ //
984
+ // Same contract as every other helper: a thin wrapper over call() over the /v1
985
+ // binding, Bearer + x-org-protocol on every request, x-idempotency-key on the
986
+ // side-effecting methods. All fail open — a disabled/unreachable server yields an
987
+ // error frame, never a throw — so a missing org credential surfaces downstream as
988
+ // the router's existing `missing_credential`, never as a crash.
989
+ //
990
+ // SCOPES (frozen in protocol.mjs): credential.put / list / revoke are ADMIN
991
+ // (reserved namespace ⇒ always require `admin`; an ordinary agent token is
992
+ // rejected with FORBIDDEN_SCOPE). credential.lease requires `credential.use`,
993
+ // which IS in DEFAULT_AGENT_SCOPES — so a normal paired agent can lease but never
994
+ // put/list/revoke.
995
+ //
996
+ // SECURITY: credential.lease returns the plaintext ONLY in the response frame
997
+ // (over TLS in prod). credential.list NEVER returns the value (metadata only).
998
+ // The value is NEVER logged anywhere on either side. These helpers MUST NOT
999
+ // persist the leased value to disk; the in-memory cache lives in
1000
+ // lib/model-router/org-credentials.mjs, not here.
1001
+
1002
+ /**
1003
+ * Lease an org-level third-party credential (credential.lease). Scope
1004
+ * `credential.use` (in DEFAULT_AGENT_SCOPES). The server resolves the caller's
1005
+ * verified identity, checks the credential's allowedRoles/allowedAgents, and —
1006
+ * on success — OPENS the AES-256-GCM box transiently and returns the plaintext
1007
+ * in the result frame ONLY: `{ provider, value, ttlMs, expiresAt, auth_env? }`.
1008
+ * A role/agent the credential does not permit gets FORBIDDEN_SCOPE.
1009
+ *
1010
+ * NOT idempotent in the registry (each lease is a fresh short-lived grant +
1011
+ * audit row); only pass an `o.idempotencyKey` if you explicitly want to coalesce
1012
+ * identical retries. Fail-open: an unreachable server yields an error frame so
1013
+ * the router falls through to whatever local env already has.
1014
+ *
1015
+ * @param {object} params - { provider }
1016
+ * @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
1017
+ * @returns {Promise<object>} res frame {ok:true,result:{provider,value,ttlMs,expiresAt}}|{ok:false,error}
1018
+ */
1019
+ export function credentialLease(params, o = {}) {
1020
+ return call("credential.lease", params || {}, o);
1021
+ }
1022
+
1023
+ /**
1024
+ * Set (upsert) an org-level third-party credential (credential.put). ADMIN —
1025
+ * reserved namespace, always requires the `admin` scope; an ordinary agent token
1026
+ * is rejected with FORBIDDEN_SCOPE. The admin sets each provider key ONCE; the
1027
+ * server SEALS the value at rest (AES-256-GCM) and never logs it. Idempotent on
1028
+ * `provider` — pass a stable `o.idempotencyKey` (auto-derived from
1029
+ * `params.provider` when absent) so a re-pushed key collapses to one rotation.
1030
+ *
1031
+ * @param {object} params - { provider, value, auth_env?, allowedRoles?, allowedAgents?, ttlMs? }
1032
+ * @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
1033
+ */
1034
+ export function credentialPut(params, o = {}) {
1035
+ const opts = o;
1036
+ if (!opts.idempotencyKey && params && params.provider) {
1037
+ opts.idempotencyKey = `credential.put:${params.provider}`;
1038
+ }
1039
+ return call("credential.put", params || {}, opts);
1040
+ }
1041
+
1042
+ /**
1043
+ * List org credentials' METADATA (credential.list). ADMIN. NEVER returns the
1044
+ * value — only provider, auth_env, allowedRoles/allowedAgents, ttl, timestamps.
1045
+ * Non-side-effecting. Returns the res frame; the caller reads `result.credentials`.
1046
+ * @param {object} params - { provider? }
1047
+ * @param {object} o - { base, token, fetchImpl? }
1048
+ */
1049
+ export function credentialList(params, o = {}) {
1050
+ return call("credential.list", params || {}, o);
1051
+ }
1052
+
1053
+ /**
1054
+ * Revoke an org credential (credential.revoke). ADMIN. Future leases for the
1055
+ * provider fail; already-leased values expire at their TTL (the org key never
1056
+ * persisted on the agent, so revoke + TTL is the bound on exposure). NOT
1057
+ * idempotent in the registry; pass a stable `o.idempotencyKey` for retries.
1058
+ * @param {object} params - { provider, reason? }
1059
+ * @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
1060
+ */
1061
+ export function credentialRevoke(params, o = {}) {
1062
+ return call("credential.revoke", params || {}, o);
1063
+ }
1064
+
1065
+ // ---------------------------------------------------------------------------
1066
+ // Phase-4 typed RPC helpers — knowledge plane, contacts, meetings
1067
+ // ---------------------------------------------------------------------------
1068
+ //
1069
+ // The agent half of the shared knowledge plane (SPEC-org-server §4.7). Same
1070
+ // contract as the phase-1/2/3 helpers: a thin wrapper over call() / read() over
1071
+ // the /v1 binding, Bearer + x-org-protocol on every request, x-idempotency-key
1072
+ // on the idempotent side-effecting methods (knowledge.append / contacts.upsert /
1073
+ // meetings.record — auto-derived from a stable id when the caller doesn't supply
1074
+ // one). All fail open — a disabled/unreachable server yields an error frame,
1075
+ // never a throw.
1076
+ //
1077
+ // CRITICAL — the SERVER is authoritative for ACL. These helpers NEVER decide
1078
+ // what a fact's group is, never filter a search result, and never inspect a
1079
+ // barrier cell. The non-bypassable RLS floor (group membership, barrier:<cell>
1080
+ // deny-override, participant-derived ACLs) lives in the server's query layer.
1081
+ // The client only renders what the server chose to return. knowledge.search is
1082
+ // SIDE-EFFECTING by contract: every retrieval is logged to org_events family
1083
+ // 'knowledge' (the regulator-facing read-audit trail) — so it carries the same
1084
+ // idempotency/Bearer treatment as a write, not a read.
1085
+
1086
+ // --- knowledge facts (mutable-artifact semantics) --------------------------
1087
+
1088
+ /**
1089
+ * Append a knowledge episode (knowledge.append). THE sole shared-write primitive;
1090
+ * commutes (concurrent appends both land) and is IDEMPOTENT — pass a stable
1091
+ * `o.idempotencyKey` (or `params.id`) so a retried append doesn't double-write.
1092
+ * The server assigns the fact's group from the caller's verified identity / the
1093
+ * supplied `group` hint subject to the RLS floor — the client never decides ACL.
1094
+ * @param {object} params - { id?, group?, text, kind?, participants?, provenance?, links?, refs? }
1095
+ * @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
1096
+ */
1097
+ export function knowledgeAppend(params, o = {}) {
1098
+ return call("knowledge.append", params || {}, o);
1099
+ }
1100
+
1101
+ /**
1102
+ * Replace a fact via compare-and-swap on its version (knowledge.replace). The
1103
+ * server rejects a stale CAS with CONFLICT. NOT idempotent in the registry; pass
1104
+ * a stable `o.idempotencyKey` for safe retries.
1105
+ * @param {object} params - { id, expectedVersion, text, group?, provenance? }
1106
+ * @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
1107
+ */
1108
+ export function knowledgeReplace(params, o = {}) {
1109
+ return call("knowledge.replace", params || {}, o);
1110
+ }
1111
+
1112
+ /**
1113
+ * Rewrite a fact in place (knowledge.rewrite) — DESIGNATED-OWNER-ONLY. The server
1114
+ * rejects a non-owner with FORBIDDEN_SCOPE / a method-layer owner check. NOT
1115
+ * idempotent; pass a stable `o.idempotencyKey` for retries.
1116
+ * @param {object} params - { id, text, group?, provenance? }
1117
+ * @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
1118
+ */
1119
+ export function knowledgeRewrite(params, o = {}) {
1120
+ return call("knowledge.rewrite", params || {}, o);
1121
+ }
1122
+
1123
+ /**
1124
+ * Invalidate a fact (knowledge.invalidate) — INVALIDATE, DON'T DELETE. Marks the
1125
+ * fact invalid (provenance + tombstone) and excludes it from future search; the
1126
+ * row PERSISTS for audit and provenance/blast-radius queries. NOT idempotent;
1127
+ * pass a stable `o.idempotencyKey` for retries.
1128
+ * @param {object} params - { id, reason?, supersededBy? }
1129
+ * @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
1130
+ */
1131
+ export function knowledgeInvalidate(params, o = {}) {
1132
+ return call("knowledge.invalidate", params || {}, o);
1133
+ }
1134
+
1135
+ /**
1136
+ * Search the ACL'd shared facts (knowledge.search). SIDE-EFFECTING by contract:
1137
+ * every retrieval appends a 'knowledge' org_events row capturing
1138
+ * {agent, query, returned_ids, groups_considered} — the read-audit trail. The
1139
+ * SERVER applies the RLS floor and returns ONLY the facts this agent's verified
1140
+ * groups may see (barrier:<cell> deny-override); the client does not filter. NOT
1141
+ * idempotent in the registry (each search is a distinct audited retrieval) — only
1142
+ * pass an `o.idempotencyKey` if you explicitly want to coalesce identical retries.
1143
+ * @param {object} params - { query, group?, limit?, includeInvalid?, kind? }
1144
+ * @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
1145
+ * @returns {Promise<object>} res frame whose result carries { facts:[…], returned_ids:[…], groups_considered:[…] }
1146
+ */
1147
+ export function knowledgeSearch(params, o = {}) {
1148
+ return call("knowledge.search", params || {}, o);
1149
+ }
1150
+
1151
+ // --- contacts registry (ACL-filtered) --------------------------------------
1152
+
1153
+ /**
1154
+ * Upsert an org contact (contacts.upsert). Idempotent — pass a stable
1155
+ * `o.idempotencyKey` (or `params.id`) so a re-pushed contact collapses to one row.
1156
+ * @param {object} params - { id?, name?, emails?, phones?, handles?, org?, group?, fields? }
1157
+ * @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
1158
+ */
1159
+ export function contactsUpsert(params, o = {}) {
1160
+ return call("contacts.upsert", params || {}, o);
1161
+ }
1162
+
1163
+ /**
1164
+ * List org contacts visible to the actor (GET /v1/contacts.list). The server
1165
+ * ACL-filters; returns the contacts array, or [] on any failure (fail-open).
1166
+ * @param {object} o - { base, token, q?, group?, limit?, fetchImpl? }
1167
+ * @returns {Promise<object[]>}
1168
+ */
1169
+ export async function contactsList(o = {}) {
1170
+ const params = [];
1171
+ if (o && o.q) params.push(`q=${encodeURIComponent(o.q)}`);
1172
+ if (o && o.group) params.push(`group=${encodeURIComponent(o.group)}`);
1173
+ if (o && Number.isFinite(o.limit)) params.push(`limit=${encodeURIComponent(o.limit)}`);
1174
+ const qs = params.length ? `?${params.join("&")}` : "";
1175
+ const r = await read(`contacts.list${qs}`, o);
1176
+ if (!r.ok || !r.payload) return [];
1177
+ if (Array.isArray(r.payload)) return r.payload;
1178
+ if (Array.isArray(r.payload.contacts)) return r.payload.contacts;
1179
+ return [];
1180
+ }
1181
+
1182
+ // --- meetings registry (ACL-filtered) --------------------------------------
1183
+
1184
+ /**
1185
+ * Record a meeting (meetings.record). Idempotent — pass a stable
1186
+ * `o.idempotencyKey` (or `params.id`) so a re-pushed meeting collapses to one row.
1187
+ * @param {object} params - { id?, title?, at?, participants?, notes?, group?, refs?, fields? }
1188
+ * @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
1189
+ */
1190
+ export function meetingsRecord(params, o = {}) {
1191
+ return call("meetings.record", params || {}, o);
1192
+ }
1193
+
1194
+ /**
1195
+ * List org meetings visible to the actor (GET /v1/meetings.list). The server
1196
+ * ACL-filters (participant-derived ACL); returns the meetings array, or [] on any
1197
+ * failure (fail-open).
1198
+ * @param {object} o - { base, token, q?, group?, since?, limit?, fetchImpl? }
1199
+ * @returns {Promise<object[]>}
1200
+ */
1201
+ export async function meetingsList(o = {}) {
1202
+ const params = [];
1203
+ if (o && o.q) params.push(`q=${encodeURIComponent(o.q)}`);
1204
+ if (o && o.group) params.push(`group=${encodeURIComponent(o.group)}`);
1205
+ if (o && o.since) params.push(`since=${encodeURIComponent(o.since)}`);
1206
+ if (o && Number.isFinite(o.limit)) params.push(`limit=${encodeURIComponent(o.limit)}`);
1207
+ const qs = params.length ? `?${params.join("&")}` : "";
1208
+ const r = await read(`meetings.list${qs}`, o);
1209
+ if (!r.ok || !r.payload) return [];
1210
+ if (Array.isArray(r.payload)) return r.payload;
1211
+ if (Array.isArray(r.payload.meetings)) return r.payload.meetings;
1212
+ return [];
1213
+ }
1214
+
1215
+ // ---------------------------------------------------------------------------
1216
+ // Messaging + calling (SP3/SP10) — participate in the org's human messaging app
1217
+ // ---------------------------------------------------------------------------
1218
+ //
1219
+ // The agent half of the messaging.*/calling.* surface (protocol.mjs). Same
1220
+ // contract as every other helper: a thin wrapper over call() / read() over the
1221
+ // /v1 binding, Bearer + x-org-protocol on every request, x-idempotency-key on the
1222
+ // idempotent side-effecting methods (messaging.send is idempotent on the
1223
+ // client-supplied message id; calling.* are not, so pass a stable key only when
1224
+ // you want to coalesce retries). All fail open — a disabled/unreachable server
1225
+ // yields an error frame, never a throw.
1226
+ //
1227
+ // These are the RAW RPC wrappers. The POLICY + TELEMETRY layer (the send-gate
1228
+ // chokepoint, the afterSend hook, and the source="messaging" cost attribution)
1229
+ // lives in lib/org/messaging.mjs, which composes these. Outbound CONTENT sends
1230
+ // should go through lib/org/messaging.sendMessage, NOT messagingSend directly,
1231
+ // so the send-gate is never bypassed.
1232
+
1233
+ /** Post a message to an org channel (messaging.send). Idempotent on the message id. */
1234
+ export function messagingSend(params, o = {}) {
1235
+ return call("messaging.send", params || {}, o);
1236
+ }
1237
+
1238
+ /** React to an org message (messaging.react). */
1239
+ export function messagingReact(params, o = {}) {
1240
+ return call("messaging.react", params || {}, o);
1241
+ }
1242
+
1243
+ /** Edit an org message the agent sent (messaging.edit). */
1244
+ export function messagingEdit(params, o = {}) {
1245
+ return call("messaging.edit", params || {}, o);
1246
+ }
1247
+
1248
+ /** Read channel history (messaging.history). Read scope, non-mutating. */
1249
+ export function messagingHistory(params, o = {}) {
1250
+ return call("messaging.history", params || {}, o);
1251
+ }
1252
+
1253
+ /** List the org channels visible to this agent (messaging.channels). Read scope. */
1254
+ export function messagingChannels(params, o = {}) {
1255
+ return call("messaging.channels", params || {}, o);
1256
+ }
1257
+
1258
+ /** Start a call (calling.start). */
1259
+ export function callingStart(params, o = {}) {
1260
+ return call("calling.start", params || {}, o);
1261
+ }
1262
+
1263
+ /** Join a call (calling.join). */
1264
+ export function callingJoin(params, o = {}) {
1265
+ return call("calling.join", params || {}, o);
1266
+ }
1267
+
1268
+ /** Invite participants to a call (calling.invite). */
1269
+ export function callingInvite(params, o = {}) {
1270
+ return call("calling.invite", params || {}, o);
1271
+ }
1272
+
1273
+ /** End a call (calling.end). */
1274
+ export function callingEnd(params, o = {}) {
1275
+ return call("calling.end", params || {}, o);
1276
+ }
1277
+
1278
+ /**
1279
+ * Append one finalized transcript line to a live call (calling.addTranscriptLine).
1280
+ * Agent-operability write: the server persists a CallTranscriptLine projection row
1281
+ * (speaker = the acting member) and appends ONE redacted `calling` event — the
1282
+ * chain records line metadata (callId, memberId, ts) but NEVER the text (spec §3).
1283
+ * The actor must be a participant in a still-live call. Side-effecting.
1284
+ * @param {{callId:string, text:string}} params
1285
+ * @param {object} o - { base, token, orgId?, idempotencyKey?, fetchImpl? }
1286
+ */
1287
+ export function callingAddTranscriptLine(params, o = {}) {
1288
+ return call("calling.addTranscriptLine", params || {}, o);
1289
+ }
1290
+
1291
+ /**
1292
+ * Post one message to a live call's in-call chat panel (calling.chat). Same
1293
+ * contract as addTranscriptLine: one CallChatMessage projection row + one redacted
1294
+ * `calling` event (metadata only, never the body). Participant-gated. Side-effecting.
1295
+ * @param {{callId:string, text:string}} params
1296
+ * @param {object} o - { base, token, orgId?, idempotencyKey?, fetchImpl? }
1297
+ */
1298
+ export function callingChat(params, o = {}) {
1299
+ return call("calling.chat", params || {}, o);
1300
+ }
1301
+
1302
+ // ---------------------------------------------------------------------------
1303
+ // Calling working sessions + vision fusion (2026-08)
1304
+ // ---------------------------------------------------------------------------
1305
+ //
1306
+ // The app-share surface: an AI participant "presents" a Cohort surface
1307
+ // (doc/sheet/channel/board/charter) on the call stage, rendered locally by
1308
+ // every client and driven by step frames over the room data lane. The three
1309
+ // writes persist a CallShareSession projection and append REDACTED share.*
1310
+ // events (ids/counts only — never step text or doc content); the three reads
1311
+ // hydrate late joiners, the narrated visual stream, and recent non-speech call
1312
+ // events. Same thin fail-open call() wrappers as the rest of calling.*.
1313
+
1314
+ /**
1315
+ * Start presenting a Cohort surface on a live call (calling.appShareStart).
1316
+ * One active session per call: your own previous session is superseded; a
1317
+ * CONFLICT frame comes back while another member is presenting. Appends ONE
1318
+ * redacted `share.started` event and publishes the state frame to the room.
1319
+ * The actor must be a participant in a still-live call. Side-effecting.
1320
+ * @param {{callId:string, surface:{kind:string, ref:string, title?:string}}} params
1321
+ * @param {object} o - { base, token, orgId?, idempotencyKey?, fetchImpl? }
1322
+ */
1323
+ export function callingAppShareStart(params, o = {}) {
1324
+ return call("calling.appShareStart", params || {}, o);
1325
+ }
1326
+
1327
+ /**
1328
+ * Drive the active share with a step batch (calling.appShareAct): cursor /
1329
+ * scroll / highlight / type / refresh, ≤20 steps per call. Steps animate the
1330
+ * tile on every client; the chain records ONE redacted `share.acted` event
1331
+ * ({sessionId, stepCount} — never the step text). Session must be active and
1332
+ * owned by the acting member. Side-effecting.
1333
+ * @param {{callId:string, sessionId:string, steps:Array<object>}} params
1334
+ * @param {object} o - { base, token, orgId?, idempotencyKey?, fetchImpl? }
1335
+ */
1336
+ export function callingAppShareAct(params, o = {}) {
1337
+ return call("calling.appShareAct", params || {}, o);
1338
+ }
1339
+
1340
+ /**
1341
+ * End the active share session (calling.appShareEnd). Sharer or call host.
1342
+ * Appends one redacted `share.ended` event and clears the stage tile.
1343
+ * Side-effecting.
1344
+ * @param {{callId:string, sessionId:string, reason?:('ended'|'superseded')}} params
1345
+ * @param {object} o - { base, token, orgId?, idempotencyKey?, fetchImpl? }
1346
+ */
1347
+ export function callingAppShareEnd(params, o = {}) {
1348
+ return call("calling.appShareEnd", params || {}, o);
1349
+ }
1350
+
1351
+ /**
1352
+ * The call's active share session, or null (calling.getAppShare) — what a late
1353
+ * joiner renders: {sessionId, by, surface, startedAt, state}. Read-style.
1354
+ * @param {{callId:string}} params
1355
+ * @param {object} o - { base, token, orgId?, fetchImpl? }
1356
+ */
1357
+ export function callingGetAppShare(params, o = {}) {
1358
+ return call("calling.getAppShare", params || {}, o);
1359
+ }
1360
+
1361
+ /**
1362
+ * The narrated visual stream for a call (calling.getVisualContext): the last N
1363
+ * CallVisualEvent rows ({kind, sourceMemberId, summary, at}, chronological,
1364
+ * limit ≤50 default 20). Narration TEXT only — frames are never persisted
1365
+ * server-side. Read-style.
1366
+ * @param {{callId:string, limit?:number}} params
1367
+ * @param {object} o - { base, token, orgId?, fetchImpl? }
1368
+ */
1369
+ export function callingGetVisualContext(params, o = {}) {
1370
+ return call("calling.getVisualContext", params || {}, o);
1371
+ }
1372
+
1373
+ /**
1374
+ * Recent non-speech call events (calling.getCallEvents): reactions, raised /
1375
+ * lowered hands, joins and share.* lifecycle, mapped {kind, actorId, at,
1376
+ * payload} (payloads already redacted), limit ≤100 default 30. Read-style.
1377
+ * @param {{callId:string, limit?:number}} params
1378
+ * @param {object} o - { base, token, orgId?, fetchImpl? }
1379
+ */
1380
+ export function callingGetCallEvents(params, o = {}) {
1381
+ return call("calling.getCallEvents", params || {}, o);
1382
+ }
1383
+
1384
+ // ---------------------------------------------------------------------------
1385
+ // Pairing admin approval (authed)
1386
+ // ---------------------------------------------------------------------------
1387
+ //
1388
+ // pairRequest() (above) is the pre-auth half of the handshake. pairApprove is
1389
+ // the ADMIN half: an already-paired admin agent resolves a pending request by
1390
+ // code. Reserved-admin namespace ⇒ the dispatcher enforces `admin` scope. Thin
1391
+ // authed call() wrapper (Bearer + x-org-protocol; the request is side-effecting).
1392
+
1393
+ /**
1394
+ * Approve a pending pairing request by code (pairing.approve). Admin scope.
1395
+ * Flips the PairingRequest pending→approved server-side (CONFLICT if already
1396
+ * resolved) and appends one `pairing` event. Side-effecting.
1397
+ * @param {{code:string}} params
1398
+ * @param {object} o - { base, token, orgId?, idempotencyKey?, fetchImpl? }
1399
+ */
1400
+ export function pairApprove(params, o = {}) {
1401
+ return call("pairing.approve", params || {}, o);
1402
+ }
1403
+
1404
+ // ---------------------------------------------------------------------------
1405
+ // Branding (Brand Studio v2 write surface) — agent mirror of the human
1406
+ // branding-generation / brand-templates / brand-export server actions.
1407
+ // ---------------------------------------------------------------------------
1408
+ //
1409
+ // Thin call() wrappers over the branding.* RPCs (protocol.mjs). Writes require
1410
+ // ADMIN (the server mirrors requireWorkspaceRole("ADMIN")); the two reads
1411
+ // (listVersions/exportTemplate) use org.read. The branding surface owns its OWN
1412
+ // versioned mutation + audit (the BrandVersion spine + self-appended events), so
1413
+ // these methods are sideEffecting:false at the dispatcher layer — no
1414
+ // x-idempotency-key is derived. Fail-open like every other wrapper.
1415
+
1416
+ /** Regenerate the workspace brand system / registry copy (branding.regenerate). Admin. */
1417
+ export function brandingRegenerate(params, o = {}) {
1418
+ return call("branding.regenerate", params || {}, o);
1419
+ }
1420
+
1421
+ /** Kick off the bespoke-template pipeline for the workspace (branding.regenerateTemplates). Admin. */
1422
+ export function brandingRegenerateTemplates(params, o = {}) {
1423
+ return call("branding.regenerateTemplates", params || {}, o);
1424
+ }
1425
+
1426
+ /** Regenerate ONE bespoke template by slug (branding.regenerateTemplate). Admin. @param {{slug:string}} params */
1427
+ export function brandingRegenerateTemplate(params, o = {}) {
1428
+ return call("branding.regenerateTemplate", params || {}, o);
1429
+ }
1430
+
1431
+ /** Regenerate all canonical brand image slots (branding.regenerateImages). Admin. */
1432
+ export function brandingRegenerateImages(params, o = {}) {
1433
+ return call("branding.regenerateImages", params || {}, o);
1434
+ }
1435
+
1436
+ /** Set a brand image slot from a data URL (branding.uploadImage). Admin. @param {{slot:string, dataUrl:string}} params */
1437
+ export function brandingUploadImage(params, o = {}) {
1438
+ return call("branding.uploadImage", params || {}, o);
1439
+ }
1440
+
1441
+ /** AI-amend one registry-copy surface (branding.amendArtifact). Admin. @param {{key:string, instruction:string, elementRef?:string}} params */
1442
+ export function brandingAmendArtifact(params, o = {}) {
1443
+ return call("branding.amendArtifact", params || {}, o);
1444
+ }
1445
+
1446
+ /** AI-amend a CLONE/GENERATED template's copy (branding.amendTemplate). Admin. @param {{slug:string, instruction:string, elementRef?:string}} params */
1447
+ export function brandingAmendTemplate(params, o = {}) {
1448
+ return call("branding.amendTemplate", params || {}, o);
1449
+ }
1450
+
1451
+ /** Clone a registry/generated template into a new CLONE (branding.cloneTemplate). Admin. @param {{baseRef:string}} params */
1452
+ export function brandingCloneTemplate(params, o = {}) {
1453
+ return call("branding.cloneTemplate", params || {}, o);
1454
+ }
1455
+
1456
+ /** Soft-delete a CLONE/GENERATED template by slug (branding.deleteTemplate). Admin. @param {{slug:string}} params */
1457
+ export function brandingDeleteTemplate(params, o = {}) {
1458
+ return call("branding.deleteTemplate", params || {}, o);
1459
+ }
1460
+
1461
+ /** Restore a versioned brand target to a specific seq (branding.restoreVersion). Admin. @param {{targetKind:"ARTIFACT"|"TEMPLATE", ref:string, seq:number}} params */
1462
+ export function brandingRestoreVersion(params, o = {}) {
1463
+ return call("branding.restoreVersion", params || {}, o);
1464
+ }
1465
+
1466
+ /** Move a versioned brand target's head to the previous version (branding.undo). Admin. @param {{targetKind:"ARTIFACT"|"TEMPLATE", ref:string}} params */
1467
+ export function brandingUndo(params, o = {}) {
1468
+ return call("branding.undo", params || {}, o);
1469
+ }
1470
+
1471
+ /** Move a versioned brand target's head to the next version (branding.redo). Admin. @param {{targetKind:"ARTIFACT"|"TEMPLATE", ref:string}} params */
1472
+ export function brandingRedo(params, o = {}) {
1473
+ return call("branding.redo", params || {}, o);
1474
+ }
1475
+
1476
+ /** Read the version history of a versioned brand target (branding.listVersions). org.read. @param {{targetKind:"ARTIFACT"|"TEMPLATE", ref:string}} params */
1477
+ export function brandingListVersions(params, o = {}) {
1478
+ return call("branding.listVersions", params || {}, o);
1479
+ }
1480
+
1481
+ /** Deterministic key-free server render of a READY template to a PNG data URL (branding.exportTemplate). org.read. @param {{templateId:string, sizeId?:"mobile"|"tablet"|"desktop", theme?:"light"|"dark"}} params */
1482
+ export function brandingExportTemplate(params, o = {}) {
1483
+ return call("branding.exportTemplate", params || {}, o);
1484
+ }
1485
+
1486
+ // --- Design read/propose surface (2026-07) ---------------------------------
1487
+ //
1488
+ // The ten methods below are the half of the family an ORDINARY agent seat can
1489
+ // reach: `design.read` for the foundation/voice/template reads and the three
1490
+ // costed producers (renderTemplate / rewriteInVoice / exportKit), `design.write`
1491
+ // for generateImage + proposeChange. Foundation MUTATIONS stay human-gated
1492
+ // (DECISIONS D-7): proposeChange stages a reviewable diff and writes nothing,
1493
+ // while saveFoundation / restoreFoundation are `admin` and only an admin
1494
+ // credential reaches them at all.
1495
+ //
1496
+ // The costed methods (renderTemplate, exportKit, generateImage, rewriteInVoice)
1497
+ // return their spend — surface it. `generateImage` with `budgetCents: 0` is the
1498
+ // QUOTE call: it generates nothing, spends nothing, and still returns every
1499
+ // slot's price and staleness.
1500
+
1501
+ /** The workspace brand foundation + version-history head (branding.getFoundation). design.read. @param {{versionId?:string, historyLimit?:number}} params */
1502
+ export function brandingGetFoundation(params, o = {}) {
1503
+ return call("branding.getFoundation", params || {}, o);
1504
+ }
1505
+
1506
+ /** Voice statement, traits, SAY/AVOID lexicon, in-practice examples (branding.getVoice). design.read. */
1507
+ export function brandingGetVoice(params, o = {}) {
1508
+ return call("branding.getVoice", params || {}, o);
1509
+ }
1510
+
1511
+ /** The template gallery with per-template status chips (branding.listTemplates). design.read. @param {{visibleOnly?:boolean, category?:"digital"|"print"|"email"|"social"|"og"}} params */
1512
+ export function brandingListTemplates(params, o = {}) {
1513
+ return call("branding.listTemplates", params || {}, o);
1514
+ }
1515
+
1516
+ /** Render a template to HTML (any) or PNG (GENERATED only) (branding.renderTemplate). design.read, costed. @param {{templateId:string, theme?:string, size?:string, format?:"html"|"png", content?:object, budgetCents?:number}} params */
1517
+ export function brandingRenderTemplate(params, o = {}) {
1518
+ return call("branding.renderTemplate", params || {}, o);
1519
+ }
1520
+
1521
+ /** Rewrite a sentence in the workspace brand voice (branding.rewriteInVoice). design.read, costed. Produces copy; sends nothing. @param {{text:string, subject?:string}} params */
1522
+ export function brandingRewriteInVoice(params, o = {}) {
1523
+ return call("branding.rewriteInVoice", params || {}, o);
1524
+ }
1525
+
1526
+ /** Export the brand kit — tokens, voice, and (by default) the mark bytes (branding.exportKit). design.read, costed. @param {{unit?:"px"|"rem"|"em", includeMarks?:boolean, versionId?:string, budgetCents?:number}} params */
1527
+ export function brandingExportKit(params, o = {}) {
1528
+ return call("branding.exportKit", params || {}, o);
1529
+ }
1530
+
1531
+ /** Regenerate canonical brand image slots; omit `slots` for every STALE slot, `budgetCents:0` to QUOTE only (branding.generateImage). design.write, costed. @param {{slots?:string[], force?:boolean, budgetCents?:number}} params */
1532
+ export function brandingGenerateImage(params, o = {}) {
1533
+ return call("branding.generateImage", params || {}, o);
1534
+ }
1535
+
1536
+ /** Stage a REVIEWABLE foundation diff — writes nothing to the foundation (branding.proposeChange). design.write. Blocks are replaced wholesale: send each COMPLETE block. @param {{patch:object, note?:string, rationale?:string, expectedVersionId?:string|null}} params */
1537
+ export function brandingProposeChange(params, o = {}) {
1538
+ return call("branding.proposeChange", params || {}, o);
1539
+ }
1540
+
1541
+ /** Save the brand foundation (branding.saveFoundation). ADMIN — an ordinary agent seat proposes instead. @param {{foundation:object, expectedVersionId?:string|null, note?:string}} params */
1542
+ export function brandingSaveFoundation(params, o = {}) {
1543
+ return call("branding.saveFoundation", params || {}, o);
1544
+ }
1545
+
1546
+ /** Restore a historical foundation version as a NEW version (branding.restoreFoundation). ADMIN. @param {{versionId:string, note?:string, expectedVersionId?:string|null}} params */
1547
+ export function brandingRestoreFoundation(params, o = {}) {
1548
+ return call("branding.restoreFoundation", params || {}, o);
1549
+ }
1550
+
1551
+ // ---------------------------------------------------------------------------
1552
+ // Back-compat surface (drop-in for lib/org/cohort-client.mjs)
1553
+ // ---------------------------------------------------------------------------
1554
+
1555
+ /**
1556
+ * Project a maestro directory entry (assembleSelfEntry shape — id, name,
1557
+ * fullName, archetype{object}, towers, focus, presence, …) onto the params that
1558
+ * hq's `registry.register` accepts. The server schema is `.strict()` and only
1559
+ * models { displayName, archetype:string, humanSponsor, card } — so we MUST trim
1560
+ * here or a rich entry 400s (BAD_REQUEST). Everything the server doesn't model is
1561
+ * preserved REDACTION-SAFE under `card` (the handler records only a `hasCard`
1562
+ * boolean on the chain, never the blob). Pure; never throws.
1563
+ * @param {object} entry
1564
+ * @returns {object} registerSchema-shaped params
1565
+ */
1566
+ function entryToRegisterParams(entry) {
1567
+ const e = entry || {};
1568
+ const displayName = e.fullName || e.name || e.displayName || String(e.id || "");
1569
+ // archetype is an object in the maestro entry ({function,altitude,label}); the
1570
+ // server wants a single string. Prefer the human label, then the function.
1571
+ const arch = e.archetype;
1572
+ const archetype =
1573
+ typeof arch === "string"
1574
+ ? arch
1575
+ : (arch && (arch.label || arch.function || arch.altitude)) || "";
1576
+ const humanSponsor =
1577
+ (e.principal && (e.principal.fullName || e.principal.title)) || e.humanSponsor || "";
1578
+ const params = { card: e };
1579
+ if (displayName) params.displayName = String(displayName).slice(0, 200);
1580
+ if (archetype) params.archetype = String(archetype).slice(0, 120);
1581
+ if (humanSponsor) params.humanSponsor = String(humanSponsor).slice(0, 200);
1582
+ return params;
1583
+ }
1584
+
1585
+ /**
1586
+ * Publish (upsert) this agent's directory entry — now via the /v1 binding
1587
+ * (registry.register), preserving the legacy return shape `{ok,status}` /
1588
+ * `{ok:false,skipped:true}` so existing callers (org-sync) don't break. The rich
1589
+ * maestro entry is projected onto the server's strict registerSchema (extra
1590
+ * fields ride under `card`, chain-redacted to a `hasCard` flag). Carries the
1591
+ * derived idempotency key + optional x-org-id pin. Fail-open.
1592
+ * @param {object} o - { cfg, entry, fetchImpl? }
1593
+ * @returns {Promise<{ok:boolean, status?:number, skipped?:boolean, reason?:string}>}
1594
+ */
1595
+ export async function publishEntry(o = {}) {
1596
+ const { cfg, entry } = o;
1597
+ if (!isEnabled(cfg)) return { ok: false, skipped: true };
1598
+ if (!entry || !entry.id) return { ok: false, skipped: false, reason: "no-entry" };
1599
+ const c = configFromAgent(cfg);
1600
+ const frame = await register(entryToRegisterParams(entry), {
1601
+ base: c.base,
1602
+ token: c.token,
1603
+ orgId: c.orgId,
1604
+ idempotencyKey: `registry.register:${entry.id}`,
1605
+ fetchImpl: o.fetchImpl,
1606
+ });
1607
+ return { ok: !!(frame && frame.ok), status: frame && frame.ok ? 200 : 0 };
1608
+ }
1609
+
1610
+ /**
1611
+ * Fetch the live org directory — now via GET /v1/directory, preserving the
1612
+ * legacy contract (returns [] when disabled or on any error). Parses the array,
1613
+ * the hq `{members:[…]}` shape (each member carries its latest presence.beat
1614
+ * status snapshot per-member), and the legacy `{agents:[…]}` shape.
1615
+ * @param {object} o - { cfg, fetchImpl? }
1616
+ * @returns {Promise<object[]>}
1617
+ */
1618
+ export async function fetchDirectory(o = {}) {
1619
+ const { cfg } = o;
1620
+ if (!isEnabled(cfg)) return [];
1621
+ const c = configFromAgent(cfg);
1622
+ const r = await read("directory", { base: c.base, token: c.token, orgId: c.orgId, fetchImpl: o.fetchImpl });
1623
+ if (!r.ok || !r.payload) return [];
1624
+ if (Array.isArray(r.payload)) return r.payload;
1625
+ if (Array.isArray(r.payload.members)) return r.payload.members;
1626
+ if (Array.isArray(r.payload.agents)) return r.payload.agents;
1627
+ return [];
1628
+ }