@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,640 @@
1
+ /**
2
+ * scripts/daemon/session-outcomes.mjs — OUTCOME DISCIPLINE for daemon sessions.
3
+ *
4
+ * hq already guarantees this for LIVE CALLS (src/server/call-voice/chair.ts +
5
+ * outcomes.ts): a meeting's action points become real board rows, and a teardown
6
+ * SWEEP lands anything the chair captured but never wrote. The avatar worker is
7
+ * deliberately brainless, so an agent-sdk colleague on a call inherits that for
8
+ * free — this module does NOT duplicate any of it.
9
+ *
10
+ * What had NO equivalent is maestro's OWN daemon turns. A spawned session can
11
+ * decide things and commit to next steps ("I'll send the revised deck to Alice
12
+ * on Friday") that then exist only in `state/sessions/logs/<id>.log` — nobody
13
+ * reads a session log, so the commitment is lost and the next turn re-decides it.
14
+ * That is the exact failure the hq chair removes, and this closes it for the
15
+ * daemon.
16
+ *
17
+ * THE SHAPE (mirroring the chair's DISCIPLINE, not its code):
18
+ *
19
+ * CAPTURE The prompt asks the session to end with an `OUTCOMES:` block (see
20
+ * prompt-builder's OUTCOME_DISCIPLINE). `parseOutcomes` reads that
21
+ * block off the session's final text — deterministic, bounded, and
22
+ * matched ONLY on the literal `OUTCOMES` heading, which is the exact
23
+ * contract the prompt establishes. It is deliberately NOT tolerant of
24
+ * "Next steps" / "Action items": a turn that merely wrote an ordinary
25
+ * prose "Next steps:" section never agreed to a machine-read contract,
26
+ * and must not silently mint org-visible board rows because of it.
27
+ * GATE `outcomeSourceShareable` decides whether this turn's source is one
28
+ * whose commitments may become ORG-VISIBLE. Fail-CLOSED: only a Slack
29
+ * conversation POSITIVELY established as public (`channel_type` /
30
+ * `is_private` off the event — never the id prefix, which Slack does
31
+ * not guarantee) qualifies. A DM, a private channel, email, an unknown
32
+ * source or no source at all lands nothing — see the gate's own note.
33
+ * LAND `landOutcomes` writes each one through the EXISTING org protocol:
34
+ * `board.create` for the task (lib/org/board.mjs → createItem) plus a
35
+ * single `knowledge.append` episode (lib/org/knowledge.mjs → remember)
36
+ * recording the commitment set. NO new protocol methods — the protocol
37
+ * is checksum-pinned and cross-repo-synced.
38
+ * SWEEP `sweepSessionOutcomes` is the post-session hook, called from the
39
+ * dispatcher's onClose. It is DRIVEN, not left to the model to
40
+ * remember: whatever the turn captured is landed at session end.
41
+ *
42
+ * IDEMPOTENT, TWO WAYS. In-process via the per-session `landed` flag (so a double
43
+ * close is a no-op), and — the one that survives a restart — via a DETERMINISTIC
44
+ * `itemId` derived from (sessionId, normalised title). `board.create` treats a
45
+ * caller-supplied id that already exists as a replay, so re-landing the same
46
+ * commitment never doubles a board row.
47
+ *
48
+ * FAIL-OPEN, ALWAYS. Disabled, unconfigured, unreachable, unparseable ⇒ a benign
49
+ * `{created:0, skipped:true}`. Nothing here may ever throw into the dispatch path:
50
+ * an outcome write must never cost the user their reply.
51
+ *
52
+ * OFF BY DEFAULT behind `DAEMON_SESSION_OUTCOMES=1` (same opt-in shape as
53
+ * `DAEMON_CONTEXT_COMPILER`). This writes REAL rows to the live org board from
54
+ * every clean daemon turn; on a live daemon the safe default is a machine that
55
+ * does nothing until it is switched on deliberately.
56
+ *
57
+ * Pure-core + injectable IO: every collaborator (createItemImpl / rememberImpl /
58
+ * cfg / enabled) can be injected, so the tests drive the real logic without a
59
+ * server. ESM, Node builtins only.
60
+ *
61
+ * @module scripts/daemon/session-outcomes
62
+ */
63
+
64
+ "use strict";
65
+
66
+ import { createHash } from "node:crypto";
67
+
68
+ import { createItem as orgCreateItem } from "../../lib/org/board.mjs";
69
+ import { isEnabled as orgEnabled, configFromAgent } from "../../lib/org/client.mjs";
70
+ import { remember as orgRemember } from "../../lib/org/knowledge.mjs";
71
+
72
+ /** Cap what ONE turn can write. A runaway summary must not flood the board. */
73
+ export const MAX_OUTCOMES = 8;
74
+
75
+ const MAX_TITLE_CHARS = 200;
76
+ const MAX_PARTY_CHARS = 80;
77
+ /** Only ever scan the tail of a long transcript for the outcomes block. */
78
+ const MAX_SCAN_CHARS = 20_000;
79
+
80
+ // ---------------------------------------------------------------------------
81
+ // Flag
82
+ // ---------------------------------------------------------------------------
83
+
84
+ /**
85
+ * Is the outcome discipline switched on? OFF unless `DAEMON_SESSION_OUTCOMES=1`.
86
+ * @param {object} [env] injectable environment (defaults to process.env)
87
+ * @returns {boolean}
88
+ */
89
+ export function outcomesEnabled(env = process.env) {
90
+ return String((env && env.DAEMON_SESSION_OUTCOMES) || "") === "1";
91
+ }
92
+
93
+ // ---------------------------------------------------------------------------
94
+ // Source gate — may THIS turn's commitments become ORG-VISIBLE?
95
+ // ---------------------------------------------------------------------------
96
+
97
+ /**
98
+ * CONFIDENTIALITY GATE. A landed outcome is not a private note: `board.create`
99
+ * is sent with no `workstreamId` (so the server gives the row no `channelId`,
100
+ * and hq's channel-less board query applies no channel filter) and the episode's
101
+ * `knowledge.append` carries no group (so hq's `normalizeGroupId(undefined)`
102
+ * resolves to the `org` group). Both artifacts are therefore readable by EVERY
103
+ * member of the org — and they carry the commitment text PLUS the topic line
104
+ * (`classResult.summary`, or the item's own subject).
105
+ *
106
+ * That is correct for a public channel message. It is NOT correct for a private
107
+ * DM: the daemon would be republishing a one-to-one conversation to the whole
108
+ * org. The protocol gives us no way to derive a narrower audience here —
109
+ * `board.create` accepts only a `workstreamId`, and a Slack DM maps to no
110
+ * workstream — so the gate is the answer: restrict the sweep to sources that are
111
+ * already org-visible.
112
+ *
113
+ * FAIL-CLOSED, unlike everything else in this module. Every other failure mode
114
+ * here is fail-open because the cost is a lost board row; the cost of getting
115
+ * THIS wrong is a leaked private conversation, so anything we cannot positively
116
+ * establish as public lands nothing. Email is one-to-one correspondence, and a
117
+ * source we do not recognise is by definition not established as public.
118
+ *
119
+ * THE SIGNAL IS `channel_type`/`is_private`, NEVER THE ID PREFIX. Slack is
120
+ * explicit that a conversation ID's leading letter is NOT a reliable type
121
+ * indicator: a PRIVATE channel can and does carry a `C…` id (workspaces created
122
+ * after the `G…`-for-groups era mint `C…` ids for private channels too), and an
123
+ * mpim may carry either. Only `D` for a DM is dependable. Admitting on `/^C/`
124
+ * therefore admitted private channels — the exact leak this gate exists to
125
+ * prevent. So admission now requires a POSITIVE public signal carried on the
126
+ * item by the ingest layer straight off Slack's event envelope:
127
+ *
128
+ * `channel_type === "channel"` Slack's message-event conversation type
129
+ * ("channel" public | "group" private |
130
+ * "im" DM | "mpim" group DM), or
131
+ * `is_private === false` an explicit privacy verdict (conversations.list
132
+ * / conversations.info).
133
+ *
134
+ * Anything else — a missing signal, an unrecognised type, an id we can only
135
+ * guess about — lands nothing. Prefixes are still consulted, but only in the
136
+ * DENYING direction (`D…` ⇒ DM, `G…` ⇒ private group), where a false positive
137
+ * costs a board row rather than a confidence.
138
+ *
139
+ * Producers: scripts/poller/slack-socket-mode.mjs (`event.channel_type` off the
140
+ * Socket Mode envelope), scripts/poller/slack-poller.mjs (`is_private` off
141
+ * conversations.list), and the YAML round-trip that carries them to the daemon
142
+ * (scripts/poller/utils.mjs `inboxItemToYaml` ⇄
143
+ * scripts/poller/inbox-scan-poller.mjs `parseInboxItemYaml`, which decodes
144
+ * `is_private` as a TRI-STATE so "absent" never reads as "public").
145
+ *
146
+ * @param {object} [item] the inbox item the turn was about
147
+ * @returns {{shareable:boolean, reason:string}}
148
+ */
149
+ export function outcomeSourceShareable(item) {
150
+ if (!item || typeof item !== "object") return { shareable: false, reason: "no-source" };
151
+
152
+ // Explicit privacy signals win outright, whatever the ids say.
153
+ if (item.is_dm === true) return { shareable: false, reason: "dm" };
154
+ if (item.is_private === true) return { shareable: false, reason: "private-channel" };
155
+
156
+ const channel = String(item.channel || "").trim();
157
+ if (/^dm\//i.test(channel)) return { shareable: false, reason: "dm" };
158
+ if (/^email\//i.test(channel)) return { shareable: false, reason: "private-correspondence" };
159
+
160
+ const service = String(item.service || "").trim().toLowerCase();
161
+ if (!service) return { shareable: false, reason: "unknown-source" };
162
+ if (service !== "slack") return { shareable: false, reason: "non-channel-source" };
163
+
164
+ const channelId = String(item.channel_id || "").trim();
165
+ if (!channelId) return { shareable: false, reason: "unknown-channel" };
166
+ // Prefixes may only DENY (see the note above): `D` is Slack's one dependable
167
+ // prefix, and a `G` id is never a public channel.
168
+ if (/^D/.test(channelId)) return { shareable: false, reason: "dm" };
169
+ if (/^G/.test(channelId)) return { shareable: false, reason: "private-channel" };
170
+
171
+ // Now the positive signal. Unrecognised types fail closed rather than falling
172
+ // through to the is_private check — a type we cannot interpret is not a type
173
+ // we may publish from.
174
+ const channelType = String(item.channel_type || "").trim().toLowerCase();
175
+ if (channelType) {
176
+ if (channelType === "im" || channelType === "mpim") return { shareable: false, reason: "dm" };
177
+ if (channelType === "group") return { shareable: false, reason: "private-channel" };
178
+ if (channelType !== "channel") return { shareable: false, reason: "unknown-channel-type" };
179
+ return { shareable: true, reason: "public-channel" };
180
+ }
181
+ if (item.is_private === false) return { shareable: true, reason: "public-channel" };
182
+
183
+ // No `channel_type`, no `is_private`: the id alone cannot establish privacy.
184
+ return { shareable: false, reason: "privacy-unknown" };
185
+ }
186
+
187
+ // ---------------------------------------------------------------------------
188
+ // Capture — pure parsing of the session's final text
189
+ // ---------------------------------------------------------------------------
190
+
191
+ /**
192
+ * The heading that introduces a turn's commitments.
193
+ *
194
+ * Generous about markdown decoration (`## `, `**…**`, trailing colon) because
195
+ * that is what a model actually emits — and STRICT about the word, which must be
196
+ * the literal `OUTCOMES` the prompt asked for.
197
+ *
198
+ * It used to also match "next steps" / "action items" / "action points" /
199
+ * "commitments". That made the capture surface WIDER THAN THE CONTRACT: a turn
200
+ * that merely wrote a prose "Next steps:" section — one whose prompt may never
201
+ * have carried OUTCOME_DISCIPLINE at all, because prompt-builder injects it only
202
+ * when the flag is already `1` at buildPrompt time while the sweep re-reads the
203
+ * flag at close time — would silently mint org-visible board rows it never
204
+ * agreed to. Capture is now exactly the block the prompt establishes.
205
+ *
206
+ * The PLURAL is required for the same reason: the contract is literally
207
+ * `OUTCOMES:`, while a singular "Outcome:" is an ordinary prose section header —
208
+ * accepting it would reopen the very gap this narrowing closed.
209
+ */
210
+ const OUTCOME_HEADING =
211
+ /^\s*(?:#{1,6}\s*)?(?:\*\*|__)?\s*outcomes\s*(?:\*\*|__)?\s*(?::\s*(.*))?\s*$/i;
212
+
213
+ /** A markdown list item: `- x`, `* x`, `1. x`, `- [ ] x`. */
214
+ const LIST_ITEM = /^\s*(?:[-*+•]|\d+[.)])\s+(?:\[[ xX]?\]\s*)?(.+)$/;
215
+
216
+ /** "nothing to land" answers, so an honest empty block stays empty. */
217
+ const EMPTY_ANSWER = /^(?:none|n\/?a|nothing|no (?:outcomes?|next steps?|action items?|commitments?))\b/i;
218
+
219
+ /** Strip markdown emphasis/code fences from a captured title. */
220
+ function plain(s) {
221
+ return String(s || "")
222
+ .replace(/[`*_]+/g, "")
223
+ .replace(/\s+/g, " ")
224
+ .trim();
225
+ }
226
+
227
+ /**
228
+ * Split a captured line into its title and any owner/timing the session stated.
229
+ * Segments are separated by an em/en dash, `--`, `|` or `;` and tagged
230
+ * `owner:`/`by:` and `due:`/`when:`. Untagged trailing segments stay in the title
231
+ * — guessing would put the wrong name on someone's task.
232
+ */
233
+ function splitParts(line) {
234
+ const segments = String(line).split(/\s+(?:—|–|--|\||;)\s+/);
235
+ let title = segments.shift() || "";
236
+ let owner;
237
+ let due;
238
+ const kept = [];
239
+ for (const seg of segments) {
240
+ const m = /^\s*(owner|assignee|by|due|when|timing)\s*:\s*(.+)$/i.exec(seg);
241
+ if (!m) {
242
+ kept.push(seg);
243
+ continue;
244
+ }
245
+ const tag = m[1].toLowerCase();
246
+ const value = plain(m[2]);
247
+ if (!value) continue;
248
+ if ((tag === "owner" || tag === "assignee" || tag === "by") && !owner) owner = value;
249
+ else if (!due) due = value;
250
+ }
251
+ if (kept.length) title = [title, ...kept].join(" — ");
252
+ return { title: plain(title), owner, due };
253
+ }
254
+
255
+ /**
256
+ * Extract this turn's commitments from its final text. Pure, bounded, and
257
+ * order-preserving; returns `[]` when the turn committed to nothing (which is a
258
+ * perfectly good answer — most turns should).
259
+ *
260
+ * @param {string} text the session's final assistant text
261
+ * @returns {Array<{title:string, owner?:string, due?:string}>}
262
+ */
263
+ export function parseOutcomes(text) {
264
+ const raw = String(text || "");
265
+ if (!raw.trim()) return [];
266
+ const body = raw.length > MAX_SCAN_CHARS ? raw.slice(-MAX_SCAN_CHARS) : raw;
267
+ const lines = body.split(/\r?\n/);
268
+
269
+ const out = [];
270
+ const seen = new Set();
271
+
272
+ const push = (line) => {
273
+ if (out.length >= MAX_OUTCOMES) return;
274
+ const { title, owner, due } = splitParts(line);
275
+ if (!title || EMPTY_ANSWER.test(title)) return;
276
+ const key = title.toLowerCase();
277
+ if (seen.has(key)) return;
278
+ seen.add(key);
279
+ const entry = { title: title.slice(0, MAX_TITLE_CHARS) };
280
+ if (owner) entry.owner = owner.slice(0, MAX_PARTY_CHARS);
281
+ if (due) entry.due = due.slice(0, MAX_PARTY_CHARS);
282
+ out.push(entry);
283
+ };
284
+
285
+ for (let i = 0; i < lines.length && out.length < MAX_OUTCOMES; i += 1) {
286
+ const heading = OUTCOME_HEADING.exec(lines[i]);
287
+ if (!heading) continue;
288
+
289
+ // `OUTCOMES: none` / `Next steps: ship the deck` — an inline single answer.
290
+ const inline = plain(heading[1] || "");
291
+ if (inline) {
292
+ if (!EMPTY_ANSWER.test(inline)) push(inline);
293
+ continue;
294
+ }
295
+
296
+ // Otherwise consume the list that follows. Blank lines are allowed BEFORE the
297
+ // first item (models like a blank after a heading) and TERMINATE the block
298
+ // once items have started, so the paragraph after the list is never swallowed.
299
+ let started = false;
300
+ for (let j = i + 1; j < lines.length && out.length < MAX_OUTCOMES; j += 1) {
301
+ const line = lines[j];
302
+ if (!line.trim()) {
303
+ if (started) { i = j; break; }
304
+ continue;
305
+ }
306
+ const item = LIST_ITEM.exec(line);
307
+ if (!item) { i = j - 1; break; }
308
+ started = true;
309
+ push(item[1]);
310
+ i = j;
311
+ }
312
+ }
313
+
314
+ return out;
315
+ }
316
+
317
+ /**
318
+ * Recover the final assistant text from a `claude --print --output-format json`
319
+ * stdout buffer. Tolerant of a leading banner line (same slice-the-first-balanced
320
+ * -object trick the dispatcher's usage parser uses) and of a plain-text run.
321
+ * Never throws.
322
+ *
323
+ * @param {string} stdout
324
+ * @returns {string} the result text, or "" when nothing usable was found
325
+ */
326
+ export function resultTextFromStdout(stdout) {
327
+ const trimmed = String(stdout || "").trim();
328
+ if (!trimmed) return "";
329
+ let obj = null;
330
+ try {
331
+ obj = JSON.parse(trimmed);
332
+ } catch {
333
+ const start = trimmed.indexOf("{");
334
+ const end = trimmed.lastIndexOf("}");
335
+ if (start !== -1 && end > start) {
336
+ try { obj = JSON.parse(trimmed.slice(start, end + 1)); } catch { obj = null; }
337
+ }
338
+ }
339
+ if (obj && typeof obj === "object") {
340
+ if (typeof obj.result === "string") return obj.result;
341
+ if (obj.result && typeof obj.result.text === "string") return obj.result.text;
342
+ return "";
343
+ }
344
+ // Not JSON at all (a --print text run): the whole buffer is the reply.
345
+ return trimmed;
346
+ }
347
+
348
+ // ---------------------------------------------------------------------------
349
+ // Per-session state — the daemon's small echo of the chair's ChairState
350
+ // ---------------------------------------------------------------------------
351
+
352
+ /** sessionId -> { outcomes: Array, landed: boolean } */
353
+ const sessions = new Map();
354
+
355
+ /** Bound the map so a long-lived daemon cannot accumulate session state. */
356
+ const MAX_TRACKED_SESSIONS = 200;
357
+
358
+ /**
359
+ * Record commitments captured for a session (deduped, bounded, order-preserving).
360
+ * Safe to call repeatedly; a later capture merges into the earlier one.
361
+ *
362
+ * @param {string} sessionId
363
+ * @param {Array<{title:string, owner?:string, due?:string}>} outcomes
364
+ * @returns {Array} the session's captured outcomes after the merge
365
+ */
366
+ export function noteOutcomes(sessionId, outcomes) {
367
+ if (!sessionId) return [];
368
+ let s = sessions.get(sessionId);
369
+ if (!s) {
370
+ if (sessions.size >= MAX_TRACKED_SESSIONS) {
371
+ // Oldest-first eviction; insertion order is Map's iteration order.
372
+ const oldest = sessions.keys().next();
373
+ if (!oldest.done) sessions.delete(oldest.value);
374
+ }
375
+ s = { outcomes: [], landed: false };
376
+ sessions.set(sessionId, s);
377
+ }
378
+ for (const o of Array.isArray(outcomes) ? outcomes : []) {
379
+ const title = plain(o && o.title).slice(0, MAX_TITLE_CHARS);
380
+ if (!title) continue;
381
+ if (s.outcomes.some((x) => x.title.toLowerCase() === title.toLowerCase())) continue;
382
+ if (s.outcomes.length >= MAX_OUTCOMES) break;
383
+ const entry = { title };
384
+ if (o.owner) entry.owner = String(o.owner).slice(0, MAX_PARTY_CHARS);
385
+ if (o.due) entry.due = String(o.due).slice(0, MAX_PARTY_CHARS);
386
+ s.outcomes.push(entry);
387
+ }
388
+ return s.outcomes;
389
+ }
390
+
391
+ /** What a session captured, or `[]`. */
392
+ export function getSessionOutcomes(sessionId) {
393
+ const s = sessions.get(sessionId);
394
+ return s ? s.outcomes : [];
395
+ }
396
+
397
+ /** Have this session's outcomes already been landed? */
398
+ export function outcomesLanded(sessionId) {
399
+ const s = sessions.get(sessionId);
400
+ return !!(s && s.landed);
401
+ }
402
+
403
+ /** Mark a session's outcomes landed (the in-process half of idempotency). */
404
+ export function markOutcomesLanded(sessionId) {
405
+ const s = sessions.get(sessionId);
406
+ if (s) s.landed = true;
407
+ }
408
+
409
+ /** Drop a session's state (called after the sweep). */
410
+ export function clearSessionOutcomes(sessionId) {
411
+ sessions.delete(sessionId);
412
+ }
413
+
414
+ /** Test seam. */
415
+ export function _resetSessionOutcomes() {
416
+ sessions.clear();
417
+ }
418
+
419
+ // ---------------------------------------------------------------------------
420
+ // Land — through the EXISTING org protocol (board.create + knowledge.append)
421
+ // ---------------------------------------------------------------------------
422
+
423
+ /**
424
+ * A stable, replay-safe board id for one commitment of one session. `board.create`
425
+ * treats a caller-supplied id that already exists as an idempotent replay, so this
426
+ * is what makes re-landing safe across a daemon restart (where the in-process
427
+ * `landed` flag is gone).
428
+ *
429
+ * @param {string} sessionId
430
+ * @param {string} title
431
+ * @returns {string}
432
+ */
433
+ export function outcomeItemId(sessionId, title) {
434
+ const norm = plain(title).toLowerCase();
435
+ const digest = createHash("sha1")
436
+ .update(`${String(sessionId || "")}${norm}`)
437
+ .digest("hex")
438
+ .slice(0, 24);
439
+ return `so-${digest}`;
440
+ }
441
+
442
+ /** The provenance line that lets a board row be traced back to its turn. */
443
+ function detailFor(o, a) {
444
+ const bits = [];
445
+ if (o.owner) bits.push(`Owner as stated: ${o.owner}.`);
446
+ if (o.due) bits.push(`Timing as stated: ${o.due}.`);
447
+ bits.push(
448
+ `Committed in ${a.who} daemon session ${a.sessionId || "unknown"}${a.topic ? ` — ${a.topic}` : ""}.`
449
+ );
450
+ return bits.join(" ").slice(0, 5000);
451
+ }
452
+
453
+ /**
454
+ * Write a turn's commitments through the org protocol: one `board.create` per
455
+ * commitment plus ONE `knowledge.append` episode recording the set (so the outcome
456
+ * is both actionable and recallable). Fail-open — every failure path returns a
457
+ * benign result rather than throwing.
458
+ *
459
+ * @param {object} a
460
+ * @param {string} a.sessionId
461
+ * @param {Array<{title:string, owner?:string, due?:string}>} a.outcomes
462
+ * @param {object} [a.cfg] agent org config (config/org.yaml shape)
463
+ * @param {object} [a.item] the inbox/queue item the turn was about. REQUIRED for
464
+ * anything to land: the confidentiality gate is fail-closed, so an absent or
465
+ * non-public source writes nothing (see `outcomeSourceShareable`).
466
+ * @param {object} [a.classResult] classifier output (for the topic line)
467
+ * @param {string} [a.agentName] the agent's own name, for the episode text
468
+ * @param {Function} [a.createItemImpl] injectable board.create
469
+ * @param {Function} [a.rememberImpl] injectable knowledge.append
470
+ * @param {Function} [a.fetchImpl] injectable fetch, forwarded to the client
471
+ * @returns {Promise<{created:number, remembered?:boolean, skipped?:boolean, reason?:string}>}
472
+ */
473
+ export async function landOutcomes(a = {}) {
474
+ try {
475
+ const cfg = a.cfg || {};
476
+ if (!orgEnabled(cfg)) return { created: 0, skipped: true, reason: "org-disabled" };
477
+
478
+ // The confidentiality gate sits on the WRITER, not just the sweep, so every
479
+ // path into the org protocol is covered. Fail-closed by construction.
480
+ const gate = outcomeSourceShareable(a.item);
481
+ if (!gate.shareable) return { created: 0, skipped: true, reason: `source-${gate.reason}` };
482
+
483
+ const wanted = (Array.isArray(a.outcomes) ? a.outcomes : [])
484
+ .filter((o) => o && plain(o.title))
485
+ .slice(0, MAX_OUTCOMES);
486
+ if (wanted.length === 0) return { created: 0, skipped: true, reason: "nothing-to-land" };
487
+
488
+ const conn = configFromAgent(cfg);
489
+ const createImpl = a.createItemImpl || orgCreateItem;
490
+ const rememberImpl = a.rememberImpl || orgRemember;
491
+ const who = a.agentName || "the agent";
492
+ const topic =
493
+ (a.classResult && a.classResult.summary) ||
494
+ (a.item && (a.item.subject || a.item.title)) ||
495
+ null;
496
+
497
+ let created = 0;
498
+ const landedTitles = [];
499
+ for (const o of wanted) {
500
+ const title = plain(o.title).slice(0, MAX_TITLE_CHARS);
501
+ const itemId = outcomeItemId(a.sessionId, title);
502
+ try {
503
+ // board.create is `.strict()` server-side — send only its own fields.
504
+ const res = await createImpl(
505
+ { itemId, title, detail: detailFor(o, { who, sessionId: a.sessionId, topic }), priority: "P2" },
506
+ { base: conn.base, token: conn.token, fetchImpl: a.fetchImpl, idempotencyKey: `board.create:${itemId}` }
507
+ );
508
+ // The client never throws; a failure comes back as an error frame.
509
+ if (!res || res.error) continue;
510
+ created += 1;
511
+ landedTitles.push(title);
512
+ } catch {
513
+ // One bad row must not strand the rest.
514
+ }
515
+ }
516
+
517
+ // One episode for the whole set, so the commitments are recallable even if the
518
+ // board is unreachable. The SERVER derives the fact's group/ACL from the
519
+ // caller's verified identity — we pass no group hint.
520
+ let remembered = false;
521
+ const forEpisode = landedTitles.length ? landedTitles : wanted.map((o) => plain(o.title));
522
+ try {
523
+ const text = [
524
+ `${who} committed to the following in a daemon session${topic ? ` re: ${topic}` : ""}:`,
525
+ ...forEpisode.map((t) => `- ${t}`),
526
+ ]
527
+ .join(" ")
528
+ .slice(0, 1200);
529
+ const r = await rememberImpl(text, {
530
+ cfg,
531
+ kind: "episode",
532
+ provenance: { source: "daemon-session-outcomes", sessionId: a.sessionId || null },
533
+ idempotencyKey: `knowledge.append:outcomes:${a.sessionId || "unknown"}`,
534
+ fetchImpl: a.fetchImpl,
535
+ });
536
+ remembered = !!(r && r.ok);
537
+ } catch {
538
+ // Fail-open.
539
+ }
540
+
541
+ if (created > 0 || remembered) markOutcomesLanded(a.sessionId);
542
+ return { created, remembered };
543
+ } catch {
544
+ return { created: 0, skipped: true };
545
+ }
546
+ }
547
+
548
+ // ---------------------------------------------------------------------------
549
+ // Sweep — the post-session hook
550
+ // ---------------------------------------------------------------------------
551
+
552
+ /**
553
+ * Post-session hook: capture this turn's commitments and LAND them. This is the
554
+ * daemon's answer to hq's `sweepUnlandedOutcomes` — the guarantee is DRIVEN by
555
+ * the close path rather than by the model remembering to write anything down.
556
+ *
557
+ * No-op when the flag is off, when the SOURCE IS NOT ORG-VISIBLE (a DM, a private
558
+ * channel, email, or a source we cannot establish as public — see
559
+ * `outcomeSourceShareable`), when the org integration is disabled, when nothing
560
+ * was committed, or when this session already landed. Never throws.
561
+ *
562
+ * @param {object} a
563
+ * @param {string} a.sessionId
564
+ * @param {string} [a.stdout] the session's raw stdout (--output-format json)
565
+ * @param {string} [a.resultText] pre-extracted final text (wins over stdout)
566
+ * @param {object} [a.item] the inbox item; REQUIRED — the fail-closed
567
+ * confidentiality gate lands nothing without a demonstrably public source
568
+ * @param {object} [a.classResult]
569
+ * @param {object} [a.cfg]
570
+ * @param {boolean} [a.enabled] override the env flag (tests)
571
+ * @param {string} [a.agentName]
572
+ * @param {Function} [a.createItemImpl]
573
+ * @param {Function} [a.rememberImpl]
574
+ * @param {Function} [a.fetchImpl]
575
+ * @returns {Promise<{created:number, captured?:number, skipped?:boolean, reason?:string}>}
576
+ */
577
+ export async function sweepSessionOutcomes(a = {}) {
578
+ try {
579
+ const enabled = a.enabled !== undefined ? a.enabled : outcomesEnabled();
580
+ if (!enabled) return { created: 0, skipped: true, reason: "disabled" };
581
+ if (!a.sessionId) return { created: 0, skipped: true, reason: "no-session" };
582
+
583
+ // CONFIDENTIALITY: a landed outcome is org-visible, so only an already
584
+ // org-visible source may produce one. Checked BEFORE the text is even parsed
585
+ // — a private turn's commitments are never captured, let alone written.
586
+ const gate = outcomeSourceShareable(a.item);
587
+ if (!gate.shareable) {
588
+ clearSessionOutcomes(a.sessionId);
589
+ return { created: 0, captured: 0, skipped: true, reason: `source-${gate.reason}` };
590
+ }
591
+
592
+ if (outcomesLanded(a.sessionId)) {
593
+ clearSessionOutcomes(a.sessionId);
594
+ return { created: 0, skipped: true, reason: "already-landed" };
595
+ }
596
+
597
+ const text =
598
+ typeof a.resultText === "string" && a.resultText
599
+ ? a.resultText
600
+ : resultTextFromStdout(a.stdout);
601
+ const parsed = parseOutcomes(text);
602
+ const captured = noteOutcomes(a.sessionId, parsed);
603
+ if (captured.length === 0) {
604
+ clearSessionOutcomes(a.sessionId);
605
+ return { created: 0, captured: 0, skipped: true, reason: "nothing-captured" };
606
+ }
607
+
608
+ const res = await landOutcomes({
609
+ sessionId: a.sessionId,
610
+ outcomes: captured,
611
+ cfg: a.cfg,
612
+ item: a.item,
613
+ classResult: a.classResult,
614
+ agentName: a.agentName,
615
+ createItemImpl: a.createItemImpl,
616
+ rememberImpl: a.rememberImpl,
617
+ fetchImpl: a.fetchImpl,
618
+ });
619
+ clearSessionOutcomes(a.sessionId);
620
+ return { ...res, captured: captured.length };
621
+ } catch {
622
+ return { created: 0, skipped: true, reason: "error" };
623
+ }
624
+ }
625
+
626
+ export default {
627
+ MAX_OUTCOMES,
628
+ outcomesEnabled,
629
+ outcomeSourceShareable,
630
+ parseOutcomes,
631
+ resultTextFromStdout,
632
+ noteOutcomes,
633
+ getSessionOutcomes,
634
+ outcomesLanded,
635
+ markOutcomesLanded,
636
+ clearSessionOutcomes,
637
+ outcomeItemId,
638
+ landOutcomes,
639
+ sweepSessionOutcomes,
640
+ };