@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,689 @@
1
+ /**
2
+ * Maestro — Action Executor
3
+ *
4
+ * Executes tool calls from the Claude API. Write tools run agent-local
5
+ * scripts; lookup/RAG tools run targeted `claude --print` queries that
6
+ * leverage MCP servers (Gmail, Calendar, Slack, filesystem, etc.)
7
+ * via the agent's Max subscription.
8
+ *
9
+ * Usage:
10
+ * import { executeAction } from "@cohortapp/agent-sdk/lib/action-executor.js";
11
+ * const result = await executeAction("search_email", { query: "..." }, callerInfo, sessionId);
12
+ *
13
+ * @module action-executor
14
+ */
15
+
16
+ import { execFile } from "node:child_process";
17
+ import fs from "node:fs";
18
+ import path from "node:path";
19
+ import {
20
+ getToolNamesForAccessLevel,
21
+ normalizeAccessLevel,
22
+ } from "./tool-definitions.js";
23
+ import { isOrgTool, executeOrgTool } from "./org/tool-surface.mjs";
24
+ import { resolveClaudeBin, augmentedPath } from "./claude-bin.mjs";
25
+
26
+ const AGENT_ROOT = process.env.AGENT_ROOT || process.cwd();
27
+
28
+ // The historical model for a lookup `claude --print` spawn. When the model
29
+ // router is absent / inert / killed, lookups fall back to this exactly as
30
+ // before (behaviour-preserving default).
31
+ export const LOOKUP_DEFAULT_MODEL = "haiku";
32
+
33
+ // ---------------------------------------------------------------------------
34
+ // Spawn tuning constants (audit L25)
35
+ // ---------------------------------------------------------------------------
36
+ // These were previously inline magic numbers at each spawn site, which made
37
+ // them invisible and impossible to tune per deployment. They are hoisted here
38
+ // as named, env-overridable constants. A non-numeric / absent env var falls
39
+ // back to the original default, so existing behaviour is preserved verbatim.
40
+
41
+ /** Wall-clock budget for a lookup `claude --print` spawn (ms). */
42
+ export const LOOKUP_TIMEOUT_MS =
43
+ Number(process.env.MAESTRO_LOOKUP_TIMEOUT_MS) || 25000;
44
+ /** Max stdout buffer for a lookup `claude --print` spawn (bytes). */
45
+ export const LOOKUP_MAX_BUFFER =
46
+ Number(process.env.MAESTRO_LOOKUP_MAX_BUFFER) || 1024 * 1024;
47
+ /** Wall-clock budget for an agent-local write-script spawn (ms). */
48
+ export const SCRIPT_TIMEOUT_MS =
49
+ Number(process.env.MAESTRO_SCRIPT_TIMEOUT_MS) || 15000;
50
+
51
+ // ---------------------------------------------------------------------------
52
+ // Access Control
53
+ // ---------------------------------------------------------------------------
54
+
55
+ // Privilege ranking (higher = more privileged). Anything not listed — which,
56
+ // after normalisation, can only be "default" — ranks as least privilege.
57
+ const ACCESS_LEVEL_RANK = { default: 0, voice: 1, leadership: 2, ceo: 3 };
58
+
59
+ /**
60
+ * Resolve the EFFECTIVE access level for an action — defence-in-depth (audit M4).
61
+ *
62
+ * executeAction historically trusted whatever `accessLevel` the caller put on
63
+ * `callerInfo`. A compromised or buggy caller could therefore self-assert "ceo"
64
+ * and obtain CEO-only write tools. This helper derives the access level from
65
+ * authenticated provenance when it is available and CLAMPS a self-asserted level
66
+ * so it can never EXCEED the provenance-derived one.
67
+ *
68
+ * Contract (additive — existing callers keep working unchanged):
69
+ * - If `callerInfo.verifiedAccessLevel` is present (provenance-derived), it is
70
+ * the upper bound. The self-asserted `callerInfo.accessLevel` is honoured
71
+ * only when it would NOT grant more privilege; otherwise it is clamped down
72
+ * to the verified level. (A self-asserted level may narrow, never escalate.)
73
+ * - Otherwise fall back to the self-asserted `callerInfo.accessLevel` (legacy
74
+ * path).
75
+ * - If nothing usable is provided, default to least privilege ("default").
76
+ * - Unknown / badly-cased levels are normalised (never empty, never throw).
77
+ *
78
+ * Clamping is by privilege RANK, not string equality.
79
+ *
80
+ * @param {object|null|undefined} callerInfo - { slug, name, accessLevel, verifiedAccessLevel }
81
+ * @returns {string} A guaranteed-known access-level key (never empty).
82
+ */
83
+ export function resolveEffectiveAccessLevel(callerInfo) {
84
+ const info = callerInfo && typeof callerInfo === "object" ? callerInfo : {};
85
+ const rankOf = (level) => ACCESS_LEVEL_RANK[normalizeAccessLevel(level)] ?? 0;
86
+
87
+ const verifiedRaw = info.verifiedAccessLevel;
88
+
89
+ // No authenticated provenance: trust the self-asserted level (legacy path),
90
+ // normalised to least privilege if unknown / absent.
91
+ if (verifiedRaw === undefined || verifiedRaw === null) {
92
+ return normalizeAccessLevel(info.accessLevel);
93
+ }
94
+
95
+ const verified = normalizeAccessLevel(verifiedRaw);
96
+
97
+ // Provenance present but no self-asserted level: the verified level is the
98
+ // baseline (nothing to clamp).
99
+ if (info.accessLevel === undefined || info.accessLevel === null) {
100
+ return verified;
101
+ }
102
+
103
+ const asserted = normalizeAccessLevel(info.accessLevel);
104
+
105
+ // Clamp: a self-asserted level may only narrow, never escalate, privilege.
106
+ if (rankOf(asserted) > rankOf(verified)) {
107
+ console.warn(
108
+ `[action-executor] Self-asserted access level ${JSON.stringify(
109
+ info.accessLevel,
110
+ )} exceeds verified level ${JSON.stringify(
111
+ verifiedRaw,
112
+ )}; clamping to verified.`,
113
+ );
114
+ return verified;
115
+ }
116
+
117
+ return asserted;
118
+ }
119
+
120
+ function isAuthorized(toolName, accessLevel) {
121
+ return getToolNamesForAccessLevel(accessLevel).includes(toolName);
122
+ }
123
+
124
+ // ---------------------------------------------------------------------------
125
+ // Self-auditing (audit observability F1/F3)
126
+ // ---------------------------------------------------------------------------
127
+
128
+ // The PostToolUse shell hook (scripts/hooks/post-action-log.sh) only sees the
129
+ // Bash/MCP tools Claude Code itself invokes — it never sees executeAction's
130
+ // own dispatch (slack_send, draft_email, voice calls, …) or its authorization
131
+ // denials. So executeAction writes its OWN audit row per invocation, including
132
+ // DENIED attempts, into the same date-stamped stream. This is best-effort and
133
+ // MUST never throw into the action path (a logging failure cannot break a
134
+ // send), mirroring the fail-open discipline of every other log writer.
135
+
136
+ /** Best-effort target for the audit row: first present, never throws. */
137
+ function auditTarget(input) {
138
+ if (!input || typeof input !== "object") return null;
139
+ const cmd =
140
+ typeof input.command === "string" ? input.command.slice(0, 200) : undefined;
141
+ const t =
142
+ input.file_path ??
143
+ cmd ??
144
+ input.url ??
145
+ input.to ??
146
+ input.channel ??
147
+ input.recipient ??
148
+ null;
149
+ return t == null ? null : String(t);
150
+ }
151
+
152
+ /**
153
+ * Append one JSONL audit row for an executeAction invocation. Fail-open: any
154
+ * I/O or serialisation error is swallowed so logging can never break the
155
+ * action it is recording.
156
+ *
157
+ * EXPORTED (spec §1.5b) so the MCP tools/call dispatcher (lib/mcp/server.mjs)
158
+ * writes the SAME row shape into the SAME logs/audit/<date>-actions.jsonl
159
+ * stream for its plane — one row shape, both planes, no double-logging (the
160
+ * native path's rows are written here by executeAction; the MCP dispatcher
161
+ * writes its own with session_id "mcp:<pid>").
162
+ *
163
+ * @param {string} agentRoot
164
+ * @param {{tool:string,status:string,denied:boolean,input:object,sessionId:string}} row
165
+ */
166
+ export function appendActionAudit(agentRoot, { tool, status, denied, input, sessionId }) {
167
+ try {
168
+ const auditDir = path.join(agentRoot, "logs", "audit");
169
+ fs.mkdirSync(auditDir, { recursive: true });
170
+ const date = new Date().toISOString().slice(0, 10);
171
+ const file = path.join(auditDir, `${date}-actions.jsonl`);
172
+ const entry = {
173
+ timestamp: new Date().toISOString().replace(/\.\d{3}Z$/, "Z"),
174
+ tool: typeof tool === "string" ? tool : String(tool ?? "unknown"),
175
+ status,
176
+ denied: Boolean(denied),
177
+ target: auditTarget(input),
178
+ session_id: sessionId == null ? null : String(sessionId),
179
+ };
180
+ fs.appendFileSync(file, JSON.stringify(entry) + "\n");
181
+ } catch {
182
+ /* best effort — never throw into the action path */
183
+ }
184
+ }
185
+
186
+ // ---------------------------------------------------------------------------
187
+ // Public API
188
+ // ---------------------------------------------------------------------------
189
+
190
+ /**
191
+ * Execute a tool call.
192
+ * @param {string} toolName
193
+ * @param {object} input
194
+ * @param {object} callerInfo - { slug, name, accessLevel, verifiedAccessLevel? }
195
+ * @param {string} sessionId
196
+ * @param {object} [options] - { agentRoot, scripts }
197
+ * @returns {Promise<{success: boolean, result: string}>}
198
+ */
199
+ export async function executeAction(toolName, input, callerInfo, sessionId, options = {}) {
200
+ const agentRoot = options.agentRoot || AGENT_ROOT;
201
+
202
+ // Authorise against the EFFECTIVE access level (defence-in-depth, audit M4):
203
+ // derived from authenticated provenance when available, with any
204
+ // self-asserted level clamped so it cannot escalate privilege. Falls back to
205
+ // least privilege when nothing usable is provided.
206
+ const effectiveAccessLevel = resolveEffectiveAccessLevel(callerInfo);
207
+ if (!isAuthorized(toolName, effectiveAccessLevel)) {
208
+ appendActionAudit(agentRoot, {
209
+ tool: toolName,
210
+ status: "denied",
211
+ denied: true,
212
+ input,
213
+ sessionId,
214
+ });
215
+ return { success: false, result: "You do not have permission to perform this action." };
216
+ }
217
+
218
+ // Carry the agent identity (when the caller supplied one) into the lookup
219
+ // ledger rows so cost attribution joins to the right agent. Everything else
220
+ // about the lookup path stays on its fail-open defaults (lazy router import).
221
+ const lookupOpts = { agent: options.agent ?? callerInfo?.slug ?? null };
222
+
223
+ let outcome;
224
+ try {
225
+ // Org (Cohort) tools — the curated surface from lib/org/tool-surface.mjs.
226
+ // ONE executor shared with the cohort-mcp plane: config resolution
227
+ // (env-first, config-file fallback — the executor always runs inside an
228
+ // agent repo so config/org.yaml is the normal source), the outbound
229
+ // send-gate for messaging_send/email_send (and org_rpc passthroughs of
230
+ // those methods), and the org client's fail-open frame semantics. The
231
+ // access clamp above already covered authorization (name-set based), and
232
+ // the appendActionAudit call below records the row for the NATIVE plane.
233
+ if (isOrgTool(toolName)) {
234
+ const frame = await executeOrgTool(toolName, input, {
235
+ agentRoot,
236
+ fetchImpl: options.fetchImpl,
237
+ screenImpl: options.screenImpl,
238
+ });
239
+ outcome = frame.ok
240
+ ? {
241
+ success: true,
242
+ result:
243
+ typeof frame.result === "string"
244
+ ? frame.result
245
+ : JSON.stringify(frame.result ?? null, null, 2),
246
+ meta: frame.result,
247
+ }
248
+ : {
249
+ success: false,
250
+ result: `${(frame.error && frame.error.code) || "ERROR"}: ${(frame.error && frame.error.message) || "org call failed"}`,
251
+ };
252
+ }
253
+ // Lookup tools — RAG via Claude Code CLI. Only the public registry is
254
+ // dispatchable from here; INTERNAL_LOOKUP_PROMPTS entries are reached
255
+ // exclusively via their dedicated write-tool executors below.
256
+ else if (LOOKUP_PROMPTS[toolName]) {
257
+ outcome = await executeLookup(toolName, input, agentRoot, lookupOpts);
258
+ } else {
259
+ // Write tools — delegate to agent-local scripts
260
+ switch (toolName) {
261
+ case "slack_send":
262
+ outcome = await executeScript(agentRoot, "slack-send.sh", [input.recipient, input.message]);
263
+ break;
264
+ case "draft_email":
265
+ outcome = await executeDraft(agentRoot, input);
266
+ break;
267
+ case "whatsapp_send":
268
+ outcome = await executeScript(agentRoot, "send-whatsapp.sh", [input.recipient, input.message]);
269
+ break;
270
+ case "sms_send":
271
+ outcome = await executeScript(agentRoot, "send-sms.sh", [input.recipient, input.message]);
272
+ break;
273
+ case "send_voice_message":
274
+ outcome = await executeSendVoiceMessage(agentRoot, input);
275
+ break;
276
+ case "place_voice_call":
277
+ outcome = await executePlaceVoiceCall(agentRoot, input);
278
+ break;
279
+ case "rag_search":
280
+ outcome = await executeRagSearch(agentRoot, input);
281
+ break;
282
+ case "queue_update":
283
+ outcome = await executeQueueUpdate(agentRoot, input, lookupOpts);
284
+ break;
285
+ case "create_action_item":
286
+ outcome = await executeCreateItem(agentRoot, input, lookupOpts);
287
+ break;
288
+ case "schedule_meeting":
289
+ outcome = await executeLookup("schedule_meeting", input, agentRoot, lookupOpts);
290
+ break;
291
+ case "create_reminder":
292
+ outcome = await executeLookup("create_reminder", input, agentRoot, lookupOpts);
293
+ break;
294
+ case "generate_memo":
295
+ outcome = await executeGenerateMemo(agentRoot, input);
296
+ break;
297
+ case "generate_report":
298
+ outcome = await executeLookup("generate_report", input, agentRoot, lookupOpts);
299
+ break;
300
+ default:
301
+ outcome = { success: false, result: `Unknown tool: ${toolName}` };
302
+ }
303
+ }
304
+ } catch (err) {
305
+ outcome = { success: false, result: `Action failed: ${err.message}` };
306
+ }
307
+
308
+ appendActionAudit(agentRoot, {
309
+ tool: toolName,
310
+ status: outcome.success ? "completed" : "failed",
311
+ denied: false,
312
+ input,
313
+ sessionId,
314
+ });
315
+ return outcome;
316
+ }
317
+
318
+ // ---------------------------------------------------------------------------
319
+ // Lookup / RAG via Claude Code CLI
320
+ // ---------------------------------------------------------------------------
321
+
322
+ // Public lookup tools — directly addressable by name from executeAction.
323
+ const LOOKUP_PROMPTS = {
324
+ search_email: (i) =>
325
+ `Search Gmail for: "${i.query}". Return top ${i.max_results || 5} results with sender, subject, date, 1-sentence summary. Be concise — spoken aloud.`,
326
+ search_calendar: (i) =>
327
+ `Look up calendar: "${i.query}" for ${i.time_range || "this week"}. List events with title, time, attendees. Be concise.`,
328
+ search_files: (i) =>
329
+ `Search operational files for: "${i.query}"${i.scope && i.scope !== "all" ? ` in ${i.scope}/` : ""}. Summarise in 2-4 sentences.`,
330
+ search_meetings: (i) =>
331
+ `Search Granola meeting notes for: "${i.query}"${i.date_range ? ` from ${i.date_range}` : ""}. Summarise key discussions and decisions in 2-4 sentences.`,
332
+ search_slack: (i) =>
333
+ `Search Slack for: "${i.query}"${i.channel ? ` in ${i.channel}` : ""}. Summarise who said what, when. 2-4 sentences.`,
334
+ search_web: (i) =>
335
+ `Web search: "${i.query}". Summarise top ${i.max_results || 5} results concisely.`,
336
+ search_documents: (i) =>
337
+ `Search shared documents for: "${i.query}"${i.doc_type && i.doc_type !== "all" ? ` (${i.doc_type} only)` : ""}. List titles and brief summaries.`,
338
+ search_decisions: (i) =>
339
+ `Search the decision archive for: "${i.query}". Include rationale, who decided, and outcome. 2-4 sentences.`,
340
+ lookup_person: (i) =>
341
+ `Look up information about ${i.name}: ${i.info_type || "all available info"} — role, contact, recent activity. Be concise.`,
342
+ search_regulatory: (i) =>
343
+ `Search regulatory/compliance info for: "${i.query}"${i.jurisdiction ? ` in ${i.jurisdiction}` : ""}. Status, deadlines, requirements. Be concise.`,
344
+ search_strategy: (i) =>
345
+ `Search strategic context for: "${i.query}". Priorities, OKRs, initiative status. 2-4 sentences.`,
346
+ search_precedents: (i) =>
347
+ `Search operational precedents for: "${i.query}". How was this handled before? Templates? 2-4 sentences.`,
348
+ search_financial: (i) =>
349
+ `Search financial data for: "${i.query}". Runway, burn, capital readiness. Be concise.`,
350
+ search_pipeline: (i) =>
351
+ `Search pipeline for: "${i.query}"${i.stage && i.stage !== "all" ? ` (stage: ${i.stage})` : ""}. Deal status, partner conversations. Be concise.`,
352
+ search_engineering: (i) =>
353
+ `Search engineering health for: "${i.query}"${i.repo ? ` in ${i.repo}` : ""}. PRs, delivery confidence, blockers. Be concise.`,
354
+ search_hiring: (i) =>
355
+ `Search hiring pipeline for: "${i.query}"${i.status && i.status !== "all" ? ` (status: ${i.status})` : ""}. Roles, candidates, offers. Be concise.`,
356
+ search_risk: (i) =>
357
+ `Search risk register for: "${i.query}"${i.severity && i.severity !== "all" ? ` (severity: ${i.severity})` : ""}. Risks, mitigations, owners. Be concise.`,
358
+ schedule_meeting: (i) =>
359
+ `Schedule a meeting: "${i.title}" with ${i.attendees} on ${i.date}${i.time ? ` at ${i.time}` : ""}${i.duration ? ` for ${i.duration}` : ""}. Check availability and create the event. Confirm what was scheduled.`,
360
+ create_reminder: (i) =>
361
+ `Create a reminder: "${i.message}" for ${i.for_whom || "the principal"} at ${i.when}. Confirm when it will fire.`,
362
+ generate_report: (i) =>
363
+ `Generate a ${i.report_type} report${i.custom_query ? `: ${i.custom_query}` : ""}${i.time_range ? ` for ${i.time_range}` : ""}. Aggregate data from dashboards and queues. Format as a concise executive summary.`,
364
+ };
365
+
366
+ // Internal lookup prompts (audit L24). These were previously appended to
367
+ // LOOKUP_PROMPTS via post-declaration mutation, which hid them from the
368
+ // declared object literal and meant `const` only froze the binding, not the
369
+ // shape. They live in their own clearly-named map; they are NOT directly
370
+ // dispatchable from executeAction (the public LOOKUP_PROMPTS check above
371
+ // excludes them) and are reached only by their dedicated write-tool
372
+ // executors (executeQueueUpdate, executeCreateItem) which call executeLookup
373
+ // with the internal name explicitly.
374
+ const INTERNAL_LOOKUP_PROMPTS = {
375
+ queue_update_internal: (i) =>
376
+ `In the state/queues/ directory, find the item matching "${i.search_term}" and update its status to "${i.new_status}"${i.new_priority ? ` and priority to "${i.new_priority}"` : ""}${i.note ? `. Add note: "${i.note}"` : ""}. Confirm what was updated.`,
377
+ create_item_internal: (i) =>
378
+ `Create a new item in state/queues/${i.queue || "action-stack"}.yaml with title "${i.title}", priority "${i.priority || "high"}"${i.due_date ? `, due ${i.due_date}` : ""}${i.owner ? `, owner: ${i.owner}` : ""}${i.context ? `, context: "${i.context}"` : ""}. Use the existing YAML format. Confirm what was created.`,
379
+ };
380
+
381
+ /**
382
+ * Resolve the MODEL FLAG for a lookup spawn through the model router (#29
383
+ * residual): most lookup/RAG tools are Haiku-class, so routing the SELECTION
384
+ * lets the cheapest capable model serve them, and stamps a `decision_id` we can
385
+ * join to the cost ledger. This is a model-selection + cost-accounting upgrade
386
+ * ONLY — the spawn itself (a `claude --print` with MCP access) is unchanged.
387
+ *
388
+ * FAIL-OPEN by construction: the kill switch, the absence of a v2 config, or ANY
389
+ * router error all return the historical `haiku` default with no decision_id —
390
+ * the lookup never breaks because routing did.
391
+ *
392
+ * Injectable for tests (no real router import on the test path) via `opts`:
393
+ * - opts.resolve(req, ropts) → RouteDecision (defaults to lazy resolveChain)
394
+ * - opts.loadConfig(root) → routing config or null (defaults to lazy loader)
395
+ * - opts.env → env for the kill-switch probe (defaults process.env)
396
+ *
397
+ * @returns {Promise<{modelFlag:string, decision:object|null, decisionId:string|null,
398
+ * provider:string|null, model:string|null, ref:string|null,
399
+ * catalog:object|null}>}
400
+ */
401
+ export async function resolveLookupModel(toolName, agentRoot, opts = {}) {
402
+ const fallback = {
403
+ modelFlag: LOOKUP_DEFAULT_MODEL,
404
+ decision: null,
405
+ decisionId: null,
406
+ provider: null,
407
+ model: null,
408
+ ref: null,
409
+ catalog: null,
410
+ };
411
+ try {
412
+ const env = opts.env || process.env || {};
413
+ // Kill switch: stock Anthropic, no router selection (mirrors resolveChain).
414
+ const force = env.MAESTRO_ROUTER_FORCE_ANTHROPIC;
415
+ if (force === "1" || force === "true") return fallback;
416
+
417
+ // Load the routing config — null when absent (preserve "current behaviour").
418
+ let config = opts.config;
419
+ if (config === undefined) {
420
+ const loadConfig =
421
+ opts.loadConfig ||
422
+ (async () => {
423
+ const m = await import("./model-router.mjs");
424
+ return m.loadRoutingConfig(agentRoot);
425
+ });
426
+ config = await loadConfig(agentRoot);
427
+ }
428
+ // Only the v2 router produces a model-selecting RouteDecision; anything else
429
+ // (no config, v1) keeps the historical default.
430
+ if (!config || config.schema_version !== 2) return fallback;
431
+
432
+ const resolveFn =
433
+ opts.resolve ||
434
+ (async (req, ropts) => {
435
+ const m = await import("./model-router/resolve.mjs");
436
+ return m.resolveChain(req, ropts);
437
+ });
438
+ const decision = await resolveFn(
439
+ {
440
+ task_class: "session.lookup",
441
+ // Lookups are tool-LESS RAG queries over MCP; they read sensitive
442
+ // mailboxes/calendars, so deny-default to sensitive.
443
+ data_class: "sensitive",
444
+ harness_hint: "session",
445
+ needs_tool_use: true,
446
+ source: "lookup",
447
+ lane: "realtime",
448
+ },
449
+ { config, agentRoot, env },
450
+ );
451
+ if (!decision || !decision.chosen) return fallback;
452
+
453
+ const sa = decision.spawnArgs || {};
454
+ const chosen = decision.chosen || {};
455
+ return {
456
+ modelFlag: sa.modelFlag || chosen.model || LOOKUP_DEFAULT_MODEL,
457
+ decision,
458
+ decisionId: decision.decision_id || null,
459
+ provider: chosen.provider || null,
460
+ model: chosen.model || null,
461
+ ref: (chosen.catalogRow && chosen.catalogRow.ref) || null,
462
+ catalog: (chosen.catalogRow && { byRef: { [chosen.catalogRow.ref]: chosen.catalogRow } }) || null,
463
+ };
464
+ } catch {
465
+ // Absolute fail-open: any router fault → the historical default model.
466
+ return fallback;
467
+ }
468
+ }
469
+
470
+ /**
471
+ * Write ONE ledger row for a lookup spawn (cost accounting). Best-effort and
472
+ * NEVER throws into the lookup path — a ledger we cannot persist degrades
473
+ * silently rather than failing the search. Injectable via `opts.writeLedger`.
474
+ */
475
+ async function writeLookupLedger(toolName, agentRoot, routed, exitReason, opts = {}) {
476
+ // No router decision → nothing the v2 ledger can price; skip (preserves the
477
+ // no-config behaviour: a plain `--model haiku` spawn wrote no ledger row).
478
+ if (!routed || !routed.decisionId || !routed.ref) return;
479
+ try {
480
+ const writer =
481
+ opts.writeLedger ||
482
+ (async (catalog, entry, deps) => {
483
+ const m = await import("./model-router/ledger.mjs");
484
+ return m.writeLedgerRow(catalog, entry, deps);
485
+ });
486
+ // Use the REAL catalog (its byRef is a Map that lookupModel/estimateCost
487
+ // query) so the row is actually priced — a synthesized plain object would
488
+ // silently price every lookup at $0. Falls back to the routed snapshot.
489
+ let catalog = routed.catalog;
490
+ try {
491
+ const cm = await import("./model-router/catalog.mjs");
492
+ catalog = cm.loadCatalogCached(agentRoot) || routed.catalog;
493
+ } catch { /* keep the routed snapshot fallback */ }
494
+ await writer(
495
+ catalog,
496
+ {
497
+ agent: opts.agent ?? null,
498
+ decision_id: routed.decisionId,
499
+ task_class: "session.lookup",
500
+ source: "lookup",
501
+ lane: "realtime",
502
+ harness: "session",
503
+ ref: routed.ref,
504
+ provider: routed.provider,
505
+ model: routed.model,
506
+ inputTokens: opts.inputTokens || 0,
507
+ outputTokens: opts.outputTokens || 0,
508
+ exitReason,
509
+ },
510
+ opts.ledgerDeps || { agentRoot },
511
+ );
512
+ } catch {
513
+ /* a ledger we can't write must not fail the lookup */
514
+ }
515
+ }
516
+
517
+ export async function executeLookup(toolName, input, agentRoot, opts = {}) {
518
+ const promptBuilder = LOOKUP_PROMPTS[toolName] || INTERNAL_LOOKUP_PROMPTS[toolName];
519
+ if (!promptBuilder) return { success: false, result: `No prompt for: ${toolName}` };
520
+
521
+ const prompt = promptBuilder(input);
522
+
523
+ // Route the MODEL SELECTION (only) through the router; the spawn itself is
524
+ // unchanged. Fail-open: routing errors yield the historical `haiku` default.
525
+ const routed = await resolveLookupModel(toolName, agentRoot, opts);
526
+
527
+ try {
528
+ const { stdout } = await new Promise((resolve, reject) => {
529
+ execFile(resolveClaudeBin(), ["--print", "--model", routed.modelFlag, "-p", prompt], {
530
+ timeout: LOOKUP_TIMEOUT_MS,
531
+ maxBuffer: LOOKUP_MAX_BUFFER,
532
+ env: { ...process.env, TERM: "dumb", PATH: augmentedPath() },
533
+ cwd: agentRoot,
534
+ }, (err, stdout, stderr) => {
535
+ if (err) reject(err);
536
+ else resolve({ stdout, stderr });
537
+ });
538
+ });
539
+
540
+ // Estimate usage from the text in hand (~4 chars/token) so the ledger row is
541
+ // actually priced, not $0 — a lookup is a one-shot, no usage envelope to parse.
542
+ const estTok = (s) => Math.ceil(String(s || "").length / 4);
543
+ await writeLookupLedger(toolName, agentRoot, routed, "ok", {
544
+ ...opts, inputTokens: opts.inputTokens || estTok(prompt), outputTokens: opts.outputTokens || estTok(stdout),
545
+ });
546
+ return { success: true, result: (stdout || "").trim() || "No results found." };
547
+ } catch (err) {
548
+ await writeLookupLedger(toolName, agentRoot, routed, err.killed ? "timeout" : "error", { ...opts, inputTokens: opts.inputTokens || Math.ceil(String(prompt || "").length / 4) });
549
+ if (err.killed) return { success: false, result: "Search timed out. Try a more specific query." };
550
+ return { success: false, result: `Search failed: ${err.message}` };
551
+ }
552
+ }
553
+
554
+ // ---------------------------------------------------------------------------
555
+ // Write tool executors (generic implementations — agents can override)
556
+ // ---------------------------------------------------------------------------
557
+
558
+ async function executeScript(agentRoot, scriptName, args) {
559
+ const scriptPath = path.join(agentRoot, "scripts", scriptName);
560
+ if (!fs.existsSync(scriptPath)) {
561
+ return { success: false, result: `Script not found: ${scriptName}` };
562
+ }
563
+
564
+ return new Promise((resolve) => {
565
+ execFile("bash", [scriptPath, ...args.filter(Boolean)], {
566
+ timeout: SCRIPT_TIMEOUT_MS,
567
+ cwd: agentRoot,
568
+ env: { ...process.env },
569
+ }, (err, stdout, stderr) => {
570
+ if (err) resolve({ success: false, result: stderr || err.message });
571
+ else resolve({ success: true, result: stdout.trim() || "Done." });
572
+ });
573
+ });
574
+ }
575
+
576
+ async function executeDraft(agentRoot, input) {
577
+ const draftsDir = path.join(agentRoot, "outputs/drafts");
578
+ fs.mkdirSync(draftsDir, { recursive: true });
579
+
580
+ const date = new Date().toISOString().split("T")[0];
581
+ const slug = input.to.replace(/[^a-zA-Z0-9]/g, "-").toLowerCase();
582
+ const filePath = path.join(draftsDir, `${date}-draft-${slug}.md`);
583
+
584
+ const content = `---\nto: ${input.to}\nsubject: ${input.subject}\nvoice: ${input.voice || "agent"}\nurgency: ${input.urgency || "routine"}\ndate: ${new Date().toISOString()}\n---\n\n${input.body}\n`;
585
+ fs.writeFileSync(filePath, content);
586
+
587
+ if (input.urgency === "immediate") {
588
+ return executeScript(agentRoot, "send-email.sh", [input.to, input.subject, input.body]);
589
+ }
590
+
591
+ return { success: true, result: `Draft saved: ${filePath}` };
592
+ }
593
+
594
+ async function executeQueueUpdate(agentRoot, input, opts = {}) {
595
+ // Delegate to claude CLI for intelligent queue searching and updating.
596
+ // queue_update_internal lives in INTERNAL_LOOKUP_PROMPTS (audit L24).
597
+ return executeLookup("queue_update_internal", input, agentRoot, opts);
598
+ }
599
+
600
+ async function executeCreateItem(agentRoot, input, opts = {}) {
601
+ // create_item_internal lives in INTERNAL_LOOKUP_PROMPTS (audit L24).
602
+ return executeLookup("create_item_internal", input, agentRoot, opts);
603
+ }
604
+
605
+ async function executeGenerateMemo(agentRoot, input) {
606
+ const draftsDir = path.join(agentRoot, "outputs/drafts");
607
+ fs.mkdirSync(draftsDir, { recursive: true });
608
+
609
+ const slug = input.title.replace(/[^a-zA-Z0-9]/g, "-").toLowerCase().slice(0, 40);
610
+ const filePath = path.join(draftsDir, `${new Date().toISOString().split("T")[0]}-${slug}.md`);
611
+ fs.writeFileSync(filePath, `# ${input.title}\n\n${input.content}\n`);
612
+
613
+ return { success: true, result: `Memo saved to ${filePath}` };
614
+ }
615
+
616
+ // ---------------------------------------------------------------------------
617
+ // Voice — outbound TTS messages + live conversational PSTN
618
+ // ---------------------------------------------------------------------------
619
+
620
+ async function executeSendVoiceMessage(agentRoot, input) {
621
+ try {
622
+ const { sendVoiceMessage } = await import("./voice/outbound.mjs");
623
+ const result = await sendVoiceMessage({
624
+ to: input.to,
625
+ text: input.text,
626
+ transport: input.transport,
627
+ voice: input.voice,
628
+ agentRoot,
629
+ });
630
+ return {
631
+ success: true,
632
+ result:
633
+ `Voice message delivered via ${result.transport} (${result.audio_bytes} bytes, ` +
634
+ `~$${result.cost_usd}). message_id=${result.message_id}`,
635
+ meta: result,
636
+ };
637
+ } catch (err) {
638
+ return { success: false, result: `send_voice_message failed: ${err.message}` };
639
+ }
640
+ }
641
+
642
+ async function executeRagSearch(agentRoot, input) {
643
+ try {
644
+ const { search } = await import("./rag/search.mjs");
645
+ const hits = await search({
646
+ query: input.query,
647
+ topN: Math.min(input.top_n || 5, 20),
648
+ kind: input.kind,
649
+ agentRoot,
650
+ });
651
+ if (hits.length === 0) {
652
+ return { success: true, result: `No matches for "${input.query}" in the indexed corpus. Try fewer / different terms, or run scripts/rag/build-index.mjs to refresh the index.` };
653
+ }
654
+ const formatted = hits.map((h, i) =>
655
+ `${i + 1}. ${h.source_path} (${h.kind}, rel=${h.relevance.toFixed(2)})` +
656
+ `\n title: ${h.title}` +
657
+ (h.heading ? `\n section: ${h.heading}` : "") +
658
+ `\n snippet: ${h.snippet.replace(/\s+/g, " ").slice(0, 240)}`
659
+ ).join("\n");
660
+ return {
661
+ success: true,
662
+ result: `Found ${hits.length} match(es) for "${input.query}":\n${formatted}`,
663
+ meta: { hits },
664
+ };
665
+ } catch (err) {
666
+ return { success: false, result: `rag_search failed: ${err.message}` };
667
+ }
668
+ }
669
+
670
+ async function executePlaceVoiceCall(agentRoot, input) {
671
+ try {
672
+ const { placeOutboundCall } = await import("./voice/outbound.mjs");
673
+ const result = await placeOutboundCall({
674
+ to: input.to,
675
+ brief: input.brief,
676
+ voice: input.voice,
677
+ agentRoot,
678
+ });
679
+ return {
680
+ success: true,
681
+ result:
682
+ `Outbound call initiated. call_id=${result.call_id}, twilio_sid=${result.twilio_call_sid}, ` +
683
+ `status=${result.status}. Transcript will appear in state/inbox/voice/ when the call ends.`,
684
+ meta: result,
685
+ };
686
+ } catch (err) {
687
+ return { success: false, result: `place_voice_call failed: ${err.message}` };
688
+ }
689
+ }