@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,352 @@
1
+ /**
2
+ * lib/learning/session-index.mjs — verbatim transcript FTS5 store (WS3).
3
+ *
4
+ * Complementary to collective memory cards: cards are the fast, budgeted gist
5
+ * injected at SessionStart; THIS is the on-demand, verbatim recall of the
6
+ * agent's own past sessions. A separate SQLite DB at state/learning/sessions.db
7
+ * (so it never collides with the RAG corpus index), mirroring the FTS5 usage in
8
+ * lib/rag/indexer.mjs.
9
+ *
10
+ * Schema:
11
+ * turns(body, session_id UNINDEXED, role UNINDEXED, ts UNINDEXED, seq UNINDEXED)
12
+ * — FTS5 virtual table, one row per transcript turn.
13
+ * sessions(session_id PK, file, mtime_ms, turn_count, first_ts, last_ts, indexed_at)
14
+ * — ingest bookkeeping (idempotent re-ingest via mtime).
15
+ *
16
+ * Search has four modes (ported from Hermes):
17
+ * discovery — bm25 ranked hits + snippet + a per-session BOOKEND (the first &
18
+ * last turn of each matched session, for orientation).
19
+ * scroll — a window of ±N turns around a given (session_id, seq).
20
+ * read — every turn of one session, in order.
21
+ * browse — the most-recent sessions (metadata), newest first.
22
+ *
23
+ * Reads are fail-open at the call boundary (a missing DB → empty results), but
24
+ * ingest surfaces hard FS/DB errors to its caller (the reflect worker swallows
25
+ * them) so a genuinely broken DB is debuggable.
26
+ *
27
+ * @module lib/learning/session-index
28
+ */
29
+
30
+ "use strict";
31
+
32
+ import { readFileSync, statSync, existsSync, readdirSync, mkdirSync } from "node:fs";
33
+ import { join, resolve, basename } from "node:path";
34
+
35
+ let _Database = null;
36
+ async function loadDatabase() {
37
+ if (_Database) return _Database;
38
+ const mod = await import("better-sqlite3");
39
+ _Database = mod.default || mod;
40
+ return _Database;
41
+ }
42
+
43
+ const DB_REL = "state/learning/sessions.db";
44
+
45
+ const SCHEMA = `
46
+ CREATE VIRTUAL TABLE IF NOT EXISTS turns USING fts5(
47
+ body,
48
+ session_id UNINDEXED,
49
+ role UNINDEXED,
50
+ ts UNINDEXED,
51
+ seq UNINDEXED,
52
+ tokenize = "porter unicode61 remove_diacritics 2"
53
+ );
54
+
55
+ CREATE TABLE IF NOT EXISTS sessions (
56
+ session_id TEXT PRIMARY KEY,
57
+ file TEXT NOT NULL,
58
+ mtime_ms INTEGER NOT NULL,
59
+ turn_count INTEGER NOT NULL,
60
+ first_ts TEXT,
61
+ last_ts TEXT,
62
+ indexed_at INTEGER NOT NULL
63
+ );
64
+
65
+ CREATE INDEX IF NOT EXISTS sessions_last_ts ON sessions(last_ts);
66
+ `;
67
+
68
+ /** Absolute path of the sessions DB for an agent root. */
69
+ export function dbPath(agentRoot) {
70
+ return join(resolve(agentRoot), DB_REL);
71
+ }
72
+
73
+ function openDb(Database, agentRoot, { readonly = false } = {}) {
74
+ const p = dbPath(agentRoot);
75
+ if (readonly) {
76
+ if (!existsSync(p)) return null;
77
+ return new Database(p, { readonly: true });
78
+ }
79
+ mkdirSync(join(resolve(agentRoot), "state", "learning"), { recursive: true });
80
+ const db = new Database(p);
81
+ db.pragma("journal_mode = WAL");
82
+ db.pragma("synchronous = NORMAL");
83
+ db.exec(SCHEMA);
84
+ return db;
85
+ }
86
+
87
+ /** Pull role-tagged content out of one transcript record (Claude Code JSONL). */
88
+ function recordToTurn(obj) {
89
+ const role = obj.role || (obj.message && obj.message.role) || obj.type || "";
90
+ let content = obj.content ?? (obj.message && obj.message.content);
91
+ if (Array.isArray(content)) {
92
+ content = content
93
+ .map((part) => (typeof part === "string" ? part : part && part.text ? part.text : ""))
94
+ .filter(Boolean)
95
+ .join(" ");
96
+ } else if (typeof content !== "string") {
97
+ content = typeof obj.text === "string" ? obj.text : "";
98
+ }
99
+ const ts = obj.timestamp || obj.ts || obj.time || "";
100
+ return { role: String(role || ""), body: String(content || "").trim(), ts: String(ts || "") };
101
+ }
102
+
103
+ /**
104
+ * Derive the session id for a transcript file. Prefer an explicit session id in
105
+ * the records; fall back to the filename stem (Claude Code names files by
106
+ * session id, e.g. `<uuid>.jsonl`).
107
+ */
108
+ function deriveSessionId(records, file) {
109
+ for (const r of records) {
110
+ const sid = r.sessionId || r.session_id || (r.message && r.message.session_id);
111
+ if (sid) return String(sid);
112
+ }
113
+ return basename(file).replace(/\.jsonl$/i, "");
114
+ }
115
+
116
+ /** Parse a JSONL transcript file into turn rows. */
117
+ function parseTranscript(file) {
118
+ const raw = readFileSync(file, "utf8");
119
+ const records = [];
120
+ for (const line of raw.split("\n")) {
121
+ const t = line.trim();
122
+ if (!t.startsWith("{")) continue;
123
+ try { records.push(JSON.parse(t)); } catch { /* skip bad line */ }
124
+ }
125
+ const sessionId = deriveSessionId(records, file);
126
+ const turns = [];
127
+ let seq = 0;
128
+ for (const rec of records) {
129
+ const turn = recordToTurn(rec);
130
+ if (!turn.body) continue;
131
+ turns.push({ ...turn, seq: seq++ });
132
+ }
133
+ return { sessionId, turns };
134
+ }
135
+
136
+ /**
137
+ * Ingest one transcript file. Idempotent: a file whose mtime matches the
138
+ * recorded one is skipped; a re-ingest under a changed mtime replaces the
139
+ * session's turns wholesale. Returns a small status object.
140
+ *
141
+ * @param {object} o - { agentRoot, file, force?, _db? }
142
+ * @returns {Promise<{sessionId:?string, turns:number, skipped:boolean, reason:string}>}
143
+ */
144
+ export async function ingestSession(o = {}) {
145
+ const { agentRoot, file } = o;
146
+ if (!agentRoot || !file) return { sessionId: null, turns: 0, skipped: true, reason: "bad-args" };
147
+ if (!existsSync(file)) return { sessionId: null, turns: 0, skipped: true, reason: "missing-file" };
148
+
149
+ const Database = await loadDatabase();
150
+ const db = o._db || openDb(Database, agentRoot);
151
+ const ownDb = !o._db;
152
+ try {
153
+ const st = statSync(file);
154
+ const mtime = Math.floor(st.mtimeMs);
155
+ const { sessionId, turns } = parseTranscript(file);
156
+ if (!sessionId) return { sessionId: null, turns: 0, skipped: true, reason: "no-session-id" };
157
+
158
+ const existing = db.prepare("SELECT mtime_ms FROM sessions WHERE session_id = ?").get(sessionId);
159
+ if (existing && existing.mtime_ms === mtime && !o.force) {
160
+ return { sessionId, turns: 0, skipped: true, reason: "unchanged" };
161
+ }
162
+
163
+ const insertTurn = db.prepare(
164
+ "INSERT INTO turns (body, session_id, role, ts, seq) VALUES (?, ?, ?, ?, ?)"
165
+ );
166
+ const deleteTurns = db.prepare("DELETE FROM turns WHERE session_id = ?");
167
+ const upsertSession = db.prepare(
168
+ "INSERT INTO sessions (session_id, file, mtime_ms, turn_count, first_ts, last_ts, indexed_at) " +
169
+ "VALUES (?, ?, ?, ?, ?, ?, ?) ON CONFLICT(session_id) DO UPDATE SET " +
170
+ "file=excluded.file, mtime_ms=excluded.mtime_ms, turn_count=excluded.turn_count, " +
171
+ "first_ts=excluded.first_ts, last_ts=excluded.last_ts, indexed_at=excluded.indexed_at"
172
+ );
173
+
174
+ const firstTs = turns.length ? turns[0].ts : "";
175
+ const lastTs = turns.length ? turns[turns.length - 1].ts : "";
176
+ const txn = db.transaction(() => {
177
+ deleteTurns.run(sessionId);
178
+ for (const t of turns) insertTurn.run(t.body, sessionId, t.role, t.ts, t.seq);
179
+ upsertSession.run(sessionId, file, mtime, turns.length, firstTs, lastTs, Date.now());
180
+ });
181
+ txn();
182
+ return { sessionId, turns: turns.length, skipped: false, reason: existing ? "updated" : "added" };
183
+ } finally {
184
+ if (ownDb) db.close();
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Ingest every `*.jsonl` under a logs directory (default logs/sessions). One DB
190
+ * connection for the batch. Idempotent. Fail-open per-file (a bad file is
191
+ * skipped, not fatal).
192
+ *
193
+ * @param {object} o - { agentRoot, logsDir?, force? }
194
+ * @returns {Promise<{added:number, updated:number, skipped:number, files:number}>}
195
+ */
196
+ export async function ingestAll(o = {}) {
197
+ const { agentRoot } = o;
198
+ const result = { added: 0, updated: 0, skipped: 0, files: 0 };
199
+ if (!agentRoot) return result;
200
+ const logsDir = o.logsDir || join(resolve(agentRoot), "logs", "sessions");
201
+ if (!existsSync(logsDir)) return result;
202
+
203
+ let files;
204
+ try { files = readdirSync(logsDir).filter((f) => f.endsWith(".jsonl")); }
205
+ catch { return result; }
206
+
207
+ const Database = await loadDatabase();
208
+ const db = openDb(Database, agentRoot);
209
+ try {
210
+ for (const f of files) {
211
+ result.files++;
212
+ try {
213
+ const r = await ingestSession({ agentRoot, file: join(logsDir, f), force: o.force, _db: db });
214
+ if (r.reason === "added") result.added++;
215
+ else if (r.reason === "updated") result.updated++;
216
+ else result.skipped++;
217
+ } catch {
218
+ result.skipped++; // a single corrupt transcript must not abort the batch
219
+ }
220
+ }
221
+ } finally {
222
+ db.close();
223
+ }
224
+ return result;
225
+ }
226
+
227
+ // ---------------------------------------------------------------------------
228
+ // Search — 4 modes
229
+ // ---------------------------------------------------------------------------
230
+
231
+ const STOPWORDS = new Set(["the", "and", "for", "are", "but", "not", "you", "all", "with", "this", "that", "from", "what", "when", "have", "was", "were"]);
232
+
233
+ function tokenizeQuery(q) {
234
+ const all = String(q || "")
235
+ .toLowerCase()
236
+ .replace(/[^\p{L}\p{N}\s]/gu, " ")
237
+ .split(/\s+/)
238
+ .filter((t) => t.length >= 3 && !STOPWORDS.has(t));
239
+ if (!all.length) return [];
240
+ return Array.from(new Set(all)).sort((a, b) => b.length - a.length).slice(0, 16).map((t) => `"${t.replace(/"/g, "")}"`);
241
+ }
242
+
243
+ /** First & last turn of a session — the orientation "bookend" for a hit. */
244
+ function bookend(db, sessionId) {
245
+ const first = db.prepare("SELECT role, body, seq, ts FROM turns WHERE session_id = ? ORDER BY seq ASC LIMIT 1").get(sessionId);
246
+ const last = db.prepare("SELECT role, body, seq, ts FROM turns WHERE session_id = ? ORDER BY seq DESC LIMIT 1").get(sessionId);
247
+ return { first: first || null, last: last || null };
248
+ }
249
+
250
+ /**
251
+ * Search the transcript store.
252
+ *
253
+ * @param {string} query free-text (discovery/scroll); ignored for read/browse
254
+ * @param {object} [opts]
255
+ * @param {string} [opts.agentRoot]
256
+ * @param {"discovery"|"scroll"|"read"|"browse"} [opts.mode="discovery"]
257
+ * @param {number} [opts.topN=8]
258
+ * @param {string} [opts.sessionId] required for read; the anchor for scroll
259
+ * @param {number} [opts.seq] scroll anchor turn
260
+ * @param {number} [opts.window=6] scroll half-window (turns each side)
261
+ * @returns {Promise<object>} shape varies by mode (see code)
262
+ */
263
+ export async function search(query, opts = {}) {
264
+ const agentRoot = opts.agentRoot || process.env.AGENT_ROOT || process.cwd();
265
+ const mode = opts.mode || "discovery";
266
+ const Database = await loadDatabase();
267
+ const db = openDb(Database, agentRoot, { readonly: true });
268
+ if (!db) return emptyFor(mode);
269
+ try {
270
+ if (mode === "read") return readMode(db, opts);
271
+ if (mode === "browse") return browseMode(db, opts);
272
+ if (mode === "scroll") return scrollMode(db, opts);
273
+ return discoveryMode(db, query, opts);
274
+ } finally {
275
+ db.close();
276
+ }
277
+ }
278
+
279
+ function emptyFor(mode) {
280
+ if (mode === "read") return { mode, sessionId: null, turns: [] };
281
+ if (mode === "browse") return { mode, sessions: [] };
282
+ if (mode === "scroll") return { mode, sessionId: null, turns: [] };
283
+ return { mode: "discovery", hits: [] };
284
+ }
285
+
286
+ function discoveryMode(db, query, opts) {
287
+ const topN = Math.min(Math.max(parseInt(opts.topN, 10) || 8, 1), 50);
288
+ const tokens = tokenizeQuery(query);
289
+ if (!tokens.length) return { mode: "discovery", hits: [] };
290
+ const match = tokens.join(" OR ");
291
+ const rows = db.prepare(`
292
+ SELECT session_id, role, ts, seq, body,
293
+ bm25(turns) AS score,
294
+ snippet(turns, 0, '«', '»', '…', 18) AS snippet
295
+ FROM turns
296
+ WHERE turns MATCH ?
297
+ ORDER BY bm25(turns)
298
+ LIMIT ?
299
+ `).all(match, topN);
300
+
301
+ const seen = new Map();
302
+ const hits = [];
303
+ for (const r of rows) {
304
+ const hit = {
305
+ session_id: r.session_id,
306
+ seq: r.seq,
307
+ role: r.role,
308
+ ts: r.ts,
309
+ snippet: r.snippet,
310
+ score: r.score,
311
+ };
312
+ if (!seen.has(r.session_id)) {
313
+ seen.set(r.session_id, true);
314
+ hit.bookend = bookend(db, r.session_id);
315
+ }
316
+ hits.push(hit);
317
+ }
318
+ return { mode: "discovery", hits };
319
+ }
320
+
321
+ function scrollMode(db, opts) {
322
+ const sessionId = opts.sessionId;
323
+ if (!sessionId) return { mode: "scroll", sessionId: null, turns: [] };
324
+ const anchor = Number.isFinite(opts.seq) ? opts.seq : 0;
325
+ const half = Number.isFinite(opts.window) ? opts.window : 6;
326
+ const lo = anchor - half;
327
+ const hi = anchor + half;
328
+ const turns = db.prepare(
329
+ "SELECT role, body, seq, ts FROM turns WHERE session_id = ? AND seq BETWEEN ? AND ? ORDER BY seq ASC"
330
+ ).all(sessionId, lo, hi);
331
+ return { mode: "scroll", sessionId, anchor, window: half, turns };
332
+ }
333
+
334
+ function readMode(db, opts) {
335
+ const sessionId = opts.sessionId;
336
+ if (!sessionId) return { mode: "read", sessionId: null, turns: [] };
337
+ const turns = db.prepare(
338
+ "SELECT role, body, seq, ts FROM turns WHERE session_id = ? ORDER BY seq ASC"
339
+ ).all(sessionId);
340
+ const meta = db.prepare("SELECT * FROM sessions WHERE session_id = ?").get(sessionId) || null;
341
+ return { mode: "read", sessionId, meta, turns };
342
+ }
343
+
344
+ function browseMode(db, opts) {
345
+ const topN = Math.min(Math.max(parseInt(opts.topN, 10) || 15, 1), 200);
346
+ const sessions = db.prepare(
347
+ "SELECT session_id, turn_count, first_ts, last_ts, indexed_at FROM sessions ORDER BY last_ts DESC, indexed_at DESC LIMIT ?"
348
+ ).all(topN);
349
+ return { mode: "browse", sessions };
350
+ }
351
+
352
+ export default { ingestSession, ingestAll, search, dbPath };
@@ -0,0 +1,125 @@
1
+ /**
2
+ * session-index.test.mjs — transcript FTS5 store tests (temp DB, no network).
3
+ * Run: node --test lib/learning/session-index.test.mjs
4
+ *
5
+ * Covers: ingest idempotency (mtime skip + force replace), discovery bm25 +
6
+ * bookend, scroll window, read full session, browse recency.
7
+ */
8
+ "use strict";
9
+
10
+ import { test } from "node:test";
11
+ import assert from "node:assert/strict";
12
+ import { mkdtempSync, mkdirSync, writeFileSync, utimesSync, existsSync } from "node:fs";
13
+ import { tmpdir } from "node:os";
14
+ import { join } from "node:path";
15
+
16
+ import { ingestSession, ingestAll, search, dbPath } from "./session-index.mjs";
17
+
18
+ function tmpRoot() {
19
+ const root = mkdtempSync(join(tmpdir(), "maestro-sessidx-"));
20
+ mkdirSync(join(root, "logs", "sessions"), { recursive: true });
21
+ return root;
22
+ }
23
+
24
+ /** Write a JSONL transcript with explicit sessionId on each record. */
25
+ function writeTranscript(root, name, sessionId, turns, mtimeSec) {
26
+ const file = join(root, "logs", "sessions", `${name}.jsonl`);
27
+ const lines = turns.map((t) => JSON.stringify({
28
+ sessionId, role: t.role, content: t.body, timestamp: t.ts || "",
29
+ }));
30
+ writeFileSync(file, lines.join("\n") + "\n");
31
+ if (mtimeSec) utimesSync(file, mtimeSec, mtimeSec);
32
+ return file;
33
+ }
34
+
35
+ const TURNS = [
36
+ { role: "user", body: "We need to reconcile the billing export with stripe", ts: "2026-06-01T09:00:00Z" },
37
+ { role: "assistant", body: "I will pull the export and diff it against the ledger", ts: "2026-06-01T09:01:00Z" },
38
+ { role: "user", body: "Great, focus on the duplicate charge anomaly", ts: "2026-06-01T09:02:00Z" },
39
+ { role: "assistant", body: "Found three duplicates; refund issued and reconciliation closed", ts: "2026-06-01T09:03:00Z" },
40
+ ];
41
+
42
+ test("ingestSession: adds turns, idempotent on unchanged mtime, replaces on force", async () => {
43
+ const root = tmpRoot();
44
+ const file = writeTranscript(root, "sess-a", "SID-A", TURNS, 1_700_000_000);
45
+
46
+ const r1 = await ingestSession({ agentRoot: root, file });
47
+ assert.equal(r1.sessionId, "SID-A");
48
+ assert.equal(r1.turns, 4);
49
+ assert.equal(r1.reason, "added");
50
+ assert.ok(existsSync(dbPath(root)), "db created");
51
+
52
+ // Re-ingest, same mtime → skipped (idempotent).
53
+ const r2 = await ingestSession({ agentRoot: root, file });
54
+ assert.equal(r2.skipped, true);
55
+ assert.equal(r2.reason, "unchanged");
56
+
57
+ // Force → replace.
58
+ const r3 = await ingestSession({ agentRoot: root, file, force: true });
59
+ assert.equal(r3.skipped, false);
60
+ assert.equal(r3.turns, 4);
61
+ });
62
+
63
+ test("ingestAll: ingests a directory, then skips unchanged on a second pass", async () => {
64
+ const root = tmpRoot();
65
+ writeTranscript(root, "sess-a", "SID-A", TURNS, 1_700_000_000);
66
+ writeTranscript(root, "sess-b", "SID-B", TURNS.slice(0, 2), 1_700_000_100);
67
+
68
+ const first = await ingestAll({ agentRoot: root });
69
+ assert.equal(first.files, 2);
70
+ assert.equal(first.added, 2);
71
+
72
+ const second = await ingestAll({ agentRoot: root });
73
+ assert.equal(second.added, 0);
74
+ assert.equal(second.skipped, 2, "unchanged files skipped");
75
+ });
76
+
77
+ test("search discovery: bm25 hit + per-session bookend (first & last turn)", async () => {
78
+ const root = tmpRoot();
79
+ const file = writeTranscript(root, "sess-a", "SID-A", TURNS, 1_700_000_000);
80
+ await ingestSession({ agentRoot: root, file });
81
+
82
+ const res = await search("duplicate charge anomaly", { agentRoot: root, mode: "discovery", topN: 5 });
83
+ assert.equal(res.mode, "discovery");
84
+ assert.ok(res.hits.length >= 1, "found a matching turn");
85
+ const top = res.hits[0];
86
+ assert.equal(top.session_id, "SID-A");
87
+ assert.match(top.snippet, /duplicate|anomaly|charge/i);
88
+ // Bookend present on the first hit of the session.
89
+ assert.ok(top.bookend, "bookend attached");
90
+ assert.match(top.bookend.first.body, /reconcile the billing export/);
91
+ assert.match(top.bookend.last.body, /reconciliation closed/);
92
+ });
93
+
94
+ test("search scroll: window of turns around an anchor seq", async () => {
95
+ const root = tmpRoot();
96
+ const file = writeTranscript(root, "sess-a", "SID-A", TURNS, 1_700_000_000);
97
+ await ingestSession({ agentRoot: root, file });
98
+
99
+ const res = await search("", { agentRoot: root, mode: "scroll", sessionId: "SID-A", seq: 1, window: 1 });
100
+ assert.equal(res.mode, "scroll");
101
+ const seqs = res.turns.map((t) => t.seq);
102
+ assert.deepEqual(seqs, [0, 1, 2], "±1 around seq 1");
103
+ });
104
+
105
+ test("search read: full session in order; browse: recent sessions", async () => {
106
+ const root = tmpRoot();
107
+ writeTranscript(root, "sess-a", "SID-A", TURNS, 1_700_000_000);
108
+ writeTranscript(root, "sess-b", "SID-B", TURNS.slice(0, 2), 1_700_000_100);
109
+ await ingestAll({ agentRoot: root });
110
+
111
+ const read = await search("", { agentRoot: root, mode: "read", sessionId: "SID-A" });
112
+ assert.equal(read.turns.length, 4);
113
+ assert.deepEqual(read.turns.map((t) => t.seq), [0, 1, 2, 3]);
114
+
115
+ const browse = await search("", { agentRoot: root, mode: "browse" });
116
+ assert.equal(browse.mode, "browse");
117
+ const ids = browse.sessions.map((s) => s.session_id);
118
+ assert.ok(ids.includes("SID-A") && ids.includes("SID-B"));
119
+ });
120
+
121
+ test("search: missing DB fails open to empty per mode", async () => {
122
+ const root = tmpRoot(); // nothing ingested
123
+ assert.deepEqual((await search("x", { agentRoot: root, mode: "discovery" })).hits, []);
124
+ assert.deepEqual((await search("", { agentRoot: root, mode: "browse" })).sessions, []);
125
+ });