@adaptic/maestro 1.0.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 (854) hide show
  1. package/.claude/commands/init-agent.md +99 -0
  2. package/.claude/commands/init-maestro.md +565 -0
  3. package/.claude/settings.json +114 -0
  4. package/.env.example +152 -0
  5. package/README.md +491 -0
  6. package/agents/board-prep/agent.md +80 -0
  7. package/agents/browser-operator/agent.md +52 -0
  8. package/agents/calendar-ops/agent.md +50 -0
  9. package/agents/capital-raising/agent.md +69 -0
  10. package/agents/ceo-briefing/agent.md +72 -0
  11. package/agents/communications/agent.md +69 -0
  12. package/agents/competitive-intelligence/agent.md +49 -0
  13. package/agents/corporate-development/agent.md +66 -0
  14. package/agents/decision-log/agent.md +65 -0
  15. package/agents/desktop-operator/agent.md +59 -0
  16. package/agents/engineering-coordination/agent.md +51 -0
  17. package/agents/founder-voice/agent.md +72 -0
  18. package/agents/fund-ops/agent.md +52 -0
  19. package/agents/gmail-operator/agent.md +62 -0
  20. package/agents/hiring-org-design/agent.md +119 -0
  21. package/agents/inbound-dispatcher/agent.md +66 -0
  22. package/agents/legal-structuring/agent.md +65 -0
  23. package/agents/market-research/agent.md +48 -0
  24. package/agents/partnerships/agent.md +59 -0
  25. package/agents/platform-architecture/agent.md +57 -0
  26. package/agents/pmo-execution/agent.md +60 -0
  27. package/agents/product-strategy/agent.md +50 -0
  28. package/agents/regulatory-dfsa/agent.md +96 -0
  29. package/agents/risk-register/agent.md +62 -0
  30. package/agents/rollup-target-sourcing/agent.md +59 -0
  31. package/agents/session-spawner/agent.md +64 -0
  32. package/agents/slack-operator/agent.md +60 -0
  33. package/agents/sophie-chief-of-staff/agent.md +134 -0
  34. package/agents/strategic-planning/agent.md +54 -0
  35. package/agents/whatsapp-operator/agent.md +60 -0
  36. package/agents/workflow-automation/agent.md +61 -0
  37. package/bin/maestro.mjs +388 -0
  38. package/desktop-control/README.md +56 -0
  39. package/desktop-control/app-profiles/gmail.yaml +120 -0
  40. package/desktop-control/app-profiles/slack.yaml +315 -0
  41. package/desktop-control/app-profiles/whatsapp.yaml +107 -0
  42. package/docs/architecture/agent-topology.md +2222 -0
  43. package/docs/architecture/continuous-monitoring.md +221 -0
  44. package/docs/architecture/mcp-capability-map.md +560 -0
  45. package/docs/architecture/system-architecture.md +1273 -0
  46. package/docs/business-synthesis/ADAPTIC-GROUP-FINAL-OWNERSHIP-STRUCTURE.pdf +13667 -10
  47. package/docs/business-synthesis/adaptic-overview.md +296 -0
  48. package/docs/business-synthesis/executive-operating-model.md +261 -0
  49. package/docs/governance/action-approval-model.md +331 -0
  50. package/docs/governance/communications-policy.md +410 -0
  51. package/docs/governance/desktop-control-safety.md +499 -0
  52. package/docs/guides/agent-persona-setup.md +600 -0
  53. package/docs/operating-charter.md +87 -0
  54. package/docs/prompts/board-pack-cover-template.md +37 -0
  55. package/docs/prompts/decision-recommendation-template.md +88 -0
  56. package/docs/prompts/followup-message-template.md +141 -0
  57. package/docs/prompts/investor-letter-template.md +52 -0
  58. package/docs/prompts/morning-brief-template.md +82 -0
  59. package/docs/prompts/presentation-template.md +58 -0
  60. package/docs/prompts/weekly-strategic-memo-template.md +104 -0
  61. package/docs/runbooks/mac-mini-bootstrap.md +404 -0
  62. package/docs/runbooks/perpetual-operations.md +505 -0
  63. package/docs/runbooks/recovery-and-failover.md +588 -0
  64. package/docs/superpowers/plans/2026-04-02-phase0-operational-foundation.md +2550 -0
  65. package/docs/superpowers/plans/2026-04-03-phase1-executive-core.md +1085 -0
  66. package/docs/superpowers/plans/2026-04-03-phase2-people-product-commercial.md +739 -0
  67. package/docs/superpowers/plans/2026-04-05-information-barrier-implementation.md +926 -0
  68. package/docs/superpowers/plans/2026-04-06-session-context-dedup.md +1994 -0
  69. package/docs/superpowers/specs/2026-04-02-phase0-operational-foundation-design.md +842 -0
  70. package/docs/superpowers/specs/2026-04-03-phase1-executive-core-design.md +516 -0
  71. package/docs/superpowers/specs/2026-04-03-phase2-people-product-commercial-design.md +452 -0
  72. package/docs/superpowers/specs/2026-04-03-phase3-4-final-towers-design.md +129 -0
  73. package/docs/superpowers/specs/2026-04-05-information-barrier-design.md +678 -0
  74. package/docs/superpowers/specs/2026-04-05-reactive-daemon-design.md +237 -0
  75. package/docs/superpowers/specs/2026-04-06-session-context-dedup-design.md +369 -0
  76. package/docs/workflows/executive-cadence.md +218 -0
  77. package/ingest/README.md +87 -0
  78. package/mcp/README.md +51 -0
  79. package/package.json +48 -0
  80. package/plugins/maestro-skills/plugin.json +55 -0
  81. package/plugins/maestro-skills/skills/board-deck.md +68 -0
  82. package/plugins/maestro-skills/skills/decision-brief.md +89 -0
  83. package/plugins/maestro-skills/skills/draft-comms.md +84 -0
  84. package/plugins/maestro-skills/skills/evening-wrap.md +53 -0
  85. package/plugins/maestro-skills/skills/hiring-triage.md +74 -0
  86. package/plugins/maestro-skills/skills/inbox-triage.md +61 -0
  87. package/plugins/maestro-skills/skills/morning-brief.md +54 -0
  88. package/plugins/maestro-skills/skills/pipeline-review.md +76 -0
  89. package/plugins/maestro-skills/skills/regulatory-status.md +81 -0
  90. package/plugins/maestro-skills/skills/schedule-meeting.md +91 -0
  91. package/plugins/maestro-skills/skills/slack-followup.md +64 -0
  92. package/plugins/maestro-skills/skills/weekly-memo.md +70 -0
  93. package/policies/action-classification.yaml +110 -0
  94. package/policies/information-barriers.yaml +119 -0
  95. package/policies/prompt-injection-defence.yaml +138 -0
  96. package/public/assets/adaptic-icon-dark.png +0 -0
  97. package/public/assets/adaptic-icon-dark.svg +4 -0
  98. package/public/assets/adaptic-icon-light.svg +4 -0
  99. package/public/assets/adaptic-logo-dark.svg +17 -0
  100. package/public/assets/adaptic-logo-light.svg +17 -0
  101. package/scaffold/CLAUDE.md +21 -0
  102. package/scaffold/config/agent.ts +69 -0
  103. package/scaffold/config/agent.ts.example +218 -0
  104. package/schedules/README.md +49 -0
  105. package/schedules/triggers/backlog-executor.md +102 -0
  106. package/schedules/triggers/daily-evening-wrap.md +142 -0
  107. package/schedules/triggers/daily-midday-sweep.md +58 -0
  108. package/schedules/triggers/daily-morning-brief.md +55 -0
  109. package/schedules/triggers/inbox-processor.md +115 -0
  110. package/schedules/triggers/meeting-action-capture.md +60 -0
  111. package/schedules/triggers/meeting-prep.md +69 -0
  112. package/schedules/triggers/quarterly-self-assessment.md +54 -0
  113. package/schedules/triggers/weekly-engineering-health.md +37 -0
  114. package/schedules/triggers/weekly-execution.md +67 -0
  115. package/schedules/triggers/weekly-hiring.md +53 -0
  116. package/schedules/triggers/weekly-priorities.md +38 -0
  117. package/schedules/triggers/weekly-strategic-memo.md +124 -0
  118. package/scripts/__pycache__/disclosure_assessment.cpython-313.pyc +0 -0
  119. package/scripts/__pycache__/disclosure_boundaries.cpython-313.pyc +0 -0
  120. package/scripts/__pycache__/email_quote_thread.cpython-313.pyc +0 -0
  121. package/scripts/__pycache__/email_thread_dedup.cpython-313.pyc +0 -0
  122. package/scripts/__pycache__/mehran-inbox-poller.cpython-313.pyc +0 -0
  123. package/scripts/__pycache__/outbound_dedup.cpython-313.pyc +0 -0
  124. package/scripts/__pycache__/pre-draft-context.cpython-313.pyc +0 -0
  125. package/scripts/__pycache__/pre_draft_lookup.cpython-313.pyc +0 -0
  126. package/scripts/__pycache__/send-email-as-mehran.cpython-313.pyc +0 -0
  127. package/scripts/__pycache__/send-email-threaded.cpython-313.pyc +0 -0
  128. package/scripts/__pycache__/send-email-with-attachment.cpython-313.pyc +0 -0
  129. package/scripts/__pycache__/validate_outbound.cpython-313.pyc +0 -0
  130. package/scripts/airtable-crm-populate.md +99 -0
  131. package/scripts/archive-email.sh +41 -0
  132. package/scripts/comms-monitor.sh +285 -0
  133. package/scripts/configure-whatsapp-sandbox.sh +201 -0
  134. package/scripts/continuous-monitor.sh +86 -0
  135. package/scripts/daemon/classifier.mjs +355 -0
  136. package/scripts/daemon/context-compiler.mjs +490 -0
  137. package/scripts/daemon/dispatcher.mjs +385 -0
  138. package/scripts/daemon/health.mjs +72 -0
  139. package/scripts/daemon/prompt-builder.mjs +426 -0
  140. package/scripts/daemon/responder.mjs +547 -0
  141. package/scripts/daemon/session-lock.mjs +520 -0
  142. package/scripts/daemon/sophie-daemon.mjs +521 -0
  143. package/scripts/daemon/test-context-compiler.mjs +238 -0
  144. package/scripts/daemon/test-integration.mjs +130 -0
  145. package/scripts/daemon/test-session-lock.mjs +215 -0
  146. package/scripts/disclosure_assessment.py +873 -0
  147. package/scripts/disclosure_boundaries.py +562 -0
  148. package/scripts/email-signature-mehran.html +52 -0
  149. package/scripts/email-signature.html +44 -0
  150. package/scripts/email_quote_thread.py +167 -0
  151. package/scripts/email_thread_dedup.py +361 -0
  152. package/scripts/emergency-stop.sh +41 -0
  153. package/scripts/healthcheck.sh +110 -0
  154. package/scripts/hooks/block-mcp-slack-send.sh +7 -0
  155. package/scripts/hooks/post-action-log.sh +17 -0
  156. package/scripts/hooks/pre-send-audit.sh +57 -0
  157. package/scripts/hooks/session-end-log.sh +27 -0
  158. package/scripts/huddle/audio-bridge.mjs +664 -0
  159. package/scripts/huddle/boot-slack-cdp.sh +102 -0
  160. package/scripts/huddle/huddle-controller.mjs +942 -0
  161. package/scripts/huddle/huddle-server.mjs +1059 -0
  162. package/scripts/huddle/launch-slack.sh +232 -0
  163. package/scripts/huddle/node_modules/.package-lock.json +50 -0
  164. package/scripts/huddle/node_modules/@anthropic-ai/sdk/CHANGELOG.md +1677 -0
  165. package/scripts/huddle/node_modules/@anthropic-ai/sdk/LICENSE +8 -0
  166. package/scripts/huddle/node_modules/@anthropic-ai/sdk/README.md +674 -0
  167. package/scripts/huddle/node_modules/@anthropic-ai/sdk/_vendor/partial-json-parser/parser.d.mts +3 -0
  168. package/scripts/huddle/node_modules/@anthropic-ai/sdk/_vendor/partial-json-parser/parser.d.mts.map +1 -0
  169. package/scripts/huddle/node_modules/@anthropic-ai/sdk/_vendor/partial-json-parser/parser.d.ts +3 -0
  170. package/scripts/huddle/node_modules/@anthropic-ai/sdk/_vendor/partial-json-parser/parser.d.ts.map +1 -0
  171. package/scripts/huddle/node_modules/@anthropic-ai/sdk/_vendor/partial-json-parser/parser.js +226 -0
  172. package/scripts/huddle/node_modules/@anthropic-ai/sdk/_vendor/partial-json-parser/parser.js.map +1 -0
  173. package/scripts/huddle/node_modules/@anthropic-ai/sdk/_vendor/partial-json-parser/parser.mjs +223 -0
  174. package/scripts/huddle/node_modules/@anthropic-ai/sdk/_vendor/partial-json-parser/parser.mjs.map +1 -0
  175. package/scripts/huddle/node_modules/@anthropic-ai/sdk/api-promise.d.mts +2 -0
  176. package/scripts/huddle/node_modules/@anthropic-ai/sdk/api-promise.d.mts.map +1 -0
  177. package/scripts/huddle/node_modules/@anthropic-ai/sdk/api-promise.d.ts +2 -0
  178. package/scripts/huddle/node_modules/@anthropic-ai/sdk/api-promise.d.ts.map +1 -0
  179. package/scripts/huddle/node_modules/@anthropic-ai/sdk/api-promise.js +6 -0
  180. package/scripts/huddle/node_modules/@anthropic-ai/sdk/api-promise.js.map +1 -0
  181. package/scripts/huddle/node_modules/@anthropic-ai/sdk/api-promise.mjs +2 -0
  182. package/scripts/huddle/node_modules/@anthropic-ai/sdk/api-promise.mjs.map +1 -0
  183. package/scripts/huddle/node_modules/@anthropic-ai/sdk/bin/cli +53 -0
  184. package/scripts/huddle/node_modules/@anthropic-ai/sdk/bin/migration-config.json +7 -0
  185. package/scripts/huddle/node_modules/@anthropic-ai/sdk/client.d.mts +225 -0
  186. package/scripts/huddle/node_modules/@anthropic-ai/sdk/client.d.mts.map +1 -0
  187. package/scripts/huddle/node_modules/@anthropic-ai/sdk/client.d.ts +225 -0
  188. package/scripts/huddle/node_modules/@anthropic-ai/sdk/client.d.ts.map +1 -0
  189. package/scripts/huddle/node_modules/@anthropic-ai/sdk/client.js +536 -0
  190. package/scripts/huddle/node_modules/@anthropic-ai/sdk/client.js.map +1 -0
  191. package/scripts/huddle/node_modules/@anthropic-ai/sdk/client.mjs +531 -0
  192. package/scripts/huddle/node_modules/@anthropic-ai/sdk/client.mjs.map +1 -0
  193. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/api-promise.d.mts +49 -0
  194. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/api-promise.d.mts.map +1 -0
  195. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/api-promise.d.ts +49 -0
  196. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/api-promise.d.ts.map +1 -0
  197. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/api-promise.js +76 -0
  198. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/api-promise.js.map +1 -0
  199. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/api-promise.mjs +72 -0
  200. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/api-promise.mjs.map +1 -0
  201. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/error.d.mts +47 -0
  202. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/error.d.mts.map +1 -0
  203. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/error.d.ts +47 -0
  204. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/error.d.ts.map +1 -0
  205. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/error.js +114 -0
  206. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/error.js.map +1 -0
  207. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/error.mjs +98 -0
  208. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/error.mjs.map +1 -0
  209. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/pagination.d.mts +63 -0
  210. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/pagination.d.mts.map +1 -0
  211. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/pagination.d.ts +63 -0
  212. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/pagination.d.ts.map +1 -0
  213. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/pagination.js +123 -0
  214. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/pagination.js.map +1 -0
  215. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/pagination.mjs +117 -0
  216. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/pagination.mjs.map +1 -0
  217. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/resource.d.mts +6 -0
  218. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/resource.d.mts.map +1 -0
  219. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/resource.d.ts +6 -0
  220. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/resource.d.ts.map +1 -0
  221. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/resource.js +11 -0
  222. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/resource.js.map +1 -0
  223. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/resource.mjs +7 -0
  224. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/resource.mjs.map +1 -0
  225. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/streaming.d.mts +31 -0
  226. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/streaming.d.mts.map +1 -0
  227. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/streaming.d.ts +31 -0
  228. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/streaming.d.ts.map +1 -0
  229. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/streaming.js +282 -0
  230. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/streaming.js.map +1 -0
  231. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/streaming.mjs +277 -0
  232. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/streaming.mjs.map +1 -0
  233. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/uploads.d.mts +3 -0
  234. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/uploads.d.mts.map +1 -0
  235. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/uploads.d.ts +3 -0
  236. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/uploads.d.ts.map +1 -0
  237. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/uploads.js +6 -0
  238. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/uploads.js.map +1 -0
  239. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/uploads.mjs +2 -0
  240. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/uploads.mjs.map +1 -0
  241. package/scripts/huddle/node_modules/@anthropic-ai/sdk/error.d.mts +2 -0
  242. package/scripts/huddle/node_modules/@anthropic-ai/sdk/error.d.mts.map +1 -0
  243. package/scripts/huddle/node_modules/@anthropic-ai/sdk/error.d.ts +2 -0
  244. package/scripts/huddle/node_modules/@anthropic-ai/sdk/error.d.ts.map +1 -0
  245. package/scripts/huddle/node_modules/@anthropic-ai/sdk/error.js +6 -0
  246. package/scripts/huddle/node_modules/@anthropic-ai/sdk/error.js.map +1 -0
  247. package/scripts/huddle/node_modules/@anthropic-ai/sdk/error.mjs +2 -0
  248. package/scripts/huddle/node_modules/@anthropic-ai/sdk/error.mjs.map +1 -0
  249. package/scripts/huddle/node_modules/@anthropic-ai/sdk/index.d.mts +7 -0
  250. package/scripts/huddle/node_modules/@anthropic-ai/sdk/index.d.mts.map +1 -0
  251. package/scripts/huddle/node_modules/@anthropic-ai/sdk/index.d.ts +7 -0
  252. package/scripts/huddle/node_modules/@anthropic-ai/sdk/index.d.ts.map +1 -0
  253. package/scripts/huddle/node_modules/@anthropic-ai/sdk/index.js +35 -0
  254. package/scripts/huddle/node_modules/@anthropic-ai/sdk/index.js.map +1 -0
  255. package/scripts/huddle/node_modules/@anthropic-ai/sdk/index.mjs +8 -0
  256. package/scripts/huddle/node_modules/@anthropic-ai/sdk/index.mjs.map +1 -0
  257. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/builtin-types.d.mts +73 -0
  258. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/builtin-types.d.mts.map +1 -0
  259. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/builtin-types.d.ts +73 -0
  260. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/builtin-types.d.ts.map +1 -0
  261. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/builtin-types.js +4 -0
  262. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/builtin-types.js.map +1 -0
  263. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/builtin-types.mjs +3 -0
  264. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/builtin-types.mjs.map +1 -0
  265. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/constants.d.mts +5 -0
  266. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/constants.d.mts.map +1 -0
  267. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/constants.d.ts +5 -0
  268. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/constants.d.ts.map +1 -0
  269. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/constants.js +15 -0
  270. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/constants.js.map +1 -0
  271. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/constants.mjs +12 -0
  272. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/constants.mjs.map +1 -0
  273. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/jsonl.d.mts +10 -0
  274. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/jsonl.d.mts.map +1 -0
  275. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/jsonl.d.ts +10 -0
  276. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/jsonl.d.ts.map +1 -0
  277. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/jsonl.js +39 -0
  278. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/jsonl.js.map +1 -0
  279. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/jsonl.mjs +35 -0
  280. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/jsonl.mjs.map +1 -0
  281. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/line.d.mts +17 -0
  282. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/line.d.mts.map +1 -0
  283. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/line.d.ts +17 -0
  284. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/line.d.ts.map +1 -0
  285. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/line.js +113 -0
  286. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/line.js.map +1 -0
  287. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/line.mjs +108 -0
  288. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/line.mjs.map +1 -0
  289. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/detect-platform.d.mts +15 -0
  290. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/detect-platform.d.mts.map +1 -0
  291. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/detect-platform.d.ts +15 -0
  292. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/detect-platform.d.ts.map +1 -0
  293. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/detect-platform.js +162 -0
  294. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/detect-platform.js.map +1 -0
  295. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/detect-platform.mjs +157 -0
  296. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/detect-platform.mjs.map +1 -0
  297. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/errors.d.mts +3 -0
  298. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/errors.d.mts.map +1 -0
  299. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/errors.d.ts +3 -0
  300. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/errors.d.ts.map +1 -0
  301. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/errors.js +41 -0
  302. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/errors.js.map +1 -0
  303. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/errors.mjs +36 -0
  304. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/errors.mjs.map +1 -0
  305. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/headers.d.mts +22 -0
  306. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/headers.d.mts.map +1 -0
  307. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/headers.d.ts +22 -0
  308. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/headers.d.ts.map +1 -0
  309. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/headers.js +79 -0
  310. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/headers.js.map +1 -0
  311. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/headers.mjs +74 -0
  312. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/headers.mjs.map +1 -0
  313. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/parse.d.mts +17 -0
  314. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/parse.d.mts.map +1 -0
  315. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/parse.d.ts +17 -0
  316. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/parse.d.ts.map +1 -0
  317. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/parse.js +55 -0
  318. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/parse.js.map +1 -0
  319. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/parse.mjs +51 -0
  320. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/parse.mjs.map +1 -0
  321. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/request-options.d.mts +34 -0
  322. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/request-options.d.mts.map +1 -0
  323. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/request-options.d.ts +34 -0
  324. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/request-options.d.ts.map +1 -0
  325. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/request-options.js +14 -0
  326. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/request-options.js.map +1 -0
  327. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/request-options.mjs +10 -0
  328. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/request-options.mjs.map +1 -0
  329. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/shim-types.d.mts +28 -0
  330. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/shim-types.d.ts +28 -0
  331. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/shims.d.mts +20 -0
  332. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/shims.d.mts.map +1 -0
  333. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/shims.d.ts +20 -0
  334. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/shims.d.ts.map +1 -0
  335. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/shims.js +92 -0
  336. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/shims.js.map +1 -0
  337. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/shims.mjs +85 -0
  338. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/shims.mjs.map +1 -0
  339. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/stream-utils.d.mts +8 -0
  340. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/stream-utils.d.mts.map +1 -0
  341. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/stream-utils.d.ts +8 -0
  342. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/stream-utils.d.ts.map +1 -0
  343. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/stream-utils.js +38 -0
  344. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/stream-utils.js.map +1 -0
  345. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/stream-utils.mjs +35 -0
  346. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/stream-utils.mjs.map +1 -0
  347. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/to-file.d.mts +45 -0
  348. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/to-file.d.mts.map +1 -0
  349. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/to-file.d.ts +45 -0
  350. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/to-file.d.ts.map +1 -0
  351. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/to-file.js +96 -0
  352. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/to-file.js.map +1 -0
  353. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/to-file.mjs +93 -0
  354. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/to-file.mjs.map +1 -0
  355. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/tslib.js +81 -0
  356. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/tslib.mjs +17 -0
  357. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/types.d.mts +67 -0
  358. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/types.d.mts.map +1 -0
  359. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/types.d.ts +67 -0
  360. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/types.d.ts.map +1 -0
  361. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/types.js +4 -0
  362. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/types.js.map +1 -0
  363. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/types.mjs +3 -0
  364. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/types.mjs.map +1 -0
  365. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/uploads.d.mts +42 -0
  366. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/uploads.d.mts.map +1 -0
  367. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/uploads.d.ts +42 -0
  368. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/uploads.d.ts.map +1 -0
  369. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/uploads.js +146 -0
  370. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/uploads.js.map +1 -0
  371. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/uploads.mjs +136 -0
  372. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/uploads.mjs.map +1 -0
  373. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/base64.d.mts +3 -0
  374. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/base64.d.mts.map +1 -0
  375. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/base64.d.ts +3 -0
  376. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/base64.d.ts.map +1 -0
  377. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/base64.js +38 -0
  378. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/base64.js.map +1 -0
  379. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/base64.mjs +33 -0
  380. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/base64.mjs.map +1 -0
  381. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/bytes.d.mts +4 -0
  382. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/bytes.d.mts.map +1 -0
  383. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/bytes.d.ts +4 -0
  384. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/bytes.d.ts.map +1 -0
  385. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/bytes.js +31 -0
  386. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/bytes.js.map +1 -0
  387. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/bytes.mjs +26 -0
  388. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/bytes.mjs.map +1 -0
  389. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/env.d.mts +9 -0
  390. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/env.d.mts.map +1 -0
  391. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/env.d.ts +9 -0
  392. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/env.d.ts.map +1 -0
  393. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/env.js +22 -0
  394. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/env.js.map +1 -0
  395. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/env.mjs +18 -0
  396. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/env.mjs.map +1 -0
  397. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/log.d.mts +37 -0
  398. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/log.d.mts.map +1 -0
  399. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/log.d.ts +37 -0
  400. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/log.d.ts.map +1 -0
  401. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/log.js +86 -0
  402. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/log.js.map +1 -0
  403. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/log.mjs +80 -0
  404. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/log.mjs.map +1 -0
  405. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/path.d.mts +15 -0
  406. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/path.d.mts.map +1 -0
  407. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/path.d.ts +15 -0
  408. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/path.d.ts.map +1 -0
  409. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/path.js +58 -0
  410. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/path.js.map +1 -0
  411. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/path.mjs +53 -0
  412. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/path.mjs.map +1 -0
  413. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/sleep.d.mts +2 -0
  414. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/sleep.d.mts.map +1 -0
  415. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/sleep.d.ts +2 -0
  416. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/sleep.d.ts.map +1 -0
  417. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/sleep.js +7 -0
  418. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/sleep.js.map +1 -0
  419. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/sleep.mjs +3 -0
  420. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/sleep.mjs.map +1 -0
  421. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/uuid.d.mts +5 -0
  422. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/uuid.d.mts.map +1 -0
  423. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/uuid.d.ts +5 -0
  424. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/uuid.d.ts.map +1 -0
  425. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/uuid.js +19 -0
  426. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/uuid.js.map +1 -0
  427. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/uuid.mjs +15 -0
  428. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/uuid.mjs.map +1 -0
  429. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/values.d.mts +16 -0
  430. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/values.d.mts.map +1 -0
  431. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/values.d.ts +16 -0
  432. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/values.d.ts.map +1 -0
  433. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/values.js +109 -0
  434. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/values.js.map +1 -0
  435. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/values.mjs +92 -0
  436. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/values.mjs.map +1 -0
  437. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils.d.mts +7 -0
  438. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils.d.mts.map +1 -0
  439. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils.d.ts +7 -0
  440. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils.d.ts.map +1 -0
  441. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils.js +11 -0
  442. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils.js.map +1 -0
  443. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils.mjs +8 -0
  444. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils.mjs.map +1 -0
  445. package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/BetaMessageStream.d.mts +114 -0
  446. package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/BetaMessageStream.d.mts.map +1 -0
  447. package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/BetaMessageStream.d.ts +114 -0
  448. package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/BetaMessageStream.d.ts.map +1 -0
  449. package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/BetaMessageStream.js +553 -0
  450. package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/BetaMessageStream.js.map +1 -0
  451. package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/BetaMessageStream.mjs +549 -0
  452. package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/BetaMessageStream.mjs.map +1 -0
  453. package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/MessageStream.d.mts +114 -0
  454. package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/MessageStream.d.mts.map +1 -0
  455. package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/MessageStream.d.ts +114 -0
  456. package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/MessageStream.d.ts.map +1 -0
  457. package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/MessageStream.js +553 -0
  458. package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/MessageStream.js.map +1 -0
  459. package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/MessageStream.mjs +549 -0
  460. package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/MessageStream.mjs.map +1 -0
  461. package/scripts/huddle/node_modules/@anthropic-ai/sdk/package.json +185 -0
  462. package/scripts/huddle/node_modules/@anthropic-ai/sdk/pagination.d.mts +2 -0
  463. package/scripts/huddle/node_modules/@anthropic-ai/sdk/pagination.d.mts.map +1 -0
  464. package/scripts/huddle/node_modules/@anthropic-ai/sdk/pagination.d.ts +2 -0
  465. package/scripts/huddle/node_modules/@anthropic-ai/sdk/pagination.d.ts.map +1 -0
  466. package/scripts/huddle/node_modules/@anthropic-ai/sdk/pagination.js +6 -0
  467. package/scripts/huddle/node_modules/@anthropic-ai/sdk/pagination.js.map +1 -0
  468. package/scripts/huddle/node_modules/@anthropic-ai/sdk/pagination.mjs +2 -0
  469. package/scripts/huddle/node_modules/@anthropic-ai/sdk/pagination.mjs.map +1 -0
  470. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resource.d.mts +2 -0
  471. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resource.d.mts.map +1 -0
  472. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resource.d.ts +2 -0
  473. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resource.d.ts.map +1 -0
  474. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resource.js +6 -0
  475. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resource.js.map +1 -0
  476. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resource.mjs +2 -0
  477. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resource.mjs.map +1 -0
  478. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/beta.d.mts +61 -0
  479. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/beta.d.mts.map +1 -0
  480. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/beta.d.ts +61 -0
  481. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/beta.d.ts.map +1 -0
  482. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/beta.js +25 -0
  483. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/beta.js.map +1 -0
  484. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/beta.mjs +20 -0
  485. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/beta.mjs.map +1 -0
  486. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/files.d.mts +151 -0
  487. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/files.d.mts.map +1 -0
  488. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/files.d.ts +151 -0
  489. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/files.d.ts.map +1 -0
  490. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/files.js +122 -0
  491. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/files.js.map +1 -0
  492. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/files.mjs +118 -0
  493. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/files.mjs.map +1 -0
  494. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/index.d.mts +5 -0
  495. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/index.d.mts.map +1 -0
  496. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/index.d.ts +5 -0
  497. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/index.d.ts.map +1 -0
  498. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/index.js +13 -0
  499. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/index.js.map +1 -0
  500. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/index.mjs +6 -0
  501. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/index.mjs.map +1 -0
  502. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/batches.d.mts +343 -0
  503. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/batches.d.mts.map +1 -0
  504. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/batches.d.ts +343 -0
  505. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/batches.d.ts.map +1 -0
  506. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/batches.js +204 -0
  507. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/batches.js.map +1 -0
  508. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/batches.mjs +200 -0
  509. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/batches.mjs.map +1 -0
  510. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/index.d.mts +3 -0
  511. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/index.d.mts.map +1 -0
  512. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/index.d.ts +3 -0
  513. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/index.d.ts.map +1 -0
  514. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/index.js +9 -0
  515. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/index.js.map +1 -0
  516. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/index.mjs +4 -0
  517. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/index.mjs.map +1 -0
  518. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/messages.d.mts +1561 -0
  519. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/messages.d.mts.map +1 -0
  520. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/messages.d.ts +1561 -0
  521. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/messages.d.ts.map +1 -0
  522. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/messages.js +86 -0
  523. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/messages.js.map +1 -0
  524. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/messages.mjs +81 -0
  525. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/messages.mjs.map +1 -0
  526. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages.d.mts +2 -0
  527. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages.d.mts.map +1 -0
  528. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages.d.ts +2 -0
  529. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages.d.ts.map +1 -0
  530. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages.js +6 -0
  531. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages.js.map +1 -0
  532. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages.mjs +3 -0
  533. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages.mjs.map +1 -0
  534. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/models.d.mts +74 -0
  535. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/models.d.mts.map +1 -0
  536. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/models.d.ts +74 -0
  537. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/models.d.ts.map +1 -0
  538. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/models.js +60 -0
  539. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/models.js.map +1 -0
  540. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/models.mjs +56 -0
  541. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/models.mjs.map +1 -0
  542. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta.d.mts +2 -0
  543. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta.d.mts.map +1 -0
  544. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta.d.ts +2 -0
  545. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta.d.ts.map +1 -0
  546. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta.js +6 -0
  547. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta.js.map +1 -0
  548. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta.mjs +3 -0
  549. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta.mjs.map +1 -0
  550. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/completions.d.mts +183 -0
  551. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/completions.d.mts.map +1 -0
  552. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/completions.d.ts +183 -0
  553. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/completions.d.ts.map +1 -0
  554. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/completions.js +23 -0
  555. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/completions.js.map +1 -0
  556. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/completions.mjs +19 -0
  557. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/completions.mjs.map +1 -0
  558. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/index.d.mts +6 -0
  559. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/index.d.mts.map +1 -0
  560. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/index.d.ts +6 -0
  561. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/index.d.ts.map +1 -0
  562. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/index.js +15 -0
  563. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/index.js.map +1 -0
  564. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/index.mjs +7 -0
  565. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/index.mjs.map +1 -0
  566. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/batches.d.mts +304 -0
  567. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/batches.d.mts.map +1 -0
  568. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/batches.d.ts +304 -0
  569. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/batches.d.ts.map +1 -0
  570. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/batches.js +153 -0
  571. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/batches.js.map +1 -0
  572. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/batches.mjs +149 -0
  573. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/batches.mjs.map +1 -0
  574. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/index.d.mts +3 -0
  575. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/index.d.mts.map +1 -0
  576. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/index.d.ts +3 -0
  577. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/index.d.ts.map +1 -0
  578. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/index.js +9 -0
  579. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/index.js.map +1 -0
  580. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/index.mjs +4 -0
  581. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/index.mjs.map +1 -0
  582. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/messages.d.mts +1264 -0
  583. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/messages.d.mts.map +1 -0
  584. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/messages.d.ts +1264 -0
  585. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/messages.d.ts.map +1 -0
  586. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/messages.js +72 -0
  587. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/messages.js.map +1 -0
  588. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/messages.mjs +67 -0
  589. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/messages.mjs.map +1 -0
  590. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages.d.mts +2 -0
  591. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages.d.mts.map +1 -0
  592. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages.d.ts +2 -0
  593. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages.d.ts.map +1 -0
  594. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages.js +6 -0
  595. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages.js.map +1 -0
  596. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages.mjs +3 -0
  597. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages.mjs.map +1 -0
  598. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/models.d.mts +59 -0
  599. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/models.d.mts.map +1 -0
  600. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/models.d.ts +59 -0
  601. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/models.d.ts.map +1 -0
  602. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/models.js +45 -0
  603. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/models.js.map +1 -0
  604. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/models.mjs +41 -0
  605. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/models.mjs.map +1 -0
  606. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/shared.d.mts +42 -0
  607. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/shared.d.mts.map +1 -0
  608. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/shared.d.ts +42 -0
  609. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/shared.d.ts.map +1 -0
  610. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/shared.js +4 -0
  611. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/shared.js.map +1 -0
  612. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/shared.mjs +3 -0
  613. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/shared.mjs.map +1 -0
  614. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/top-level.d.mts +2 -0
  615. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/top-level.d.mts.map +1 -0
  616. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/top-level.d.ts +2 -0
  617. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/top-level.d.ts.map +1 -0
  618. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/top-level.js +4 -0
  619. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/top-level.js.map +1 -0
  620. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/top-level.mjs +3 -0
  621. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/top-level.mjs.map +1 -0
  622. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources.d.mts +2 -0
  623. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources.d.mts.map +1 -0
  624. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources.d.ts +2 -0
  625. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources.d.ts.map +1 -0
  626. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources.js +5 -0
  627. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources.js.map +1 -0
  628. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources.mjs +2 -0
  629. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources.mjs.map +1 -0
  630. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/_vendor/partial-json-parser/README.md +3 -0
  631. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/_vendor/partial-json-parser/parser.ts +264 -0
  632. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/api-promise.ts +2 -0
  633. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/client.ts +1070 -0
  634. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/core/README.md +3 -0
  635. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/core/api-promise.ts +101 -0
  636. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/core/error.ts +133 -0
  637. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/core/pagination.ts +201 -0
  638. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/core/resource.ts +11 -0
  639. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/core/streaming.ts +331 -0
  640. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/core/uploads.ts +2 -0
  641. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/error.ts +2 -0
  642. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/index.ts +23 -0
  643. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/README.md +3 -0
  644. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/builtin-types.ts +93 -0
  645. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/constants.ts +12 -0
  646. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/decoders/jsonl.ts +48 -0
  647. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/decoders/line.ts +135 -0
  648. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/detect-platform.ts +196 -0
  649. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/errors.ts +33 -0
  650. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/headers.ts +99 -0
  651. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/parse.ts +84 -0
  652. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/request-options.ts +39 -0
  653. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/shim-types.d.ts +28 -0
  654. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/shims.ts +107 -0
  655. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/stream-utils.ts +32 -0
  656. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/to-file.ts +159 -0
  657. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/types.ts +92 -0
  658. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/uploads.ts +193 -0
  659. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/utils/base64.ts +40 -0
  660. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/utils/bytes.ts +32 -0
  661. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/utils/env.ts +18 -0
  662. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/utils/log.ts +127 -0
  663. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/utils/path.ts +65 -0
  664. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/utils/sleep.ts +3 -0
  665. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/utils/uuid.ts +17 -0
  666. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/utils/values.ts +102 -0
  667. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/utils.ts +8 -0
  668. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/lib/.keep +4 -0
  669. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/lib/BetaMessageStream.ts +683 -0
  670. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/lib/MessageStream.ts +684 -0
  671. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/pagination.ts +2 -0
  672. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resource.ts +2 -0
  673. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/beta/beta.ts +380 -0
  674. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/beta/files.ts +258 -0
  675. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/beta/index.ts +148 -0
  676. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/beta/messages/batches.ts +502 -0
  677. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/beta/messages/index.ts +135 -0
  678. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/beta/messages/messages.ts +2249 -0
  679. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/beta/messages.ts +3 -0
  680. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/beta/models.ts +118 -0
  681. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/beta.ts +3 -0
  682. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/completions.ts +231 -0
  683. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/index.ts +121 -0
  684. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/messages/batches.ts +396 -0
  685. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/messages/index.ts +110 -0
  686. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/messages/messages.ts +1783 -0
  687. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/messages.ts +3 -0
  688. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/models.ts +103 -0
  689. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/shared.ts +72 -0
  690. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/top-level.ts +3 -0
  691. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources.ts +1 -0
  692. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/streaming.ts +2 -0
  693. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/tsconfig.json +11 -0
  694. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/uploads.ts +2 -0
  695. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/version.ts +1 -0
  696. package/scripts/huddle/node_modules/@anthropic-ai/sdk/streaming.d.mts +2 -0
  697. package/scripts/huddle/node_modules/@anthropic-ai/sdk/streaming.d.mts.map +1 -0
  698. package/scripts/huddle/node_modules/@anthropic-ai/sdk/streaming.d.ts +2 -0
  699. package/scripts/huddle/node_modules/@anthropic-ai/sdk/streaming.d.ts.map +1 -0
  700. package/scripts/huddle/node_modules/@anthropic-ai/sdk/streaming.js +6 -0
  701. package/scripts/huddle/node_modules/@anthropic-ai/sdk/streaming.js.map +1 -0
  702. package/scripts/huddle/node_modules/@anthropic-ai/sdk/streaming.mjs +2 -0
  703. package/scripts/huddle/node_modules/@anthropic-ai/sdk/streaming.mjs.map +1 -0
  704. package/scripts/huddle/node_modules/@anthropic-ai/sdk/uploads.d.mts +2 -0
  705. package/scripts/huddle/node_modules/@anthropic-ai/sdk/uploads.d.mts.map +1 -0
  706. package/scripts/huddle/node_modules/@anthropic-ai/sdk/uploads.d.ts +2 -0
  707. package/scripts/huddle/node_modules/@anthropic-ai/sdk/uploads.d.ts.map +1 -0
  708. package/scripts/huddle/node_modules/@anthropic-ai/sdk/uploads.js +6 -0
  709. package/scripts/huddle/node_modules/@anthropic-ai/sdk/uploads.js.map +1 -0
  710. package/scripts/huddle/node_modules/@anthropic-ai/sdk/uploads.mjs +2 -0
  711. package/scripts/huddle/node_modules/@anthropic-ai/sdk/uploads.mjs.map +1 -0
  712. package/scripts/huddle/node_modules/@anthropic-ai/sdk/version.d.mts +2 -0
  713. package/scripts/huddle/node_modules/@anthropic-ai/sdk/version.d.mts.map +1 -0
  714. package/scripts/huddle/node_modules/@anthropic-ai/sdk/version.d.ts +2 -0
  715. package/scripts/huddle/node_modules/@anthropic-ai/sdk/version.d.ts.map +1 -0
  716. package/scripts/huddle/node_modules/@anthropic-ai/sdk/version.js +5 -0
  717. package/scripts/huddle/node_modules/@anthropic-ai/sdk/version.js.map +1 -0
  718. package/scripts/huddle/node_modules/@anthropic-ai/sdk/version.mjs +2 -0
  719. package/scripts/huddle/node_modules/@anthropic-ai/sdk/version.mjs.map +1 -0
  720. package/scripts/huddle/node_modules/dotenv/CHANGELOG.md +520 -0
  721. package/scripts/huddle/node_modules/dotenv/LICENSE +23 -0
  722. package/scripts/huddle/node_modules/dotenv/README-es.md +411 -0
  723. package/scripts/huddle/node_modules/dotenv/README.md +645 -0
  724. package/scripts/huddle/node_modules/dotenv/SECURITY.md +1 -0
  725. package/scripts/huddle/node_modules/dotenv/config.d.ts +1 -0
  726. package/scripts/huddle/node_modules/dotenv/config.js +9 -0
  727. package/scripts/huddle/node_modules/dotenv/lib/cli-options.js +17 -0
  728. package/scripts/huddle/node_modules/dotenv/lib/env-options.js +28 -0
  729. package/scripts/huddle/node_modules/dotenv/lib/main.d.ts +162 -0
  730. package/scripts/huddle/node_modules/dotenv/lib/main.js +386 -0
  731. package/scripts/huddle/node_modules/dotenv/package.json +62 -0
  732. package/scripts/huddle/node_modules/ws/LICENSE +20 -0
  733. package/scripts/huddle/node_modules/ws/README.md +548 -0
  734. package/scripts/huddle/node_modules/ws/browser.js +8 -0
  735. package/scripts/huddle/node_modules/ws/index.js +22 -0
  736. package/scripts/huddle/node_modules/ws/lib/buffer-util.js +131 -0
  737. package/scripts/huddle/node_modules/ws/lib/constants.js +19 -0
  738. package/scripts/huddle/node_modules/ws/lib/event-target.js +292 -0
  739. package/scripts/huddle/node_modules/ws/lib/extension.js +203 -0
  740. package/scripts/huddle/node_modules/ws/lib/limiter.js +55 -0
  741. package/scripts/huddle/node_modules/ws/lib/permessage-deflate.js +528 -0
  742. package/scripts/huddle/node_modules/ws/lib/receiver.js +706 -0
  743. package/scripts/huddle/node_modules/ws/lib/sender.js +602 -0
  744. package/scripts/huddle/node_modules/ws/lib/stream.js +161 -0
  745. package/scripts/huddle/node_modules/ws/lib/subprotocol.js +62 -0
  746. package/scripts/huddle/node_modules/ws/lib/validation.js +152 -0
  747. package/scripts/huddle/node_modules/ws/lib/websocket-server.js +554 -0
  748. package/scripts/huddle/node_modules/ws/lib/websocket.js +1393 -0
  749. package/scripts/huddle/node_modules/ws/package.json +70 -0
  750. package/scripts/huddle/node_modules/ws/wrapper.mjs +21 -0
  751. package/scripts/huddle/package-lock.json +62 -0
  752. package/scripts/huddle/package.json +22 -0
  753. package/scripts/huddle/setup-audio.sh +239 -0
  754. package/scripts/huddle/start-call.mjs +318 -0
  755. package/scripts/huddle/test-pipeline.mjs +263 -0
  756. package/scripts/llm_email_dedup.py +434 -0
  757. package/scripts/local-triggers/install-all.sh +43 -0
  758. package/scripts/local-triggers/plists/ai.adaptic.slack-events-server.plist +45 -0
  759. package/scripts/local-triggers/plists/ai.adaptic.sophie-backlog-executor.plist +21 -0
  760. package/scripts/local-triggers/plists/ai.adaptic.sophie-daemon.plist +32 -0
  761. package/scripts/local-triggers/plists/ai.adaptic.sophie-inbox-processor.plist +21 -0
  762. package/scripts/local-triggers/plists/ai.adaptic.sophie-meeting-action-capture.plist +21 -0
  763. package/scripts/local-triggers/plists/ai.adaptic.sophie-meeting-prep.plist +21 -0
  764. package/scripts/local-triggers/plists/ai.adaptic.sophie-midday-sweep.plist +26 -0
  765. package/scripts/local-triggers/plists/ai.adaptic.sophie-quarterly-self-assessment.plist +62 -0
  766. package/scripts/local-triggers/plists/ai.adaptic.sophie-weekly-engineering-health.plist +28 -0
  767. package/scripts/local-triggers/plists/ai.adaptic.sophie-weekly-execution.plist +28 -0
  768. package/scripts/local-triggers/plists/ai.adaptic.sophie-weekly-hiring.plist +28 -0
  769. package/scripts/local-triggers/plists/ai.adaptic.sophie-weekly-priorities.plist +28 -0
  770. package/scripts/local-triggers/plists/ai.adaptic.sophie-weekly-strategic-memo.plist +28 -0
  771. package/scripts/local-triggers/run-trigger.sh +44 -0
  772. package/scripts/media-generation/README.md +103 -0
  773. package/scripts/media-generation/gemini-image-client.mjs +173 -0
  774. package/scripts/media-generation/generate-assets.mjs +289 -0
  775. package/scripts/media-generation/veo-video-client.mjs +219 -0
  776. package/scripts/mehran-inbox-poller.py +437 -0
  777. package/scripts/outbound-dedup-cleanup.sh +43 -0
  778. package/scripts/outbound-dedup.sh +476 -0
  779. package/scripts/outbound_dedup.py +115 -0
  780. package/scripts/parse-voice-transcript.mjs +486 -0
  781. package/scripts/pdf-generation/README.md +61 -0
  782. package/scripts/pdf-generation/build-document.mjs +230 -0
  783. package/scripts/pdf-generation/templates/board-pack.latex +136 -0
  784. package/scripts/pdf-generation/templates/corporate-letter.latex +126 -0
  785. package/scripts/pdf-generation/templates/memo.latex +114 -0
  786. package/scripts/poll-slack-events.sh +33 -0
  787. package/scripts/poller/calendar-poller.mjs +12 -0
  788. package/scripts/poller/gmail-poller.mjs +156 -0
  789. package/scripts/poller/imap-client.mjs +286 -0
  790. package/scripts/poller/index.mjs +73 -0
  791. package/scripts/poller/intra-session-check.mjs +267 -0
  792. package/scripts/poller/mehran-gmail-poller.mjs +98 -0
  793. package/scripts/poller/slack-poller.mjs +716 -0
  794. package/scripts/poller/trigger.mjs +47 -0
  795. package/scripts/poller/utils.mjs +253 -0
  796. package/scripts/poller-launchd/ai.adaptic.sophie-poller.plist +40 -0
  797. package/scripts/poller-launchd/ai.adaptic.sophie-whatsapp-handler.plist +39 -0
  798. package/scripts/poller-launchd/install.sh +38 -0
  799. package/scripts/post-interaction-indexer.py +1598 -0
  800. package/scripts/pre-draft-context.py +994 -0
  801. package/scripts/pre_draft_lookup.py +258 -0
  802. package/scripts/rag-indexer.py +629 -0
  803. package/scripts/resume-operations.sh +40 -0
  804. package/scripts/search-mehran-inbox.py +181 -0
  805. package/scripts/self-optimization/compute-metrics.py +377 -0
  806. package/scripts/send-difc-rfp.sh +98 -0
  807. package/scripts/send-email-as-mehran.py +369 -0
  808. package/scripts/send-email-threaded.py +336 -0
  809. package/scripts/send-email-with-attachment.py +360 -0
  810. package/scripts/send-email.sh +93 -0
  811. package/scripts/send-sms.sh +151 -0
  812. package/scripts/send-whatsapp.sh +261 -0
  813. package/scripts/session-start.sh +106 -0
  814. package/scripts/setup/configure-macos.sh +508 -0
  815. package/scripts/setup/init-agent.sh +450 -0
  816. package/scripts/slack-events-ctl.sh +177 -0
  817. package/scripts/slack-events-server.mjs +989 -0
  818. package/scripts/slack-react.mjs +89 -0
  819. package/scripts/slack-responded.sh +185 -0
  820. package/scripts/slack-send.sh +190 -0
  821. package/scripts/slack-typing.mjs +196 -0
  822. package/scripts/slack-upload-v2.py +95 -0
  823. package/scripts/sms-handler.mjs +436 -0
  824. package/scripts/sophie-inbox-poller.py +406 -0
  825. package/scripts/spawn-session.sh +85 -0
  826. package/scripts/system-verify.sh +171 -0
  827. package/scripts/test-email-thread-dedup.py +239 -0
  828. package/scripts/test-information-barriers.py +484 -0
  829. package/scripts/test-llm-email-dedup.py +251 -0
  830. package/scripts/test-pre-draft-integration.py +203 -0
  831. package/scripts/test-rag-phase2.sh +442 -0
  832. package/scripts/test-rag-search.sh +251 -0
  833. package/scripts/test-voice-parser.mjs +316 -0
  834. package/scripts/user-context-search.py +660 -0
  835. package/scripts/validate-outbound.py +1493 -0
  836. package/scripts/whatsapp-handler.mjs +525 -0
  837. package/teams/desktop-operations.yaml +34 -0
  838. package/teams/executive-office.yaml +27 -0
  839. package/teams/legal-and-regulatory.yaml +24 -0
  840. package/teams/platform-and-engineering.yaml +23 -0
  841. package/teams/strategy-and-growth.yaml +29 -0
  842. package/workflows/continuous/inbound-monitor.yaml +168 -0
  843. package/workflows/daily/applicant-triage.yaml +197 -0
  844. package/workflows/daily/comms-triage.yaml +80 -0
  845. package/workflows/daily/evening-wrap.yaml +65 -0
  846. package/workflows/daily/morning-brief.yaml +147 -0
  847. package/workflows/daily/slack-followup-sweep.yaml +87 -0
  848. package/workflows/event-driven/README.md +50 -0
  849. package/workflows/monthly/board-readiness.yaml +76 -0
  850. package/workflows/quarterly/strategic-scenario-analysis.yaml +85 -0
  851. package/workflows/session-protocol.md +171 -0
  852. package/workflows/weekly/hiring-review.yaml +169 -0
  853. package/workflows/weekly/rollup-pipeline-review.yaml +76 -0
  854. package/workflows/weekly/strategic-memo.yaml +79 -0
