@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,192 @@
1
+ // Gmail Poller — polls the agent's inbox via native Node.js IMAP.
2
+ // Uses imapflow + mailparser to fetch unseen emails from the agent's
3
+ // configured email address (config/agent.json#/email) and write them as
4
+ // .json files to state/inbox/gmail/.
5
+ //
6
+ // All IMAP logic is in imap-client.mjs, shared with the principal poller.
7
+
8
+ import { readFileSync, readdirSync, renameSync } from "fs";
9
+ import { join } from "path";
10
+ import { readCursor, writeCursor, AGENT_REPO_DIR } from "./utils.mjs";
11
+ import { pollImapInbox } from "./imap-client.mjs";
12
+
13
+ const GMAIL_INBOX_DIR = join(AGENT_REPO_DIR, "state", "inbox", "gmail");
14
+ const MAX_ITEMS_PER_POLL = 10;
15
+
16
+ // Load agent identity once. Email + first-name used as account labels.
17
+ let _agent = null;
18
+ function loadAgent() {
19
+ if (_agent) return _agent;
20
+ try {
21
+ _agent = JSON.parse(readFileSync(join(AGENT_REPO_DIR, "config/agent.json"), "utf-8"));
22
+ } catch {
23
+ _agent = { firstName: "agent", email: "" };
24
+ }
25
+ return _agent;
26
+ }
27
+
28
+ /**
29
+ * Load the agent's Gmail app password from env or the agent's .env file.
30
+ */
31
+ function loadGmailPassword() {
32
+ if (process.env.GMAIL_APP_PASSWORD) return process.env.GMAIL_APP_PASSWORD;
33
+ try {
34
+ const envFile = readFileSync(join(AGENT_REPO_DIR, ".env"), "utf-8");
35
+ for (const line of envFile.split("\n")) {
36
+ if (line.startsWith("GMAIL_APP_PASSWORD=")) {
37
+ let val = line.split("=", 2)[1].trim();
38
+ if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
39
+ val = val.slice(1, -1);
40
+ }
41
+ return val;
42
+ }
43
+ }
44
+ } catch { /* no .env file */ }
45
+ return "";
46
+ }
47
+
48
+ /**
49
+ * Extract sender name from email "From" header.
50
+ */
51
+ function extractSenderName(from) {
52
+ const match = from.match(/^"?([^"<]+)"?\s*</);
53
+ return match ? match[1].trim() : from.trim();
54
+ }
55
+
56
+ /**
57
+ * Extract sender email from "From" header.
58
+ */
59
+ function extractSenderEmail(from) {
60
+ const match = from.match(/<([^>]+)>/);
61
+ return match ? match[1].trim() : from.trim();
62
+ }
63
+
64
+ /**
65
+ * Determine sender privilege from email address. The principal (CEO) is
66
+ * recognised by matching against the agent's `principal.email` or
67
+ * `principal.firstName` from config/agent.json — never hardcode names.
68
+ *
69
+ * INTERNAL classification comes from CONFIG, never a hardcoded domain: the
70
+ * optional `internal_domains[]` array in config/agent.json, defaulting to the
71
+ * domain of the agent's own `email` there. Exact domain match (not substring)
72
+ * so `evil-corp.example.attacker.net` never rides an `example` allowance.
73
+ */
74
+ function internalDomains(me) {
75
+ const configured = Array.isArray(me.internal_domains)
76
+ ? me.internal_domains.map((d) => String(d).toLowerCase().replace(/^@/, "")).filter(Boolean)
77
+ : [];
78
+ if (configured.length) return configured;
79
+ const own = String(me.email || "");
80
+ const at = own.lastIndexOf("@");
81
+ return at > 0 ? [own.slice(at + 1).toLowerCase()] : [];
82
+ }
83
+
84
+ function resolveSenderPrivilege(email) {
85
+ const lower = email.toLowerCase();
86
+ const me = loadAgent();
87
+ const principalEmail = (me.principal?.email || "").toLowerCase();
88
+ const principalFirst = (me.principal?.firstName || "").toLowerCase();
89
+ if (principalEmail && lower === principalEmail) return "ceo";
90
+ if (principalFirst && lower.includes(principalFirst)) return "ceo";
91
+ const domain = lower.includes("@") ? lower.slice(lower.lastIndexOf("@") + 1) : "";
92
+ if (domain && internalDomains(me).includes(domain)) return "internal";
93
+ return "external";
94
+ }
95
+
96
+ export async function pollGmail() {
97
+ const items = [];
98
+ const errors = [];
99
+
100
+ // Run native IMAP poll for the agent's configured email address.
101
+ const me = loadAgent();
102
+ const agentSlug = (me.firstName || "agent").toLowerCase();
103
+ try {
104
+ const result = await pollImapInbox({
105
+ email: me.email,
106
+ password: loadGmailPassword(),
107
+ account: agentSlug,
108
+ fileSuffix: "email",
109
+ eventType: `${agentSlug}_email`,
110
+ logPrefix: `[${agentSlug}-gmail]`,
111
+ });
112
+ if (result.errors.length > 0) {
113
+ errors.push(...result.errors);
114
+ }
115
+ } catch (err) {
116
+ const msg = `Agent Gmail IMAP poll failed: ${err.message}`;
117
+ console.error(`[gmail] ${msg}`);
118
+ errors.push(msg);
119
+ }
120
+
121
+ // Read resulting JSON files (same as before — the IMAP client writes them)
122
+ try {
123
+ const files = readdirSync(GMAIL_INBOX_DIR)
124
+ .filter((f) => f.endsWith("-email.json") && !f.endsWith(".processed"))
125
+ .sort()
126
+ .slice(0, MAX_ITEMS_PER_POLL);
127
+
128
+ for (const file of files) {
129
+ try {
130
+ const raw = readFileSync(join(GMAIL_INBOX_DIR, file), "utf-8");
131
+ const data = JSON.parse(raw);
132
+ const email = data.email || {};
133
+ const senderEmail = extractSenderEmail(email.from || "unknown");
134
+ const senderName = extractSenderName(email.from || "unknown");
135
+ const privilege = resolveSenderPrivilege(senderEmail);
136
+ const id = file.replace(/-email\.json$/, "");
137
+
138
+ // Pass through attachment metadata
139
+ const attachments = data.attachments
140
+ ? data.attachments.map((a) => ({
141
+ name: a.name || "unnamed",
142
+ mimetype: a.mimetype || "unknown",
143
+ size: a.size || 0,
144
+ local_path: a.local_path || null,
145
+ }))
146
+ : undefined;
147
+
148
+ items.push({
149
+ id,
150
+ service: "gmail",
151
+ channel: `email/${senderEmail}`,
152
+ sender: senderName,
153
+ sender_email: senderEmail,
154
+ sender_privilege: privilege,
155
+ timestamp: data.received_at || email.date || new Date().toISOString(),
156
+ subject: (email.subject || "").replace(/\r\n/g, " ").trim(),
157
+ content: `Email from ${email.from || "unknown"}\nSubject: ${email.subject || "(no subject)"}\nDate: ${email.date || "unknown"}`,
158
+ thread_id: email.message_id || "",
159
+ is_reply: (email.subject || "").toLowerCase().startsWith("re:"),
160
+ attachments,
161
+ priority_signals: {
162
+ from_ceo: privilege === "ceo",
163
+ tagged_urgent: /urgent|asap|critical/i.test(email.subject || ""),
164
+ contains_deadline: /deadline|due|by\s+(monday|tuesday|wednesday|thursday|friday|tomorrow|end of)/i.test(email.subject || ""),
165
+ mentions_agent: new RegExp(agentSlug, "i").test(email.subject || ""),
166
+ },
167
+ raw_ref: `gmail:${id}`,
168
+ _inbox_file: file,
169
+ });
170
+
171
+ // Mark as processed after reading
172
+ try {
173
+ renameSync(
174
+ join(GMAIL_INBOX_DIR, file),
175
+ join(GMAIL_INBOX_DIR, file + ".processed"),
176
+ );
177
+ } catch {
178
+ // Already processed
179
+ }
180
+ } catch (err) {
181
+ errors.push(`Gmail file ${file}: ${err.message}`);
182
+ }
183
+ }
184
+ } catch (err) {
185
+ if (err.code !== "ENOENT") {
186
+ errors.push(`Gmail inbox scan: ${err.message}`);
187
+ }
188
+ }
189
+
190
+ writeCursor("gmail", new Date().toISOString());
191
+ return { items, errors };
192
+ }
@@ -0,0 +1,289 @@
1
+ // imap-client.mjs — Shared native IMAP polling for the agent's inbox and
2
+ // any other inbox the agent has explicit access to (e.g. principal-gmail).
3
+ // Replaces external Python scripts with direct Node.js IMAP via imapflow.
4
+ //
5
+ // Each inbox gets its own credentials and file-naming convention, but the
6
+ // connect → search UNSEEN → fetch headers+body → write JSON → disconnect
7
+ // flow is identical and lives here.
8
+
9
+ import { ImapFlow } from "imapflow";
10
+ import { writeFileSync, existsSync, readFileSync, mkdirSync } from "fs";
11
+ import { join, dirname } from "path";
12
+ import { createHash } from "crypto";
13
+ import { AGENT_REPO_DIR } from "./utils.mjs";
14
+
15
+ const INBOX_DIR = join(AGENT_REPO_DIR, "state", "inbox", "gmail");
16
+ const ATTACHMENTS_DIR = join(AGENT_REPO_DIR, "state", "inbox", "attachments");
17
+ const MAX_EMAILS_PER_CYCLE = 20;
18
+ const SNIPPET_LENGTH = 500;
19
+ const IMAP_TIMEOUT_MS = 25_000;
20
+
21
+ // ── Helpers ─────────────────────────────────────────────────────────────
22
+
23
+ function messageIdHash(messageId, fromAddr = "", subject = "", date = "") {
24
+ const key = messageId || `${fromAddr}${subject}${date}`;
25
+ return createHash("md5").update(key).digest("hex").slice(0, 16);
26
+ }
27
+
28
+ function stripHtml(html) {
29
+ let text = html.replace(/<(style|script)[^>]*>[\s\S]*?<\/\1>/gi, "");
30
+ text = text.replace(/<br\s*\/?>/gi, "\n");
31
+ text = text.replace(/<\/(p|div|tr|li)>/gi, "\n");
32
+ text = text.replace(/<[^>]+>/g, "");
33
+ text = text.replace(/&nbsp;/g, " ").replace(/&amp;/g, "&");
34
+ text = text.replace(/&lt;/g, "<").replace(/&gt;/g, ">");
35
+ text = text.replace(/&quot;/g, '"').replace(/&#39;/g, "'");
36
+ text = text.replace(/&\w+;/g, "");
37
+ text = text.replace(/[ \t]+/g, " ");
38
+ text = text.replace(/\n{3,}/g, "\n\n");
39
+ return text.trim();
40
+ }
41
+
42
+ function extractSnippet(textBody, htmlBody, maxLen = SNIPPET_LENGTH) {
43
+ let body = (textBody || "").trim();
44
+ if (!body && htmlBody) body = stripHtml(htmlBody);
45
+ return body.length > maxLen ? body.slice(0, maxLen) + "..." : body;
46
+ }
47
+
48
+ function isAlreadyProcessed(midHash, suffix) {
49
+ const baseName = `${midHash}-${suffix}.json`;
50
+ return (
51
+ existsSync(join(INBOX_DIR, baseName)) ||
52
+ existsSync(join(INBOX_DIR, baseName + ".processed"))
53
+ );
54
+ }
55
+
56
+ // ── Cursor Management ───────────────────────────────────────────────────
57
+
58
+ function cursorPath(account) {
59
+ return join(AGENT_REPO_DIR, "state", "polling", `${account}-gmail-cursor.yaml`);
60
+ }
61
+
62
+ function readImapCursor(account) {
63
+ const path = cursorPath(account);
64
+ try {
65
+ const content = readFileSync(path, "utf-8");
66
+ const cursor = {};
67
+ for (const line of content.split("\n")) {
68
+ const m = line.match(/^(\w+):\s*"?([^"\n]*)"?/);
69
+ if (m) {
70
+ let val = m[2].trim();
71
+ if (val === "null" || val === "") val = null;
72
+ cursor[m[1]] = val;
73
+ }
74
+ }
75
+ return {
76
+ last_poll: cursor.last_poll || null,
77
+ last_message_id: cursor.last_message_id || null,
78
+ messages_processed: parseInt(cursor.messages_processed || "0", 10) || 0,
79
+ };
80
+ } catch {
81
+ return { last_poll: null, last_message_id: null, messages_processed: 0 };
82
+ }
83
+ }
84
+
85
+ function writeImapCursor(account, lastPoll, lastMessageId, messagesProcessed) {
86
+ const path = cursorPath(account);
87
+ mkdirSync(dirname(path), { recursive: true });
88
+ const lp = lastPoll ? `"${lastPoll}"` : "null";
89
+ const lm = lastMessageId ? `"${lastMessageId}"` : "null";
90
+ writeFileSync(
91
+ path,
92
+ `last_poll: ${lp}\nlast_message_id: ${lm}\nmessages_processed: ${messagesProcessed}\n`,
93
+ );
94
+ }
95
+
96
+ // ── Attachment Extraction ───────────────────────────────────────────────
97
+
98
+ function saveAttachments(parsed, midHash) {
99
+ const attachments = [];
100
+ if (!parsed.attachments || parsed.attachments.length === 0) return attachments;
101
+
102
+ mkdirSync(ATTACHMENTS_DIR, { recursive: true });
103
+ for (const att of parsed.attachments) {
104
+ if (!att.filename) continue;
105
+ // Skip tiny inline images (likely signatures)
106
+ if ((att.contentType || "").startsWith("image/") && att.content && att.content.length < 1024) continue;
107
+
108
+ const safeName = att.filename.replace(/[^a-zA-Z0-9._-]/g, "_");
109
+ const localName = `${midHash}-${safeName}`;
110
+ const localPath = join(ATTACHMENTS_DIR, localName);
111
+ try {
112
+ writeFileSync(localPath, att.content);
113
+ attachments.push({
114
+ name: att.filename,
115
+ mimetype: att.contentType || "unknown",
116
+ size: att.content.length,
117
+ local_path: localPath,
118
+ });
119
+ } catch {
120
+ // Non-fatal
121
+ }
122
+ }
123
+ return attachments;
124
+ }
125
+
126
+ // ── Core IMAP Poll ──────────────────────────────────────────────────────
127
+
128
+ /**
129
+ * Poll a Gmail inbox via IMAP and write new messages as JSON to the inbox dir.
130
+ *
131
+ * @param {object} opts
132
+ * @param {string} opts.email — Email address to poll
133
+ * @param {string} opts.password — Gmail app password
134
+ * @param {string} opts.account — Account slug for cursor/file naming (e.g. agent's first name)
135
+ * @param {string} opts.fileSuffix — JSON file suffix
136
+ * @param {string} opts.eventType — Event type tag
137
+ * @param {string} opts.logPrefix — Console log prefix
138
+ * @returns {Promise<{newCount: number, errors: string[]}>}
139
+ */
140
+ export async function pollImapInbox({
141
+ email: emailAddr,
142
+ password,
143
+ account,
144
+ fileSuffix,
145
+ eventType,
146
+ logPrefix,
147
+ }) {
148
+ const errors = [];
149
+
150
+ if (!password) {
151
+ console.log(`${logPrefix} App password not set, skipping`);
152
+ return { newCount: 0, errors };
153
+ }
154
+
155
+ mkdirSync(INBOX_DIR, { recursive: true });
156
+
157
+ const cursor = readImapCursor(account);
158
+ const now = new Date().toISOString();
159
+ let newCount = 0;
160
+ let skipped = 0;
161
+ let unseenCount = 0;
162
+ let lastMid = cursor.last_message_id;
163
+ let totalProcessed = cursor.messages_processed;
164
+
165
+ const client = new ImapFlow({
166
+ host: "imap.gmail.com",
167
+ port: 993,
168
+ secure: true,
169
+ auth: { user: emailAddr, pass: password },
170
+ logger: false, // Suppress imapflow debug logs
171
+ socketTimeout: IMAP_TIMEOUT_MS,
172
+ greetTimeout: IMAP_TIMEOUT_MS,
173
+ });
174
+
175
+ try {
176
+ await client.connect();
177
+ const lock = await client.getMailboxLock("INBOX");
178
+
179
+ try {
180
+ // Search for unseen messages
181
+ const unseen = await client.search({ seen: false });
182
+ if (!unseen || unseen.length === 0) {
183
+ console.log(`${logPrefix} No unseen messages`);
184
+ writeImapCursor(account, now, lastMid, totalProcessed);
185
+ lock.release();
186
+ await client.logout();
187
+ return { newCount: 0, errors };
188
+ }
189
+
190
+ unseenCount = unseen.length;
191
+ console.log(`${logPrefix} Found ${unseenCount} unseen messages`);
192
+
193
+ // Rate limit: process most recent N
194
+ const toFetch = unseen.length > MAX_EMAILS_PER_CYCLE
195
+ ? unseen.slice(-MAX_EMAILS_PER_CYCLE)
196
+ : unseen;
197
+
198
+ if (unseen.length > MAX_EMAILS_PER_CYCLE) {
199
+ console.log(`${logPrefix} Rate limited to last ${MAX_EMAILS_PER_CYCLE}`);
200
+ }
201
+
202
+ // Fetch messages using PEEK (don't mark as read)
203
+ for (const seq of toFetch) {
204
+ try {
205
+ const msg = await client.fetchOne(seq, {
206
+ envelope: true,
207
+ bodyStructure: true,
208
+ source: true, // Full raw source for body/attachment extraction
209
+ }, { uid: false });
210
+
211
+ if (!msg || !msg.envelope) continue;
212
+
213
+ const env = msg.envelope;
214
+ const fromAddr = env.from?.[0]?.address || "";
215
+ const fromName = env.from?.[0]?.name || fromAddr;
216
+ const subject = env.subject || "";
217
+ const dateStr = env.date ? env.date.toISOString() : "";
218
+ const messageId = env.messageId || "";
219
+
220
+ const midHash = messageIdHash(messageId, fromAddr, subject, dateStr);
221
+
222
+ if (isAlreadyProcessed(midHash, fileSuffix)) {
223
+ skipped++;
224
+ continue;
225
+ }
226
+
227
+ // Parse the full message for body and attachments
228
+ let snippet = "";
229
+ let emailAttachments = [];
230
+ if (msg.source) {
231
+ try {
232
+ const { simpleParser } = await import("mailparser");
233
+ const parsed = await simpleParser(msg.source);
234
+ snippet = extractSnippet(parsed.text, parsed.html);
235
+ emailAttachments = saveAttachments(parsed, midHash);
236
+ } catch {
237
+ // Fall back to envelope-only data
238
+ snippet = "";
239
+ }
240
+ }
241
+
242
+ const record = {
243
+ received_at: now,
244
+ event_type: eventType,
245
+ source: `${account}_gmail_poll`,
246
+ email: {
247
+ from: `${fromName} <${fromAddr}>`,
248
+ from_name: fromName,
249
+ from_addr: fromAddr,
250
+ to: emailAddr,
251
+ subject,
252
+ date: dateStr,
253
+ message_id: messageId,
254
+ snippet,
255
+ },
256
+ attachments: emailAttachments.length > 0 ? emailAttachments : null,
257
+ triage_hint: null,
258
+ };
259
+
260
+ const filepath = join(INBOX_DIR, `${midHash}-${fileSuffix}.json`);
261
+ writeFileSync(filepath, JSON.stringify(record, null, 2));
262
+
263
+ newCount++;
264
+ lastMid = messageId;
265
+ console.log(`${logPrefix} New: ${fromName} <${fromAddr}> — ${subject.slice(0, 60)}`);
266
+ } catch (err) {
267
+ errors.push(`${logPrefix} Message fetch error: ${err.message}`);
268
+ }
269
+ }
270
+ } finally {
271
+ lock.release();
272
+ }
273
+
274
+ totalProcessed += newCount;
275
+ writeImapCursor(account, now, lastMid, totalProcessed);
276
+ await client.logout();
277
+
278
+ console.log(
279
+ `${logPrefix} Poll complete: ${newCount} new, ${skipped} skipped, ${unseenCount} unseen total`,
280
+ );
281
+ } catch (err) {
282
+ errors.push(`${logPrefix} IMAP error: ${err.message}`);
283
+ console.error(`${logPrefix} IMAP error: ${err.message}`);
284
+ writeImapCursor(account, now, lastMid, totalProcessed);
285
+ try { await client.logout(); } catch { /* already disconnected */ }
286
+ }
287
+
288
+ return { newCount, errors };
289
+ }
@@ -0,0 +1,156 @@
1
+ /**
2
+ * inbox-scan-poller.mjs — generic poller for channel-bus inbox items.
3
+ *
4
+ * WS2 channels (Telegram, Baileys WhatsApp, …) deliver events through the
5
+ * daemon's channel bus, which serializes each to a byte-compatible inbox item
6
+ * via `eventToInboxItem` → `writeInboxItem("<service>", item)` under
7
+ * `state/inbox/<service>/*.yaml`. Unlike Slack/Gmail/Calendar/Voice, these
8
+ * channels have no dedicated API poller — so this scanner reads the YAML items
9
+ * back into the in-memory `{ items, errors }` shape the daemon's `poll()` loop
10
+ * already consumes, and the rest of the pipeline (classify → directed-gate →
11
+ * locks → dispatch) runs unchanged.
12
+ *
13
+ * The scanner is the inverse of `inboxItemToYaml` (scripts/poller/utils.mjs):
14
+ * it parses only the fields the daemon needs and ignores the rest. Items that
15
+ * have already been processed/deferred (renamed by the daemon) are skipped via
16
+ * the same sibling-file convention `writeInboxItem` uses, so the scanner never
17
+ * re-emits a handled item.
18
+ *
19
+ * Durable admission (F1 / H2): an item admitted-but-not-yet-closed is parked at
20
+ * `<name>.dispatched` (see `markDispatched` in utils.mjs). The scanner skips a
21
+ * fresh `.dispatched` file (no double-delivery on the happy path) but RECLAIMS a
22
+ * stale one (older than the reclaim window) — so a crash/restart that loses the
23
+ * daemon's in-memory queue still re-delivers the user's message instead of
24
+ * silently dropping it. `markInboxProcessed` retires it to `.processed` on
25
+ * success, which is terminal and never reclaimed.
26
+ *
27
+ * @module scripts/poller/inbox-scan-poller
28
+ */
29
+
30
+ import { readdirSync, readFileSync, existsSync, statSync } from "node:fs";
31
+ import { join } from "node:path";
32
+ import { AGENT_REPO_DIR } from "./utils.mjs";
33
+
34
+ // Reclaim window for in-flight `.dispatched` items (F1 / H2 durable admission).
35
+ // An item parked at `.dispatched` while its session is in-flight must NOT be
36
+ // re-delivered on the happy path, but a crash/restart that loses the in-memory
37
+ // queue must leave it re-deliverable. We reclaim a `.dispatched` file only once
38
+ // it is older than this window — 2h matches the daemon's STALE_LOCK_MS and
39
+ // comfortably exceeds every inbox session timeout, so a still-running session
40
+ // is never double-delivered while a crashed one is recovered within one window.
41
+ const DEFAULT_RECLAIM_MS = 2 * 60 * 60 * 1000;
42
+
43
+ /**
44
+ * Parse a single inbox YAML body into the daemon's item shape. Tolerant of
45
+ * missing optional fields. Pure.
46
+ *
47
+ * @param {string} body
48
+ * @returns {object} item
49
+ */
50
+ export function parseInboxItemYaml(body) {
51
+ const scalar = (key) => {
52
+ const m = body.match(new RegExp(`^${key}:\\s*"?([^"\\n]*)"?\\s*$`, "m"));
53
+ return m ? m[1] : "";
54
+ };
55
+ const bool = (key) => /true/i.test((body.match(new RegExp(`^${key}:\\s*(\\S+)`, "m")) || [])[1] || "");
56
+ const nestedBool = (key) => {
57
+ const m = body.match(new RegExp(`^\\s{2}${key}:\\s*(\\S+)`, "m"));
58
+ return /true/i.test((m || [])[1] || "");
59
+ };
60
+
61
+ // Block scalar `content: |` — capture the indented block.
62
+ const blockScalar = (key) => {
63
+ const re = new RegExp(`^${key}:\\s*\\|\\s*\\n((?:\\s{2}.*\\n?)*)`, "m");
64
+ const m = body.match(re);
65
+ if (!m) return scalar(key);
66
+ return m[1].replace(/^ {2}/gm, "").replace(/\n+$/, "");
67
+ };
68
+
69
+ return {
70
+ id: scalar("id"),
71
+ service: scalar("service"),
72
+ channel: scalar("channel"),
73
+ channel_id: scalar("channel_id"),
74
+ sender: scalar("sender"),
75
+ sender_privilege: scalar("sender_privilege"),
76
+ timestamp: scalar("timestamp"),
77
+ subject: scalar("subject"),
78
+ content: blockScalar("content"),
79
+ thread_id: scalar("thread_id"),
80
+ thread_context: (() => { const tc = blockScalar("thread_context"); return tc || null; })(),
81
+ is_reply: bool("is_reply"),
82
+ kind: scalar("kind") || "message",
83
+ priority_signals: {
84
+ from_ceo: nestedBool("from_ceo"),
85
+ tagged_urgent: nestedBool("tagged_urgent"),
86
+ contains_deadline: nestedBool("contains_deadline"),
87
+ mentions_agent: nestedBool("mentions_agent"),
88
+ },
89
+ raw_ref: scalar("raw_ref"),
90
+ };
91
+ }
92
+
93
+ /**
94
+ * Scan `state/inbox/<service>/` for unprocessed `*.yaml` items, plus any stale
95
+ * `*.dispatched` items left in-flight by a crashed session (F1 / H2 reclaim).
96
+ *
97
+ * @param {string} service
98
+ * @param {object} [opts]
99
+ * @param {string} [opts.agentRoot]
100
+ * @param {number} [opts.reclaimMs] age (ms) after which a `.dispatched` item is
101
+ * considered crash-orphaned and re-delivered. Defaults to 2h (STALE_LOCK_MS).
102
+ * @param {number} [opts.now] injectable clock (epoch ms) for tests.
103
+ * @returns {{ items: object[], errors: string[] }}
104
+ */
105
+ export function scanInbox(service, opts = {}) {
106
+ const agentRoot = opts.agentRoot || AGENT_REPO_DIR;
107
+ const reclaimMs = typeof opts.reclaimMs === "number" ? opts.reclaimMs : DEFAULT_RECLAIM_MS;
108
+ const now = typeof opts.now === "number" ? opts.now : Date.now();
109
+ const dir = join(agentRoot, "state", "inbox", service);
110
+ const items = [];
111
+ const errors = [];
112
+ if (!existsSync(dir)) return { items, errors };
113
+
114
+ let files;
115
+ try {
116
+ files = readdirSync(dir);
117
+ } catch (err) {
118
+ return { items, errors: [`readdir ${service}: ${err.message}`] };
119
+ }
120
+
121
+ for (const f of files) {
122
+ // Live items always emit. A `.dispatched` item is in-flight: emit it only
123
+ // once it is stale (crash-orphaned) so the happy path never double-delivers.
124
+ const isLive = f.endsWith(".yaml");
125
+ const isDispatched = f.endsWith(".dispatched");
126
+ if (!isLive && !isDispatched) continue; // skip .processed/.deferred/.bundled
127
+ const path = join(dir, f);
128
+ if (isDispatched) {
129
+ let ageMs;
130
+ try {
131
+ // Clamp at 0: filesystem mtime can read a hair ahead of the captured
132
+ // clock, which would otherwise make a just-dispatched file look like a
133
+ // negative (i.e. "future") age and skew the reclaim comparison.
134
+ ageMs = Math.max(0, now - statSync(path).mtimeMs);
135
+ } catch (err) {
136
+ errors.push(`stat ${f}: ${err.message}`);
137
+ continue;
138
+ }
139
+ if (ageMs < reclaimMs) continue; // still in-flight — do not re-deliver
140
+ }
141
+ try {
142
+ const item = parseInboxItemYaml(readFileSync(path, "utf-8"));
143
+ if (item.id) items.push(item);
144
+ } catch (err) {
145
+ errors.push(`parse ${f}: ${err.message}`);
146
+ }
147
+ }
148
+ return { items, errors };
149
+ }
150
+
151
+ /** Build a bound poller for a service, matching the poller fn contract. */
152
+ export function makeInboxScanPoller(service, opts = {}) {
153
+ return async () => scanInbox(service, opts);
154
+ }
155
+
156
+ export default { scanInbox, parseInboxItemYaml, makeInboxScanPoller };