@@ -0,0 +1,1493 @@
1
+ #!/usr/bin/env python3
2
+ """Pre-send outbound correspondence validator.
3
+
4
+ Checks outbound messages for:
5
+ 1. Relationship claims — "our project manager" must match confirmed status
6
+ 2. Title/role accuracy — titles must match org chart / profiles
7
+ 3. AI self-disclosure — Sophie must never reveal she is AI
8
+ 4. In-person meeting self-scheduling — Sophie must not attend physical meetings
9
+ 5. Unverified commitment claims — flag strong commitment language
10
+
11
+ Usage:
12
+ echo "message body" | python3 validate-outbound.py --type email --recipient graham@kpm.com
13
+ python3 validate-outbound.py --message "message body" --type slack --recipient C099ABCDEF
14
+
15
+ Exit codes:
16
+ 0 = clean (no blocking issues)
17
+ 1 = issues found (at least one "block" severity issue)
18
+
19
+ Outputs JSON to stdout:
20
+ {"status": "clean"|"issues_found", "issues": [...], "metadata": {...}}
21
+
22
+ Performance target: < 1 second (no API calls — file reads only).
23
+
24
+ Created: 2026-04-03 — triggered by ib-20260403-008 (Graham/KPM incident)
25
+ """
26
+ import argparse
27
+ import json
28
+ import os
29
+ import re
30
+ import sys
31
+ import time
32
+ from datetime import datetime, timezone
33
+ from pathlib import Path
34
+
35
+ # Import disclosure assessment module (Information Barrier Layer 2)
36
+ _da_module = None
37
+ try:
38
+ import importlib.util as _da_ilu
39
+ _da_path = Path(__file__).resolve().parent / "disclosure_assessment.py"
40
+ if _da_path.exists():
41
+ _da_spec = _da_ilu.spec_from_file_location("disclosure_assessment", str(_da_path))
42
+ _da_mod = _da_ilu.module_from_spec(_da_spec)
43
+ _da_spec.loader.exec_module(_da_mod)
44
+ _da_module = _da_mod
45
+ except Exception:
46
+ pass
47
+
48
+ # Resolve paths relative to the repo root
49
+ SCRIPT_DIR = Path(__file__).resolve().parent
50
+ REPO_ROOT = SCRIPT_DIR.parent
51
+
52
+ # Data source paths
53
+ CONTACTS_PATH = REPO_ROOT / "config" / "contacts.yaml"
54
+ ORG_CHART_PATH = REPO_ROOT / "knowledge" / "entities" / "org-chart.md"
55
+ USER_PROFILES_DIR = REPO_ROOT / "memory" / "profiles" / "users"
56
+ VALIDATION_RULES_PATH = REPO_ROOT / "config" / "validation-rules.yaml"
57
+ ENTITY_INDEX_PATH = REPO_ROOT / "memory" / "indexes" / "entity-relationships.yaml"
58
+ AUDIT_LOG_DIR = REPO_ROOT / "logs" / "audit"
59
+
60
+
61
+ # ─────────────────────────────────────────────
62
+ # Nested YAML parser (handles maps, lists, and scalar values)
63
+ # Replaces the original flat key-value parser that missed nested fields
64
+ # like relationship.type and relationship.status (Graham incident, G4)
65
+ # ─────────────────────────────────────────────
66
+ def _parse_yaml_simple(text):
67
+ """Parse YAML text into nested dicts/lists.
68
+
69
+ Handles:
70
+ - Nested mappings (indented key: value blocks)
71
+ - Lists (- item and - key: value)
72
+ - Quoted and unquoted scalar values
73
+ - Comments and blank lines
74
+
75
+ Returns a dict (possibly nested). Flat profiles still work —
76
+ a flat file returns a flat dict, same as before.
77
+ """
78
+ try:
79
+ import yaml as _yaml
80
+ return _yaml.safe_load(text) or {}
81
+ except ImportError:
82
+ pass
83
+
84
+ lines = text.split('\n')
85
+ return _parse_yaml_block(lines, 0, 0)[0]
86
+
87
+
88
+ def _parse_yaml_block(lines, start, base_indent):
89
+ """Recursively parse a YAML block starting at *start* with *base_indent*.
90
+
91
+ Returns (parsed_dict, next_line_index).
92
+ """
93
+ result = {}
94
+ i = start
95
+ while i < len(lines):
96
+ raw = lines[i]
97
+
98
+ # Skip blank lines and comments
99
+ stripped = raw.lstrip()
100
+ if not stripped or stripped.startswith('#'):
101
+ i += 1
102
+ continue
103
+
104
+ # Measure indentation
105
+ indent = len(raw) - len(raw.lstrip())
106
+
107
+ # If we've dedented past our block, stop
108
+ if indent < base_indent and i > start:
109
+ break
110
+
111
+ # Skip lines that are deeper than expected (handled recursively)
112
+ if indent > base_indent and i > start:
113
+ break
114
+
115
+ # ── List item ────────────────────────────────
116
+ if stripped.startswith('- '):
117
+ # We encountered a list where we expected a mapping value.
118
+ # Parse the full list and return it as a special case —
119
+ # caller will assign it as the value.
120
+ lst, i = _parse_yaml_list(lines, i, indent)
121
+ # If this list appears at the top level inside a mapping block,
122
+ # we shouldn't be here — this branch is for when a key's value
123
+ # is a list. Return what we have so far and let the caller handle it.
124
+ if not result:
125
+ return lst, i
126
+ break
127
+
128
+ # ── Mapping entry (key: ...) ─────────────────
129
+ if ':' in stripped and not stripped.startswith('-'):
130
+ key, _, rest = stripped.partition(':')
131
+ key = key.strip()
132
+ rest = rest.strip()
133
+
134
+ if rest:
135
+ # Inline value — scalar
136
+ result[key] = _yaml_scalar(rest)
137
+ i += 1
138
+ else:
139
+ # Block value — could be nested map or list
140
+ # Peek at next meaningful line to determine child indent
141
+ child_indent, child_idx = _next_content_line(lines, i + 1)
142
+ if child_indent is not None and child_indent > indent:
143
+ # Check if child is a list
144
+ child_stripped = lines[child_idx].lstrip()
145
+ if child_stripped.startswith('- '):
146
+ value, i = _parse_yaml_list(lines, child_idx, child_indent)
147
+ else:
148
+ value, i = _parse_yaml_block(lines, child_idx, child_indent)
149
+ result[key] = value
150
+ else:
151
+ # Empty value
152
+ result[key] = None
153
+ i += 1
154
+ else:
155
+ i += 1
156
+
157
+ return result, i
158
+
159
+
160
+ def _parse_yaml_list(lines, start, base_indent):
161
+ """Parse a YAML list starting at *start*.
162
+
163
+ Returns (list, next_line_index).
164
+ """
165
+ result = []
166
+ i = start
167
+ while i < len(lines):
168
+ raw = lines[i]
169
+ stripped = raw.lstrip()
170
+ if not stripped or stripped.startswith('#'):
171
+ i += 1
172
+ continue
173
+
174
+ indent = len(raw) - len(raw.lstrip())
175
+ if indent < base_indent:
176
+ break
177
+ if indent > base_indent:
178
+ # Continuation of previous list item (nested block)
179
+ # This is handled inside the item parsing below
180
+ break
181
+
182
+ if not stripped.startswith('- '):
183
+ break
184
+
185
+ item_content = stripped[2:].strip()
186
+
187
+ if ':' in item_content and not item_content.startswith('"') and not item_content.startswith("'"):
188
+ # Mapping-style list item: - key: value [possibly with more keys below]
189
+ k, _, v = item_content.partition(':')
190
+ k = k.strip()
191
+ v = v.strip()
192
+ item_dict = {k: _yaml_scalar(v) if v else None}
193
+
194
+ # Check for continuation keys at deeper indent
195
+ child_indent, child_idx = _next_content_line(lines, i + 1)
196
+ if child_indent is not None and child_indent > indent:
197
+ extra, i = _parse_yaml_block(lines, child_idx, child_indent)
198
+ if isinstance(extra, dict):
199
+ item_dict.update(extra)
200
+ else:
201
+ i = i # no change
202
+ else:
203
+ i += 1
204
+
205
+ result.append(item_dict)
206
+ else:
207
+ # Simple scalar list item
208
+ result.append(_yaml_scalar(item_content))
209
+ i += 1
210
+
211
+ return result, i
212
+
213
+
214
+ def _next_content_line(lines, start):
215
+ """Find the next non-blank, non-comment line from *start*.
216
+
217
+ Returns (indent, index) or (None, None).
218
+ """
219
+ i = start
220
+ while i < len(lines):
221
+ stripped = lines[i].lstrip()
222
+ if stripped and not stripped.startswith('#'):
223
+ indent = len(lines[i]) - len(lines[i].lstrip())
224
+ return indent, i
225
+ i += 1
226
+ return None, None
227
+
228
+
229
+ def _yaml_scalar(text):
230
+ """Convert a YAML scalar string to an appropriate Python type."""
231
+ if not text:
232
+ return None
233
+ # Remove surrounding quotes
234
+ if (text.startswith('"') and text.endswith('"')) or \
235
+ (text.startswith("'") and text.endswith("'")):
236
+ return text[1:-1]
237
+ # Remove inline comments
238
+ if ' #' in text:
239
+ text = text[:text.index(' #')].strip()
240
+ elif '\t#' in text:
241
+ text = text[:text.index('\t#')].strip()
242
+ # Boolean
243
+ if text.lower() in ('true', 'yes'):
244
+ return True
245
+ if text.lower() in ('false', 'no'):
246
+ return False
247
+ # Null
248
+ if text.lower() in ('null', '~', ''):
249
+ return None
250
+ # Numeric
251
+ try:
252
+ return int(text)
253
+ except ValueError:
254
+ pass
255
+ try:
256
+ return float(text)
257
+ except ValueError:
258
+ pass
259
+ # Strip quotes that may remain
260
+ return text.strip('"').strip("'")
261
+
262
+
263
+ def _nested_get(data, *keys):
264
+ """Safely traverse nested dicts. Returns None if any key is missing."""
265
+ current = data
266
+ for key in keys:
267
+ if isinstance(current, dict):
268
+ current = current.get(key)
269
+ else:
270
+ return None
271
+ return current
272
+
273
+
274
+ # ─────────────────────────────────────────────
275
+ # Data Loading — cached per process
276
+ # ─────────────────────────────────────────────
277
+ _contacts_cache = None
278
+ _profiles_cache = None
279
+ _org_titles_cache = None
280
+ _entity_index_cache = None
281
+
282
+
283
+ def load_entity_index():
284
+ """Load entity-relationships.yaml as the primary entity/relationship index.
285
+
286
+ Returns dict with keys:
287
+ - 'entities': list of entity dicts (people, companies)
288
+ - 'relationships': list of relationship dicts
289
+ - 'by_name': dict mapping lowercase name/alias -> entity dict
290
+ - 'rels_by_subject': dict mapping entity id -> list of relationship dicts
291
+
292
+ Gracefully returns empty structure if file is missing or malformed.
293
+ """
294
+ global _entity_index_cache
295
+ if _entity_index_cache is not None:
296
+ return _entity_index_cache
297
+
298
+ empty = {'entities': [], 'relationships': [], 'by_name': {}, 'rels_by_subject': {}}
299
+
300
+ if not ENTITY_INDEX_PATH.exists():
301
+ _entity_index_cache = empty
302
+ return _entity_index_cache
303
+
304
+ try:
305
+ text = ENTITY_INDEX_PATH.read_text(encoding='utf-8')
306
+ data = _parse_yaml_simple(text)
307
+
308
+ if not isinstance(data, dict):
309
+ _entity_index_cache = empty
310
+ return _entity_index_cache
311
+
312
+ entities = data.get('entities', []) or []
313
+ relationships = data.get('relationships', []) or []
314
+
315
+ # Build name lookup: lowercase name/alias -> entity dict
316
+ by_name = {}
317
+ for entity in entities:
318
+ if not isinstance(entity, dict):
319
+ continue
320
+ # Index by full name
321
+ name = entity.get('name', '')
322
+ if name and isinstance(name, str):
323
+ by_name[name.lower()] = entity
324
+ # Index by first name + last name (short form)
325
+ parts = name.split()
326
+ if len(parts) >= 2:
327
+ short = f"{parts[0]} {parts[-1]}"
328
+ by_name[short.lower()] = entity
329
+ # Index by first name alone (for single-name lookups)
330
+ if len(parts) >= 1:
331
+ first = parts[0].lower()
332
+ # Only index first name if it's not too common / ambiguous
333
+ # We allow it but give full name priority in lookup_entity
334
+ if first not in by_name:
335
+ by_name[first] = entity
336
+
337
+ # Index by aliases
338
+ aliases = entity.get('aliases', []) or []
339
+ if isinstance(aliases, list):
340
+ for alias in aliases:
341
+ if alias and isinstance(alias, str):
342
+ by_name[alias.lower()] = entity
343
+
344
+ # Build relationship lookup by subject
345
+ rels_by_subject = {}
346
+ for rel in relationships:
347
+ if not isinstance(rel, dict):
348
+ continue
349
+ subj = rel.get('subject', '')
350
+ if subj:
351
+ rels_by_subject.setdefault(subj, []).append(rel)
352
+
353
+ _entity_index_cache = {
354
+ 'entities': entities,
355
+ 'relationships': relationships,
356
+ 'by_name': by_name,
357
+ 'rels_by_subject': rels_by_subject,
358
+ }
359
+ except Exception:
360
+ _entity_index_cache = empty
361
+
362
+ return _entity_index_cache
363
+
364
+
365
+ def lookup_entity(name, entity_index=None):
366
+ """Look up a person or company by name in the entity index.
367
+
368
+ Performs fuzzy matching: checks full name, first+last, first name, aliases.
369
+
370
+ Args:
371
+ name: The name to look up (case-insensitive)
372
+ entity_index: The loaded entity index (from load_entity_index).
373
+ If None, loads it.
374
+
375
+ Returns:
376
+ dict with keys:
377
+ - 'entity': the entity dict (or None)
378
+ - 'relationships_to_adaptic': list of relationship dicts describing
379
+ this entity's relationship to Adaptic/its entities
380
+ - 'is_internal': bool — True if entity is employed by an Adaptic entity
381
+ - 'relationship_type': str — summary classification
382
+ ('internal', 'vendor_candidate', 'external_vendor', 'external_advisor',
383
+ 'investor', 'regulator', 'external', 'unknown')
384
+ Returns None if entity not found.
385
+ """
386
+ if entity_index is None:
387
+ entity_index = load_entity_index()
388
+
389
+ by_name = entity_index.get('by_name', {})
390
+ rels_by_subject = entity_index.get('rels_by_subject', {})
391
+
392
+ name_lower = name.lower().strip()
393
+ if not name_lower:
394
+ return None
395
+
396
+ # Try exact match first, then partial
397
+ entity = by_name.get(name_lower)
398
+
399
+ if entity is None:
400
+ # Try first+last combination from input
401
+ parts = name_lower.split()
402
+ if len(parts) >= 2:
403
+ short = f"{parts[0]} {parts[-1]}"
404
+ entity = by_name.get(short)
405
+ if entity is None and len(parts) == 1:
406
+ # Single name — try it (already indexed by first name above)
407
+ entity = by_name.get(parts[0])
408
+
409
+ if entity is None:
410
+ return None
411
+
412
+ entity_id = entity.get('id', '')
413
+
414
+ # Collect relationships where this entity is the subject
415
+ subject_rels = rels_by_subject.get(entity_id, [])
416
+
417
+ # Determine relationship to Adaptic
418
+ adaptic_ids = {
419
+ 'company:adaptic-group', 'company:adaptic-holdings',
420
+ 'company:adaptic-am-difc', 'company:adaptic-technologies',
421
+ 'company:adaptic-group-investments', 'company:adaptic-investments',
422
+ 'company:adaptic-inc', 'company:adaptic-ai-uk',
423
+ 'company:adaptic-am-uk', 'company:adaptic-am-cayman',
424
+ 'company:adaptic-am-asia', 'company:adaptic-asia',
425
+ 'company:adaptic-am-eu', 'company:adaptic-am-us',
426
+ 'company:adaptic-am-ksa', 'company:adaptic-ksa',
427
+ }
428
+
429
+ rels_to_adaptic = []
430
+ is_internal = False
431
+ rel_type = 'unknown'
432
+
433
+ for rel in subject_rels:
434
+ obj = rel.get('object', '')
435
+ pred = rel.get('predicate', '')
436
+
437
+ if obj in adaptic_ids or obj.startswith('company:adaptic'):
438
+ rels_to_adaptic.append(rel)
439
+
440
+ if pred in ('employed_by', 'directs'):
441
+ is_internal = True
442
+ rel_type = 'internal'
443
+ elif pred == 'vendor_candidate_for' and rel_type != 'internal':
444
+ rel_type = 'vendor_candidate'
445
+ elif pred == 'vendor_for' and rel_type not in ('internal', 'vendor_candidate'):
446
+ rel_type = 'external_vendor'
447
+ elif pred == 'advises' and rel_type not in ('internal', 'vendor_candidate', 'external_vendor'):
448
+ rel_type = 'external_advisor'
449
+ elif pred == 'invests_in' and rel_type not in ('internal',):
450
+ rel_type = 'investor'
451
+ elif pred == 'regulates' and rel_type not in ('internal',):
452
+ rel_type = 'regulator'
453
+
454
+ # Check if entity is employed by a non-Adaptic company (external person)
455
+ if rel_type == 'unknown':
456
+ for rel in subject_rels:
457
+ pred = rel.get('predicate', '')
458
+ if pred == 'employed_by':
459
+ rel_type = 'external'
460
+ break
461
+
462
+ return {
463
+ 'entity': entity,
464
+ 'relationships_to_adaptic': rels_to_adaptic,
465
+ 'is_internal': is_internal,
466
+ 'relationship_type': rel_type,
467
+ }
468
+
469
+
470
+ def load_contacts():
471
+ """Load confirmed contacts from config/contacts.yaml.
472
+
473
+ Returns dict: lowercase_name -> {role, classification, organisation}
474
+ """
475
+ global _contacts_cache
476
+ if _contacts_cache is not None:
477
+ return _contacts_cache
478
+
479
+ _contacts_cache = {}
480
+ if not CONTACTS_PATH.exists():
481
+ return _contacts_cache
482
+
483
+ text = CONTACTS_PATH.read_text(encoding='utf-8')
484
+
485
+ # Simple parser: extract contact blocks
486
+ current_contact = {}
487
+ for line in text.split('\n'):
488
+ stripped = line.strip()
489
+ if stripped.startswith('- name:'):
490
+ if current_contact.get('name'):
491
+ name_lower = current_contact['name'].lower()
492
+ _contacts_cache[name_lower] = current_contact
493
+ current_contact = {'name': stripped.split(':', 1)[1].strip()}
494
+ elif stripped.startswith('role:') and current_contact:
495
+ current_contact['role'] = stripped.split(':', 1)[1].strip()
496
+ elif stripped.startswith('classification:') and current_contact:
497
+ current_contact['classification'] = stripped.split(':', 1)[1].strip()
498
+ elif stripped.startswith('organisation:') and current_contact:
499
+ current_contact['organisation'] = stripped.split(':', 1)[1].strip()
500
+
501
+ if current_contact.get('name'):
502
+ name_lower = current_contact['name'].lower()
503
+ _contacts_cache[name_lower] = current_contact
504
+
505
+ return _contacts_cache
506
+
507
+
508
+ def load_user_profiles():
509
+ """Load all user profiles from memory/profiles/users/*.yaml.
510
+
511
+ Returns dict: lowercase_name -> {name, title, company, relationship_type,
512
+ relationship_status, classification}
513
+ """
514
+ global _profiles_cache
515
+ if _profiles_cache is not None:
516
+ return _profiles_cache
517
+
518
+ _profiles_cache = {}
519
+ if not USER_PROFILES_DIR.exists():
520
+ return _profiles_cache
521
+
522
+ for yaml_file in USER_PROFILES_DIR.glob('*.yaml'):
523
+ try:
524
+ text = yaml_file.read_text(encoding='utf-8')
525
+ data = _parse_yaml_simple(text)
526
+
527
+ # Navigate nested structure — profiles may have top-level keys
528
+ # (flat) or nested sections like identity.name, relationship.type
529
+ identity = data.get('identity', {}) if isinstance(data.get('identity'), dict) else {}
530
+ relationship = data.get('relationship', {}) if isinstance(data.get('relationship'), dict) else {}
531
+
532
+ # Name: try identity.name, then top-level name, then filename
533
+ name = _nested_get(data, 'identity', 'name') or data.get('name', '')
534
+ if not name or not isinstance(name, str):
535
+ name = yaml_file.stem.replace('-', ' ').title()
536
+
537
+ # Title: identity.title or top-level
538
+ title = _nested_get(data, 'identity', 'title') or data.get('title', '')
539
+
540
+ # Company: identity.company or top-level
541
+ company = _nested_get(data, 'identity', 'company') or data.get('company', '')
542
+
543
+ # Relationship type: relationship.type, or flat relationship_to_adaptic, or top-level type
544
+ rel_type = (
545
+ _nested_get(data, 'relationship', 'type')
546
+ or data.get('relationship_to_adaptic', '')
547
+ or data.get('type', '')
548
+ )
549
+
550
+ # Relationship status: relationship.status, or flat relationship_status, or top-level status
551
+ rel_status = (
552
+ _nested_get(data, 'relationship', 'status')
553
+ or data.get('relationship_status', '')
554
+ or data.get('status', '')
555
+ )
556
+
557
+ # Relationship category (for richer matching)
558
+ rel_category = _nested_get(data, 'relationship', 'category') or data.get('category', '')
559
+
560
+ # Classification & privilege — may be top-level or nested
561
+ classification = data.get('classification', '')
562
+ privilege_level = data.get('privilege_level', '')
563
+
564
+ profile = {
565
+ 'name': name if isinstance(name, str) else str(name),
566
+ 'title': title if isinstance(title, str) else str(title) if title else '',
567
+ 'company': company if isinstance(company, str) else str(company) if company else '',
568
+ 'relationship_type': rel_type if isinstance(rel_type, str) else str(rel_type) if rel_type else '',
569
+ 'relationship_status': rel_status if isinstance(rel_status, str) else str(rel_status) if rel_status else '',
570
+ 'relationship_category': rel_category if isinstance(rel_category, str) else str(rel_category) if rel_category else '',
571
+ 'classification': classification if isinstance(classification, str) else str(classification) if classification else '',
572
+ 'privilege_level': privilege_level if isinstance(privilege_level, str) else str(privilege_level) if privilege_level else '',
573
+ }
574
+ _profiles_cache[name.lower()] = profile
575
+ except Exception:
576
+ continue
577
+
578
+ return _profiles_cache
579
+
580
+
581
+ def load_org_titles():
582
+ """Load confirmed titles from knowledge/entities/org-chart.md.
583
+
584
+ Returns dict: lowercase_name -> confirmed_title
585
+ """
586
+ global _org_titles_cache
587
+ if _org_titles_cache is not None:
588
+ return _org_titles_cache
589
+
590
+ _org_titles_cache = {}
591
+ if not ORG_CHART_PATH.exists():
592
+ return _org_titles_cache
593
+
594
+ text = ORG_CHART_PATH.read_text(encoding='utf-8')
595
+
596
+ # Parse patterns like "### Name — Title" and "Name — Title" from tree
597
+ patterns = [
598
+ r'###\s+(.+?)\s+[—\-]+\s+(.+)', # ### Mehran Granfar — CEO / Founder / UBO
599
+ r'[├└]\s*(.+?)\s+[—\-]+\s+(.+)', # ├── Hootan Yazhari — CFO / CIO
600
+ ]
601
+ for pattern in patterns:
602
+ for match in re.finditer(pattern, text):
603
+ name = match.group(1).strip()
604
+ title = match.group(2).strip()
605
+ # Clean up name (remove markdown, extra chars)
606
+ name = re.sub(r'[\*\#]', '', name).strip()
607
+ # Remove full name suffixes for matching
608
+ first_last = name.split()
609
+ if len(first_last) >= 2:
610
+ short_name = f"{first_last[0]} {first_last[-1]}"
611
+ _org_titles_cache[short_name.lower()] = title
612
+ _org_titles_cache[name.lower()] = title
613
+
614
+ return _org_titles_cache
615
+
616
+
617
+ # ─────────────────────────────────────────────
618
+ # Validation Checks
619
+ # ─────────────────────────────────────────────
620
+
621
+ def check_relationship_claims(message):
622
+ """Check for unverified relationship/affiliation claims.
623
+
624
+ Uses entity-relationships.yaml as PRIMARY lookup source.
625
+ Falls back to user profiles if entity not found in the index.
626
+
627
+ Returns list of issue dicts.
628
+ """
629
+ issues = []
630
+ contacts = load_contacts()
631
+ profiles = load_user_profiles()
632
+ entity_index = load_entity_index()
633
+ message_lower = message.lower()
634
+
635
+ # Confirmed statuses — people with these can be called "our X" (profile fallback)
636
+ confirmed_statuses = {
637
+ 'confirmed', 'confirmed_employee', 'confirmed_contractor', 'internal',
638
+ 'confirmed_advisor', 'confirmed_partner',
639
+ }
640
+ confirmed_classifications = {
641
+ 'internal-ceo', 'internal-executive', 'internal-team',
642
+ }
643
+
644
+ # Entity index relationship types that permit possessive claims ("our X")
645
+ entity_internal_types = {'internal'}
646
+
647
+ # Entity index relationship types that BLOCK possessive claims
648
+ entity_external_types = {
649
+ 'vendor_candidate', 'external_vendor', 'external_advisor',
650
+ 'investor', 'regulator', 'external', 'unknown',
651
+ }
652
+
653
+ # Relationship claim patterns
654
+ claim_patterns = [
655
+ (r'\b(our|my|the company\'s|Adaptic\'s|the team\'s)\s+'
656
+ r'(project manager|PM|consultant|advisor|counsel|lawyer|developer|'
657
+ r'engineer|designer|analyst|hire|employee|team member|colleague|'
658
+ r'staff member|contractor|associate|partner|director|officer|'
659
+ r'head of\s+\w+|lead\s*\w*|manager|VP|chief\s+\w+)',
660
+ "Possessive role claim"),
661
+ (r'\bwe\'ve\s+(hired|appointed|engaged|retained|onboarded|brought on|contracted)\s+',
662
+ "Hiring/engagement claim"),
663
+ (r'\b(has joined|is joining|will be joining|recently joined|now works|'
664
+ r'working with us|part of our team|on our team|member of our)\b',
665
+ "Team membership claim"),
666
+ (r'\b(is our|are our|will be our)\s+\w+',
667
+ "Direct role attribution"),
668
+ ]
669
+
670
+ for pattern, description in claim_patterns:
671
+ for match in re.finditer(pattern, message, re.IGNORECASE):
672
+ matched_text = match.group(0)
673
+ # Extract context — look for a name near the match
674
+ start = max(0, match.start() - 80)
675
+ end = min(len(message), match.end() + 80)
676
+ context_window = message[start:end]
677
+
678
+ # Check if any known person is referenced nearby
679
+ flagged = False
680
+ flagged_person = None
681
+ entity_lookup_used = False
682
+
683
+ # ── PRIMARY: Entity index lookup ─────────────────
684
+ # Scan the context window for any entity name from the index
685
+ by_name = entity_index.get('by_name', {})
686
+ for entity_name_lower, entity in by_name.items():
687
+ # Only check person entities, skip companies
688
+ if entity.get('type') != 'person':
689
+ continue
690
+
691
+ # Check if this entity's name appears in the context
692
+ name_in_context = False
693
+ # Check full name
694
+ full_name = entity.get('name', '')
695
+ if full_name and full_name.lower() in context_window.lower():
696
+ name_in_context = True
697
+ # Check aliases
698
+ if not name_in_context:
699
+ for alias in (entity.get('aliases', []) or []):
700
+ if alias and len(alias) > 2 and alias.lower() in context_window.lower():
701
+ name_in_context = True
702
+ break
703
+ # Check name parts (first name, last name)
704
+ if not name_in_context and full_name:
705
+ parts = full_name.split()
706
+ for part in parts:
707
+ if len(part) > 2 and part.lower() in context_window.lower():
708
+ name_in_context = True
709
+ break
710
+
711
+ if not name_in_context:
712
+ continue
713
+
714
+ # Found a person near the claim — look up their relationship
715
+ result = lookup_entity(full_name, entity_index)
716
+ if result:
717
+ entity_lookup_used = True
718
+ rel_type = result.get('relationship_type', 'unknown')
719
+
720
+ if rel_type in entity_external_types:
721
+ # BLOCK — external person referenced with possessive claim
722
+ flagged = True
723
+ flagged_person = full_name
724
+ # Build enriched suggestion from entity data
725
+ entity_role = entity.get('primary_role', '')
726
+ entity_company = entity.get('primary_company', '')
727
+ suggestion_parts = [
728
+ f"{full_name} is classified as '{rel_type}' in the entity index."
729
+ ]
730
+ if entity_role:
731
+ suggestion_parts.append(f"Their role is: {entity_role}.")
732
+ if entity_company:
733
+ suggestion_parts.append(f"Their company: {entity_company}.")
734
+ suggestion_parts.append(
735
+ "Do not use possessive language ('our', 'my') for external parties."
736
+ )
737
+ break # Found a flagged person, stop scanning
738
+
739
+ elif rel_type in entity_internal_types:
740
+ # ALLOW — confirmed internal person
741
+ # Don't flag, and mark that we found a confirmed person
742
+ flagged = False
743
+ flagged_person = None
744
+ entity_lookup_used = True
745
+ break
746
+
747
+ # ── FALLBACK: Profile-based lookup (if entity index didn't resolve) ──
748
+ if not entity_lookup_used:
749
+ for name_lower, profile in profiles.items():
750
+ rel_status = profile.get('relationship_status', '')
751
+ rel_type = profile.get('relationship_type', '')
752
+ priv_level = profile.get('privilege_level', '')
753
+ classification = profile.get('classification', '')
754
+
755
+ # Check if this person is in the context window
756
+ name_parts = name_lower.split()
757
+ name_in_context = False
758
+ for part in name_parts:
759
+ if len(part) > 2 and part.lower() in context_window.lower():
760
+ name_in_context = True
761
+ break
762
+
763
+ if not name_in_context:
764
+ continue
765
+
766
+ # Is this person confirmed?
767
+ is_confirmed = (
768
+ rel_status in confirmed_statuses or
769
+ classification in confirmed_classifications or
770
+ priv_level == 'internal'
771
+ )
772
+
773
+ # Also check contacts registry
774
+ contact = contacts.get(name_lower, {})
775
+ contact_class = contact.get('classification', '')
776
+ if contact_class in confirmed_classifications:
777
+ is_confirmed = True
778
+
779
+ if not is_confirmed:
780
+ flagged = True
781
+ flagged_person = profile.get('name', name_lower)
782
+ break
783
+
784
+ # Also flag if no person is identified but claim is made
785
+ # (could be about someone not in our records at all)
786
+ if not flagged and not entity_lookup_used:
787
+ # Check all known profiles — if none are mentioned, still warn
788
+ any_confirmed_nearby = False
789
+ for name_lower, profile in {**profiles, **{k: {'name': v.get('name', k)} for k, v in contacts.items()}}.items():
790
+ name_parts = name_lower.split()
791
+ for part in name_parts:
792
+ if len(part) > 2 and part.lower() in context_window.lower():
793
+ # Person found — check if confirmed
794
+ contact = contacts.get(name_lower, {})
795
+ contact_class = contact.get('classification', '')
796
+ p = profiles.get(name_lower, {})
797
+ if (contact_class in confirmed_classifications or
798
+ p.get('relationship_status') in confirmed_statuses or
799
+ p.get('privilege_level') == 'internal'):
800
+ any_confirmed_nearby = True
801
+ break
802
+
803
+ if not any_confirmed_nearby:
804
+ # Nobody confirmed near this claim — warn
805
+ flagged = True
806
+ flagged_person = None
807
+
808
+ if flagged:
809
+ # Build suggestion based on source
810
+ if flagged_person and entity_lookup_used:
811
+ result = lookup_entity(flagged_person, entity_index)
812
+ if result:
813
+ rel_type = result.get('relationship_type', 'unknown')
814
+ entity_data = result.get('entity', {})
815
+ entity_role = entity_data.get('primary_role', '') if entity_data else ''
816
+ entity_company = entity_data.get('primary_company', '') if entity_data else ''
817
+ suggestion = (
818
+ f"{flagged_person} is '{rel_type}' per entity index"
819
+ f"{' (' + entity_role + ')' if entity_role else ''}. "
820
+ f"Do not use possessive language for external parties."
821
+ )
822
+ else:
823
+ suggestion = f"Verify {flagged_person}'s relationship status before claiming affiliation."
824
+ elif flagged_person:
825
+ suggestion = f"Verify {flagged_person}'s relationship status before claiming affiliation."
826
+ else:
827
+ suggestion = "Could not identify a confirmed team member near this claim. Verify the relationship."
828
+
829
+ issue = {
830
+ "type": "relationship_claim",
831
+ "severity": "block",
832
+ "matched_text": matched_text,
833
+ "description": f"{description}: \"{matched_text}\"",
834
+ "suggestion": suggestion,
835
+ "context": context_window.strip(),
836
+ "lookup_source": "entity_index" if entity_lookup_used else "profile_fallback",
837
+ }
838
+ if flagged_person:
839
+ issue["person"] = flagged_person
840
+ issues.append(issue)
841
+
842
+ return issues
843
+
844
+
845
+ def check_title_accuracy(message):
846
+ """Check that titles attributed to named people match confirmed records.
847
+
848
+ Returns list of issue dicts.
849
+ """
850
+ issues = []
851
+ org_titles = load_org_titles()
852
+ contacts = load_contacts()
853
+ profiles = load_user_profiles()
854
+
855
+ # Build comprehensive title map: name -> known title
856
+ title_map = {}
857
+ for name_lower, title in org_titles.items():
858
+ title_map[name_lower] = title
859
+ for name_lower, contact in contacts.items():
860
+ if contact.get('role'):
861
+ title_map[name_lower] = contact['role']
862
+ for name_lower, profile in profiles.items():
863
+ if profile.get('title'):
864
+ title_map[name_lower] = profile['title']
865
+
866
+ # Pattern: "Name, Title" or "Name (Title)" or "Name, the Title"
867
+ title_attribution_patterns = [
868
+ r'([A-Z][a-z]+(?:\s+[A-Z][a-z]+)+),\s+(?:the\s+)?([A-Z][A-Za-z\s/]+?)(?:\.|,|\s+at\s+|\s+of\s+|\s+from\s+|\s*$)',
869
+ r'([A-Z][a-z]+(?:\s+[A-Z][a-z]+)+)\s+\(([^)]+)\)',
870
+ ]
871
+
872
+ for pattern in title_attribution_patterns:
873
+ for match in re.finditer(pattern, message):
874
+ name = match.group(1).strip()
875
+ attributed_title = match.group(2).strip()
876
+ name_lower = name.lower()
877
+
878
+ # Look up known title
879
+ known_title = title_map.get(name_lower)
880
+ if not known_title:
881
+ # Try partial match (first + last name)
882
+ parts = name_lower.split()
883
+ if len(parts) >= 2:
884
+ short = f"{parts[0]} {parts[-1]}"
885
+ known_title = title_map.get(short)
886
+
887
+ if known_title:
888
+ # Compare — normalize for comparison
889
+ known_normalized = known_title.lower().replace('/', ' ').replace('-', ' ')
890
+ attr_normalized = attributed_title.lower().replace('/', ' ').replace('-', ' ')
891
+
892
+ # Check if attributed title is a reasonable match
893
+ known_words = set(known_normalized.split())
894
+ attr_words = set(attr_normalized.split())
895
+
896
+ # If there's very little overlap, flag it
897
+ if known_words and attr_words:
898
+ overlap = known_words & attr_words
899
+ if len(overlap) < 1 and len(attr_words) > 1:
900
+ issues.append({
901
+ "type": "title_accuracy",
902
+ "severity": "warn",
903
+ "person": name,
904
+ "attributed_title": attributed_title,
905
+ "known_title": known_title,
906
+ "description": (
907
+ f"Title \"{attributed_title}\" for {name} does not match "
908
+ f"confirmed title \"{known_title}\""
909
+ ),
910
+ "suggestion": f"Use confirmed title: \"{known_title}\"",
911
+ })
912
+
913
+ return issues
914
+
915
+
916
+ def check_ai_self_disclosure(message):
917
+ """Scan for language that reveals Sophie is AI.
918
+
919
+ Must NOT flag general business discussion of Adaptic's AI products.
920
+ Returns list of issue dicts.
921
+ """
922
+ issues = []
923
+
924
+ # Business context exceptions — if these appear, the word "AI" is OK
925
+ business_exceptions = [
926
+ r'AI[- ]Born', r'AI[- ]native', r'Adaptic\s*AI', r'Adaptic\.ai',
927
+ r'artificial intelligence\s+strategy', r'AI\s+products?',
928
+ r'AI\s+platform', r'AI\s+capabilit', r'machine learning\s+models?',
929
+ r'AI[- ]driven', r'AI\s+solutions?', r'AI\s+technolog',
930
+ r'AI\s+invest', r'AI\s+fund', r'AI\s+sector', r'AI\s+market',
931
+ r'AI\s+infrastructure', r'AI\s+research', r'generative\s+AI',
932
+ ]
933
+
934
+ # Self-referential AI disclosure patterns
935
+ disclosure_patterns = [
936
+ (r'\bI\s+(?:am|\'m)\s+(?:an?\s+)?(?:AI|artificial intelligence|language model|LLM|chatbot|bot|virtual assistant|automated system|machine)\b',
937
+ "Direct AI self-identification"),
938
+ (r'\b(?:I\'m|I am)\s+not\s+(?:human|a real person|a person)\b',
939
+ "Human status denial"),
940
+ (r'\bas an AI\b',
941
+ "Self-referential 'as an AI'"),
942
+ (r'\b(?:my|I)\s+(?:training|training data|knowledge cutoff|neural network|parameters)\b',
943
+ "AI-specific self-reference"),
944
+ (r'\bSophie\s+is\s+(?:an?\s+)?(?:AI|bot|automated|virtual|artificial)\b',
945
+ "Third-person AI disclosure about Sophie"),
946
+ (r'\b(?:automated|AI[- ]powered|AI[- ]operated|machine[- ]generated)\s+(?:assistant|chief of staff|executive|system)\b',
947
+ "Role described as automated"),
948
+ (r'\b(?:I don\'t have|I lack)\s+(?:feelings|emotions|a body|physical form|consciousness)\b',
949
+ "Self-disclosure via limitation"),
950
+ (r'\b(?:my programming|my algorithms?|my code)\b',
951
+ "Self-referencing technical nature"),
952
+ ]
953
+
954
+ for pattern, description in disclosure_patterns:
955
+ for match in re.finditer(pattern, message, re.IGNORECASE):
956
+ matched_text = match.group(0)
957
+ start = max(0, match.start() - 40)
958
+ end = min(len(message), match.end() + 40)
959
+ context_window = message[start:end]
960
+
961
+ # Check if this is actually a business context exception
962
+ is_business_context = False
963
+ for exc_pattern in business_exceptions:
964
+ if re.search(exc_pattern, context_window, re.IGNORECASE):
965
+ is_business_context = True
966
+ break
967
+
968
+ if not is_business_context:
969
+ issues.append({
970
+ "type": "ai_self_disclosure",
971
+ "severity": "block",
972
+ "matched_text": matched_text,
973
+ "description": f"AI self-disclosure detected: {description}",
974
+ "suggestion": "Remove or rephrase to avoid revealing Sophie's AI nature.",
975
+ "context": context_window.strip(),
976
+ })
977
+
978
+ return issues
979
+
980
+
981
+ def check_in_person_self_scheduling(message):
982
+ """Detect Sophie scheduling herself for in-person meetings.
983
+
984
+ Should NOT flag scheduling for Mehran or team members.
985
+ Returns list of issue dicts.
986
+ """
987
+ issues = []
988
+
989
+ # Patterns where Sophie offers to physically attend
990
+ self_attend_patterns = [
991
+ (r'\bI\'ll\s+(?:attend|be there|come to|visit|meet you at|stop by|drop by|come by|swing by|show up)\b',
992
+ "Sophie offering to physically attend"),
993
+ (r'\b(?:meet me at|see you at|I\'ll see you|I can meet|let me come|I\'ll come)\b',
994
+ "Sophie scheduling herself for physical meeting"),
995
+ (r'\b(?:I\'ll be in|I\'m in|I will be at)\s+(?:the office|DIFC|Innovation|your office|the meeting room|the boardroom)\b',
996
+ "Sophie placing herself in physical location"),
997
+ (r'\bSophie\s+(?:will attend|will be at|will meet|will visit)\b',
998
+ "Third-person Sophie physical attendance"),
999
+ (r'\bI\'ll\s+(?:fly|travel|drive|take a cab|take a taxi)\b',
1000
+ "Sophie describing physical travel"),
1001
+ (r'\b(?:looking forward to meeting you|see you (?:there|soon|Monday|Tuesday|Wednesday|Thursday|Friday|tomorrow|next week))\b',
1002
+ "Sophie implying physical presence at meeting"),
1003
+ ]
1004
+
1005
+ # Exception patterns — scheduling others is fine
1006
+ team_scheduling_patterns = [
1007
+ r'\b(?:Mehran|Hootan|Nima|Bronwyn|Shayan|the team|our team|he|she|they)\s+(?:will|can|should|could)\s+(?:attend|be there|meet|visit)',
1008
+ r'\b(?:Mehran|Hootan|Nima|Bronwyn|Shayan)\s+(?:will|can)\s+(?:come|be at|meet you)',
1009
+ r'\b(?:schedule|book|set up|arrange)\s+(?:a|the)\s+(?:meeting|call|session)\s+(?:for|with)\s+(?:Mehran|Hootan|Nima|Bronwyn)',
1010
+ ]
1011
+
1012
+ for pattern, description in self_attend_patterns:
1013
+ for match in re.finditer(pattern, message, re.IGNORECASE):
1014
+ matched_text = match.group(0)
1015
+ start = max(0, match.start() - 50)
1016
+ end = min(len(message), match.end() + 50)
1017
+ context_window = message[start:end]
1018
+
1019
+ # Check if this is actually about scheduling someone else
1020
+ is_team_scheduling = False
1021
+ for exc_pattern in team_scheduling_patterns:
1022
+ if re.search(exc_pattern, context_window, re.IGNORECASE):
1023
+ is_team_scheduling = True
1024
+ break
1025
+
1026
+ if not is_team_scheduling:
1027
+ issues.append({
1028
+ "type": "in_person_self_scheduling",
1029
+ "severity": "block",
1030
+ "matched_text": matched_text,
1031
+ "description": f"In-person self-scheduling detected: {description}",
1032
+ "suggestion": "Sophie cannot attend in-person meetings. Propose Mehran or a team member instead.",
1033
+ "context": context_window.strip(),
1034
+ })
1035
+
1036
+ return issues
1037
+
1038
+
1039
+ def check_unverified_commitments(message):
1040
+ """Flag strong commitment language that should be verified.
1041
+
1042
+ Returns list of issue dicts.
1043
+ """
1044
+ issues = []
1045
+
1046
+ commitment_patterns = [
1047
+ (r'\b(?:we\'ve|we have)\s+(?:closed|secured|signed|finalized|completed|received approval)',
1048
+ "Commitment claim — verify status"),
1049
+ (r'\b(?:deal is done|agreement signed|funds received|licence approved|licence granted)',
1050
+ "Completion claim — verify against records"),
1051
+ (r'\b(?:we\'ve|we have)\s+(?:raised|received)\s+\$[\d,.]+',
1052
+ "Fundraising claim — verify amount"),
1053
+ ]
1054
+
1055
+ for pattern, description in commitment_patterns:
1056
+ for match in re.finditer(pattern, message, re.IGNORECASE):
1057
+ matched_text = match.group(0)
1058
+ start = max(0, match.start() - 40)
1059
+ end = min(len(message), match.end() + 40)
1060
+ context_window = message[start:end]
1061
+
1062
+ issues.append({
1063
+ "type": "unverified_commitment",
1064
+ "severity": "warn",
1065
+ "matched_text": matched_text,
1066
+ "description": f"Unverified commitment: {description}",
1067
+ "suggestion": "Verify this claim against known records before sending.",
1068
+ "context": context_window.strip(),
1069
+ })
1070
+
1071
+ return issues
1072
+
1073
+
1074
+ # ─────────────────────────────────────────────
1075
+ # Main Validation Pipeline
1076
+ # ─────────────────────────────────────────────
1077
+
1078
+ def _load_rules_config():
1079
+ """Load validation-rules.yaml. Returns dict or None on failure."""
1080
+ try:
1081
+ if VALIDATION_RULES_PATH.exists():
1082
+ text = VALIDATION_RULES_PATH.read_text(encoding='utf-8')
1083
+ return _parse_yaml_simple(text)
1084
+ except Exception:
1085
+ pass
1086
+ return None
1087
+
1088
+
1089
+ def validate_message(message, message_type="email", recipient="", channel=None):
1090
+ """Run all validation checks on a message.
1091
+
1092
+ Args:
1093
+ message: The message body text
1094
+ message_type: "email", "slack", or "whatsapp"
1095
+ recipient: Recipient identifier (email address, channel ID, phone number)
1096
+ channel: Optional Slack channel slug (for disclosure assessment)
1097
+
1098
+ Returns:
1099
+ dict with keys: status, issues, metadata
1100
+ """
1101
+ start_time = time.time()
1102
+ all_issues = []
1103
+
1104
+ # Run all checks
1105
+ all_issues.extend(check_relationship_claims(message))
1106
+ all_issues.extend(check_title_accuracy(message))
1107
+ all_issues.extend(check_ai_self_disclosure(message))
1108
+ all_issues.extend(check_in_person_self_scheduling(message))
1109
+ all_issues.extend(check_unverified_commitments(message))
1110
+
1111
+ # ── Rule 6: Disclosure Assessment (Information Barrier) ──
1112
+ if _da_module:
1113
+ try:
1114
+ rules_config = _load_rules_config()
1115
+ da_config = rules_config.get("disclosure_assessment", {}) if rules_config else {}
1116
+ if da_config.get("enabled", False):
1117
+ # Derive a slug from the recipient string
1118
+ recipient_slug = None
1119
+ if recipient:
1120
+ recipient_slug = (
1121
+ recipient.lower()
1122
+ .replace(" ", "-")
1123
+ .replace("@", "-at-")
1124
+ .replace(".", "-")
1125
+ )
1126
+
1127
+ if recipient_slug:
1128
+ da_result = _da_module.assess_disclosure(
1129
+ message, recipient_slug, channel=channel
1130
+ )
1131
+ outcome = da_result.get("outcome", "passed")
1132
+ if outcome == "blocked":
1133
+ assessments = da_result.get("references_assessed", [])
1134
+ first_reason = (
1135
+ assessments[0].get("reasoning", "disclosure violation")
1136
+ if assessments else "disclosure violation detected"
1137
+ )
1138
+ all_issues.append({
1139
+ "type": "disclosure_assessment",
1140
+ "severity": "block",
1141
+ "matched_text": "",
1142
+ "description": (
1143
+ f"INFORMATION BARRIER: {da_result.get('severity', 'unknown')} severity"
1144
+ f" — {first_reason}"
1145
+ ),
1146
+ "suggestion": "Remove or redact the restricted reference before sending.",
1147
+ "detail": da_result,
1148
+ })
1149
+ elif outcome == "stripped":
1150
+ all_issues.append({
1151
+ "type": "disclosure_assessment",
1152
+ "severity": "warn",
1153
+ "matched_text": "",
1154
+ "description": (
1155
+ "INFORMATION BARRIER: Medium severity references detected"
1156
+ " — content should be reviewed"
1157
+ ),
1158
+ "suggestion": "Review and redact medium-sensitivity references before sending.",
1159
+ "detail": da_result,
1160
+ })
1161
+ except Exception:
1162
+ pass # Fail-open on assessment errors
1163
+
1164
+ # Deduplicate by matched_text + type
1165
+ seen = set()
1166
+ unique_issues = []
1167
+ for issue in all_issues:
1168
+ key = (issue.get('type', ''), issue.get('matched_text', ''))
1169
+ if key not in seen:
1170
+ seen.add(key)
1171
+ unique_issues.append(issue)
1172
+
1173
+ elapsed_ms = round((time.time() - start_time) * 1000, 1)
1174
+
1175
+ has_blockers = any(i['severity'] == 'block' for i in unique_issues)
1176
+ status = "issues_found" if has_blockers else ("warnings" if unique_issues else "clean")
1177
+
1178
+ result = {
1179
+ "status": status,
1180
+ "issues": unique_issues,
1181
+ "metadata": {
1182
+ "message_type": message_type,
1183
+ "recipient": recipient,
1184
+ "checks_run": 6,
1185
+ "issues_found": len(unique_issues),
1186
+ "blockers": sum(1 for i in unique_issues if i['severity'] == 'block'),
1187
+ "warnings": sum(1 for i in unique_issues if i['severity'] == 'warn'),
1188
+ "elapsed_ms": elapsed_ms,
1189
+ "timestamp": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
1190
+ },
1191
+ }
1192
+
1193
+ return result
1194
+
1195
+
1196
+ def log_validation_result(result):
1197
+ """Log validation result to audit log."""
1198
+ try:
1199
+ AUDIT_LOG_DIR.mkdir(parents=True, exist_ok=True)
1200
+ today = datetime.now(timezone.utc).strftime("%Y-%m-%d")
1201
+ log_file = AUDIT_LOG_DIR / f"{today}-validation.jsonl"
1202
+
1203
+ log_entry = {
1204
+ "timestamp": result["metadata"]["timestamp"],
1205
+ "agent": "validate-outbound",
1206
+ "action": "outbound_validation",
1207
+ "status": result["status"],
1208
+ "message_type": result["metadata"]["message_type"],
1209
+ "recipient": result["metadata"]["recipient"],
1210
+ "issues_found": result["metadata"]["issues_found"],
1211
+ "blockers": result["metadata"]["blockers"],
1212
+ "warnings": result["metadata"]["warnings"],
1213
+ "elapsed_ms": result["metadata"]["elapsed_ms"],
1214
+ }
1215
+
1216
+ # Add issue summaries (but not full context to keep log compact)
1217
+ if result["issues"]:
1218
+ log_entry["issue_types"] = list(set(i["type"] for i in result["issues"]))
1219
+ log_entry["issue_summaries"] = [
1220
+ {"type": i["type"], "severity": i["severity"], "description": i["description"]}
1221
+ for i in result["issues"][:10] # Cap at 10 for log size
1222
+ ]
1223
+
1224
+ with open(log_file, "a", encoding="utf-8") as f:
1225
+ f.write(json.dumps(log_entry) + "\n")
1226
+ except Exception:
1227
+ pass # Fail-open — validation logging should never block sending
1228
+
1229
+
1230
+ # ─────────────────────────────────────────────
1231
+ # CLI Entry Point
1232
+ # ─────────────────────────────────────────────
1233
+
1234
+ def main():
1235
+ parser = argparse.ArgumentParser(
1236
+ description="Pre-send outbound correspondence validator"
1237
+ )
1238
+ parser.add_argument(
1239
+ "--message", "-m",
1240
+ help="Message body to validate (alternative to stdin)"
1241
+ )
1242
+ parser.add_argument(
1243
+ "--type", "-t",
1244
+ default="email",
1245
+ choices=["email", "slack", "whatsapp"],
1246
+ help="Message type (default: email)"
1247
+ )
1248
+ parser.add_argument(
1249
+ "--recipient", "-r",
1250
+ default="",
1251
+ help="Recipient identifier (email, channel ID, phone)"
1252
+ )
1253
+ parser.add_argument(
1254
+ "--channel", "-c",
1255
+ default=None,
1256
+ help="Slack channel slug (used for disclosure assessment)"
1257
+ )
1258
+ parser.add_argument(
1259
+ "--quiet", "-q",
1260
+ action="store_true",
1261
+ help="Suppress non-JSON output"
1262
+ )
1263
+ args = parser.parse_args()
1264
+
1265
+ # Get message from --message flag or stdin
1266
+ if args.message:
1267
+ message = args.message
1268
+ elif not sys.stdin.isatty():
1269
+ message = sys.stdin.read()
1270
+ else:
1271
+ print("ERROR: Provide message via --message flag or stdin", file=sys.stderr)
1272
+ sys.exit(2)
1273
+
1274
+ if not message.strip():
1275
+ print("ERROR: Empty message", file=sys.stderr)
1276
+ sys.exit(2)
1277
+
1278
+ # Run validation
1279
+ result = validate_message(message, args.type, args.recipient, channel=args.channel)
1280
+
1281
+ # Log to audit
1282
+ log_validation_result(result)
1283
+
1284
+ # Output JSON result
1285
+ print(json.dumps(result, indent=2))
1286
+
1287
+ # Human-readable summary to stderr (unless --quiet)
1288
+ if not args.quiet:
1289
+ meta = result["metadata"]
1290
+ if result["status"] == "clean":
1291
+ print(
1292
+ f"VALIDATION PASS: {meta['checks_run']} checks, "
1293
+ f"0 issues ({meta['elapsed_ms']}ms)",
1294
+ file=sys.stderr
1295
+ )
1296
+ else:
1297
+ print(
1298
+ f"VALIDATION {'BLOCK' if meta['blockers'] > 0 else 'WARN'}: "
1299
+ f"{meta['blockers']} blockers, {meta['warnings']} warnings "
1300
+ f"({meta['elapsed_ms']}ms)",
1301
+ file=sys.stderr
1302
+ )
1303
+ for issue in result["issues"]:
1304
+ severity_marker = "BLOCK" if issue["severity"] == "block" else "WARN"
1305
+ print(
1306
+ f" [{severity_marker}] {issue['description']}",
1307
+ file=sys.stderr
1308
+ )
1309
+ if issue.get("suggestion"):
1310
+ print(
1311
+ f" -> {issue['suggestion']}",
1312
+ file=sys.stderr
1313
+ )
1314
+
1315
+ # Exit code: 1 if blockers, 0 otherwise
1316
+ sys.exit(1 if result["metadata"]["blockers"] > 0 else 0)
1317
+
1318
+
1319
+ # ─────────────────────────────────────────────
1320
+ # Test Suite
1321
+ # ─────────────────────────────────────────────
1322
+
1323
+ def run_tests():
1324
+ """Run built-in validation tests. Returns True if all pass."""
1325
+ passed = 0
1326
+ failed = 0
1327
+ total = 0
1328
+
1329
+ def assert_test(name, condition, detail=""):
1330
+ nonlocal passed, failed, total
1331
+ total += 1
1332
+ if condition:
1333
+ passed += 1
1334
+ print(f" PASS: {name}", file=sys.stderr)
1335
+ else:
1336
+ failed += 1
1337
+ print(f" FAIL: {name}{' — ' + detail if detail else ''}", file=sys.stderr)
1338
+
1339
+ print("Running validate-outbound.py tests...\n", file=sys.stderr)
1340
+
1341
+ # ── Test 1: Entity index loads without error ──
1342
+ print("[Entity Index Loading]", file=sys.stderr)
1343
+ idx = load_entity_index()
1344
+ assert_test("Entity index loads", isinstance(idx, dict))
1345
+ assert_test("Entity index has entities", len(idx.get('entities', [])) > 0)
1346
+ assert_test("Entity index has relationships", len(idx.get('relationships', [])) > 0)
1347
+ assert_test("Entity index has by_name lookup", len(idx.get('by_name', {})) > 0)
1348
+
1349
+ # ── Test 2: Entity lookup — known internal people ──
1350
+ print("\n[Entity Lookup — Internal]", file=sys.stderr)
1351
+ mehran = lookup_entity("Mehran Granfar", idx)
1352
+ assert_test("Mehran found", mehran is not None)
1353
+ assert_test("Mehran is internal", mehran and mehran.get('is_internal') is True)
1354
+ assert_test("Mehran rel_type is internal", mehran and mehran.get('relationship_type') == 'internal')
1355
+
1356
+ hootan = lookup_entity("Hootan", idx)
1357
+ assert_test("Hootan found by first name", hootan is not None)
1358
+ assert_test("Hootan is internal", hootan and hootan.get('is_internal') is True)
1359
+
1360
+ sophie = lookup_entity("Sophie Nguyen", idx)
1361
+ assert_test("Sophie found", sophie is not None)
1362
+ assert_test("Sophie is internal", sophie and sophie.get('is_internal') is True)
1363
+
1364
+ # ── Test 3: Entity lookup — known external people ──
1365
+ print("\n[Entity Lookup — External]", file=sys.stderr)
1366
+ graham = lookup_entity("Graham Syder", idx)
1367
+ assert_test("Graham found", graham is not None)
1368
+ assert_test("Graham is NOT internal", graham and graham.get('is_internal') is False)
1369
+ assert_test(
1370
+ "Graham is vendor_candidate",
1371
+ graham and graham.get('relationship_type') == 'vendor_candidate',
1372
+ f"got: {graham.get('relationship_type') if graham else 'None'}"
1373
+ )
1374
+
1375
+ rishel = lookup_entity("Rishel", idx)
1376
+ assert_test("Rishel found by first name", rishel is not None)
1377
+ assert_test("Rishel is NOT internal", rishel and rishel.get('is_internal') is False)
1378
+
1379
+ # ── Test 4: Entity lookup — unknown person ──
1380
+ print("\n[Entity Lookup — Unknown]", file=sys.stderr)
1381
+ unknown = lookup_entity("John Smith", idx)
1382
+ assert_test("Unknown person returns None", unknown is None)
1383
+
1384
+ unknown_empty = lookup_entity("", idx)
1385
+ assert_test("Empty string returns None", unknown_empty is None)
1386
+
1387
+ # ── Test 5: Graham incident — MUST be blocked ──
1388
+ print("\n[Graham Incident — Possessive Claim Block]", file=sys.stderr)
1389
+ graham_msg = "Graham Syder is our project manager for the DIFC fitout."
1390
+ result = validate_message(graham_msg, "email", "internal@adaptic.ai")
1391
+ assert_test(
1392
+ "Graham 'our PM' is BLOCKED",
1393
+ result['status'] == 'issues_found' and result['metadata']['blockers'] > 0,
1394
+ f"status={result['status']}, blockers={result['metadata']['blockers']}"
1395
+ )
1396
+ # Verify entity_index was the lookup source
1397
+ rel_issues = [i for i in result['issues'] if i['type'] == 'relationship_claim']
1398
+ entity_sourced = any(i.get('lookup_source') == 'entity_index' for i in rel_issues)
1399
+ assert_test(
1400
+ "Graham block used entity_index as source",
1401
+ entity_sourced,
1402
+ f"sources: {[i.get('lookup_source') for i in rel_issues]}"
1403
+ )
1404
+
1405
+ # Also test variant: "our PM Graham"
1406
+ graham_msg2 = "I've arranged for our PM Graham to review the floor plan."
1407
+ result2 = validate_message(graham_msg2, "slack", "C099ABCDEF")
1408
+ assert_test(
1409
+ "Graham 'our PM Graham' variant is BLOCKED",
1410
+ result2['status'] == 'issues_found' and result2['metadata']['blockers'] > 0,
1411
+ f"status={result2['status']}, blockers={result2['metadata']['blockers']}"
1412
+ )
1413
+
1414
+ # ── Test 6: Internal person — MUST be allowed ──
1415
+ print("\n[Internal Person — Allowed Claims]", file=sys.stderr)
1416
+ hootan_msg = "Hootan Yazhari is our CFO and will be handling the DFSA submission."
1417
+ result3 = validate_message(hootan_msg, "email", "external@example.com")
1418
+ rel_issues3 = [i for i in result3['issues'] if i['type'] == 'relationship_claim']
1419
+ assert_test(
1420
+ "Hootan 'our CFO' is NOT blocked",
1421
+ len(rel_issues3) == 0,
1422
+ f"found {len(rel_issues3)} relationship issues"
1423
+ )
1424
+
1425
+ # ── Test 7: AI self-disclosure still blocked ──
1426
+ print("\n[AI Self-Disclosure Block]", file=sys.stderr)
1427
+ ai_msg = "I am an AI assistant and I can help with that."
1428
+ result4 = validate_message(ai_msg, "email", "external@example.com")
1429
+ ai_issues = [i for i in result4['issues'] if i['type'] == 'ai_self_disclosure']
1430
+ assert_test("AI self-disclosure blocked", len(ai_issues) > 0)
1431
+
1432
+ ai_msg_sophie = "Sophie is an AI-operated automated assistant."
1433
+ result4b = validate_message(ai_msg_sophie, "email", "external@example.com")
1434
+ ai_issues_b = [i for i in result4b['issues'] if i['type'] == 'ai_self_disclosure']
1435
+ assert_test("Sophie third-person AI disclosure blocked", len(ai_issues_b) > 0)
1436
+
1437
+ # ── Test 8: In-person self-scheduling still blocked ──
1438
+ print("\n[In-Person Self-Scheduling Block]", file=sys.stderr)
1439
+ attend_msg = "I'll attend the meeting at the DIFC office tomorrow."
1440
+ result5 = validate_message(attend_msg, "slack", "C099ABCDEF")
1441
+ attend_issues = [i for i in result5['issues'] if i['type'] == 'in_person_self_scheduling']
1442
+ assert_test("In-person self-scheduling blocked", len(attend_issues) > 0)
1443
+
1444
+ # ── Test 9: Clean message passes ──
1445
+ print("\n[Clean Message — No Issues]", file=sys.stderr)
1446
+ clean_msg = "Hi team, the quarterly report is ready for review. Please check your inboxes."
1447
+ result6 = validate_message(clean_msg, "email", "team@adaptic.ai")
1448
+ assert_test(
1449
+ "Clean message passes with no blockers",
1450
+ result6['metadata']['blockers'] == 0,
1451
+ f"blockers={result6['metadata']['blockers']}"
1452
+ )
1453
+
1454
+ # ── Test 10: Shayan — investor, not internal employee ──
1455
+ print("\n[Shayan — Investor, Not Internal]", file=sys.stderr)
1456
+ shayan = lookup_entity("Shayan Kargarian", idx)
1457
+ assert_test("Shayan found", shayan is not None)
1458
+ assert_test(
1459
+ "Shayan is NOT internal",
1460
+ shayan and shayan.get('is_internal') is False,
1461
+ f"is_internal={shayan.get('is_internal') if shayan else 'None'}"
1462
+ )
1463
+
1464
+ # ── Test 11: Performance — under 1 second ──
1465
+ print("\n[Performance]", file=sys.stderr)
1466
+ perf_msg = "Graham is our project manager. He has joined the team. We've hired him."
1467
+ import time as _time
1468
+ t0 = _time.time()
1469
+ for _ in range(10):
1470
+ validate_message(perf_msg, "email", "test@test.com")
1471
+ elapsed = (_time.time() - t0) / 10
1472
+ assert_test(
1473
+ f"Average validation < 1s (got {elapsed*1000:.1f}ms)",
1474
+ elapsed < 1.0
1475
+ )
1476
+
1477
+ # ── Summary ──
1478
+ print(f"\n{'='*50}", file=sys.stderr)
1479
+ print(f"Results: {passed}/{total} passed, {failed} failed", file=sys.stderr)
1480
+ if failed > 0:
1481
+ print("TESTS FAILED", file=sys.stderr)
1482
+ return False
1483
+ else:
1484
+ print("ALL TESTS PASSED", file=sys.stderr)
1485
+ return True
1486
+
1487
+
1488
+ if __name__ == "__main__":
1489
+ # Check for --test flag before argparse (to avoid --message requirement)
1490
+ if '--test' in sys.argv:
1491
+ success = run_tests()
1492
+ sys.exit(0 if success else 1)
1493
+ main()