machinaos 0.0.76 → 0.0.78

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 (393) hide show
  1. package/README.md +143 -107
  2. package/client/dist/assets/ActionBar-Du2MSFSz.js +1 -0
  3. package/client/dist/assets/ApiKeyInput-k2LBmBjb.js +1 -0
  4. package/client/dist/assets/ApiKeyPanel-C_bV9U0X.js +1 -0
  5. package/client/dist/assets/ApiUsageSection-CmVfwZzL.js +1 -0
  6. package/client/dist/assets/EmailPanel-CeKIMGu-.js +1 -0
  7. package/client/dist/assets/OAuthPanel-KA3t3Q2K.js +1 -0
  8. package/client/dist/assets/QrPairingPanel-NgNpJNuk.js +1 -0
  9. package/client/dist/assets/RateLimitSection-Du5YNVIA.js +1 -0
  10. package/client/dist/assets/StatusCard-DNLyayXc.js +1 -0
  11. package/client/dist/assets/index-DQ0nwhec.js +257 -0
  12. package/client/dist/assets/index-DxmbVskS.css +1 -0
  13. package/client/dist/assets/vendor-flow-CZmBvHRo.js +1 -0
  14. package/client/dist/assets/vendor-icons-CVrPjN2Q.js +22 -0
  15. package/client/dist/assets/vendor-markdown-CRou3yQ5.js +62 -0
  16. package/client/dist/assets/vendor-misc-C4VxKHs5.js +1 -0
  17. package/client/dist/assets/vendor-query-SzWcOU0G.js +1 -0
  18. package/client/dist/assets/vendor-radix-Dnos29jG.js +56 -0
  19. package/client/dist/assets/vendor-react-DvWIbVx0.js +1 -0
  20. package/client/dist/index.html +37 -3
  21. package/client/index.html +28 -1
  22. package/client/package.json +44 -40
  23. package/client/src/App.tsx +2 -0
  24. package/client/src/Dashboard.tsx +157 -45
  25. package/client/src/ParameterPanel.tsx +3 -5
  26. package/client/src/adapters/nodeSpecToDescription.ts +1 -0
  27. package/client/src/assets/icons/NodeIcon.tsx +32 -0
  28. package/client/src/assets/icons/index.ts +4 -0
  29. package/client/src/assets/icons/stripe.svg +1 -0
  30. package/client/src/assets/icons/themedGlyphs.ts +404 -0
  31. package/client/src/components/AIAgentNode.tsx +77 -53
  32. package/client/src/components/GenericNode.tsx +34 -52
  33. package/client/src/components/OutputPanel.tsx +64 -147
  34. package/client/src/components/ParameterRenderer.tsx +5 -3
  35. package/client/src/components/SkillEditorModal.tsx +9 -18
  36. package/client/src/components/SquareNode.tsx +97 -115
  37. package/client/src/components/StartNode.tsx +32 -42
  38. package/client/src/components/SvgFilterDefs.tsx +54 -0
  39. package/client/src/components/TeamMonitorNode.tsx +12 -14
  40. package/client/src/components/ToolkitNode.tsx +35 -60
  41. package/client/src/components/TriggerNode.tsx +43 -77
  42. package/client/src/components/__tests__/CredentialsModal.test.tsx +49 -45
  43. package/client/src/components/credentials/CredentialsModal.tsx +98 -30
  44. package/client/src/components/credentials/CredentialsPalette.tsx +73 -5
  45. package/client/src/components/credentials/catalogueAdapter.ts +17 -1
  46. package/client/src/components/credentials/panels/ApiKeyPanel.tsx +102 -37
  47. package/client/src/components/credentials/panels/EmailPanel.tsx +7 -19
  48. package/client/src/components/credentials/panels/OAuthPanel.tsx +5 -1
  49. package/client/src/components/credentials/panels/QrPairingPanel.tsx +1 -3
  50. package/client/src/components/credentials/primitives/ActionBar.tsx +7 -11
  51. package/client/src/components/credentials/primitives/OAuthConnect.tsx +19 -28
  52. package/client/src/components/credentials/sections/ProviderDefaultsSection.tsx +24 -3
  53. package/client/src/components/credentials/types.ts +12 -2
  54. package/client/src/components/credentials/useCredentialPanel.ts +43 -19
  55. package/client/src/components/icons/AIProviderIcons.tsx +16 -0
  56. package/client/src/components/onboarding/OnboardingWizard.tsx +23 -63
  57. package/client/src/components/onboarding/nodeRoleClasses.ts +23 -0
  58. package/client/src/components/onboarding/steps/CanvasStep.tsx +15 -21
  59. package/client/src/components/onboarding/steps/ConceptsStep.tsx +2 -11
  60. package/client/src/components/onboarding/steps/GetStartedStep.tsx +2 -10
  61. package/client/src/components/parameterPanel/InputSection.tsx +9 -7
  62. package/client/src/components/parameterPanel/MasterSkillEditor.tsx +84 -198
  63. package/client/src/components/parameterPanel/MiddleSection.tsx +57 -80
  64. package/client/src/components/parameterPanel/ToolSchemaEditor.tsx +31 -25
  65. package/client/src/components/parameterPanel/__tests__/InputSection.test.tsx +7 -2
  66. package/client/src/components/ui/AIResultModal.tsx +1 -1
  67. package/client/src/components/ui/CollapsibleSection.tsx +9 -5
  68. package/client/src/components/ui/CommandPalette.tsx +147 -0
  69. package/client/src/components/ui/CommandPaletteHost.tsx +189 -0
  70. package/client/src/components/ui/ComponentItem.tsx +13 -7
  71. package/client/src/components/ui/ComponentPalette.tsx +24 -13
  72. package/client/src/components/ui/ConsolePanel.tsx +19 -11
  73. package/client/src/components/ui/DropCap.tsx +28 -0
  74. package/client/src/components/ui/EditableNodeLabel.tsx +10 -2
  75. package/client/src/components/ui/InputNodesPanel.tsx +1 -1
  76. package/client/src/components/ui/Modal.tsx +38 -6
  77. package/client/src/components/ui/OutputDisplayPanel.tsx +1 -1
  78. package/client/src/components/ui/SettingsPanel.tsx +42 -13
  79. package/client/src/components/ui/StatusBar.tsx +108 -0
  80. package/client/src/components/ui/ThemeSwitcher.tsx +109 -0
  81. package/client/src/components/ui/TopToolbar.tsx +42 -25
  82. package/client/src/components/ui/WorkflowSidebar.tsx +32 -16
  83. package/client/src/components/ui/action-button.tsx +40 -15
  84. package/client/src/components/ui/button.tsx +24 -1
  85. package/client/src/components/ui/dropdown-menu.tsx +24 -2
  86. package/client/src/components/ui/input.tsx +19 -2
  87. package/client/src/components/ui/select.tsx +15 -0
  88. package/client/src/components/ui/textarea.tsx +15 -2
  89. package/client/src/contexts/AuthContext.tsx +148 -109
  90. package/client/src/contexts/ThemeContext.tsx +93 -17
  91. package/client/src/contexts/WebSocketContext.tsx +373 -206
  92. package/client/src/contexts/__tests__/AuthContext.test.tsx +221 -0
  93. package/client/src/hooks/__tests__/useDragVariable.test.ts +7 -1
  94. package/client/src/hooks/__tests__/useWorkflowOpsListener.test.ts +142 -0
  95. package/client/src/hooks/useAppTheme.ts +209 -7
  96. package/client/src/hooks/useAutoSkillEdges.ts +7 -2
  97. package/client/src/hooks/useCatalogueQuery.ts +67 -1
  98. package/client/src/hooks/useDragVariable.ts +1 -1
  99. package/client/src/hooks/useNodeAllowlist.ts +115 -8
  100. package/client/src/hooks/useOnboarding.ts +20 -8
  101. package/client/src/hooks/useParameterPanel.ts +2 -1
  102. package/client/src/hooks/useReactFlowNodes.ts +2 -1
  103. package/client/src/hooks/useSound.ts +185 -0
  104. package/client/src/hooks/useWorkflowManagement.ts +6 -8
  105. package/client/src/hooks/useWorkflowOpsListener.ts +90 -0
  106. package/client/src/index.css +65 -3
  107. package/client/src/lib/__tests__/connectionConfig.test.ts +91 -0
  108. package/client/src/lib/aiModelProviders.ts +8 -0
  109. package/client/src/lib/connectionConfig.ts +107 -0
  110. package/client/src/lib/queryPersist.ts +13 -5
  111. package/client/src/lib/sound.ts +393 -0
  112. package/client/src/main.tsx +20 -0
  113. package/client/src/store/useAppStore.ts +26 -0
  114. package/client/src/styles/canvasAnimations.ts +37 -36
  115. package/client/src/styles/theme.ts +36 -20
  116. package/client/src/test/setup.ts +1 -0
  117. package/client/src/themes/atomic.css +253 -0
  118. package/client/src/themes/base.css +373 -0
  119. package/client/src/themes/cyber.css +890 -0
  120. package/client/src/themes/dark.css +70 -0
  121. package/client/src/themes/edo.css +246 -0
  122. package/client/src/themes/greek.css +293 -0
  123. package/client/src/themes/light.css +78 -0
  124. package/client/src/themes/plague.css +253 -0
  125. package/client/src/themes/renaissance.css +727 -0
  126. package/client/src/themes/rot.css +249 -0
  127. package/client/src/themes/steampunk.css +272 -0
  128. package/client/src/themes/surveillance.css +289 -0
  129. package/client/src/themes/wasteland.css +250 -0
  130. package/client/src/types/INodeProperties.ts +5 -0
  131. package/client/src/types/NodeTypes.ts +11 -1
  132. package/client/src/types/__tests__/cloudEvents.test.ts +99 -0
  133. package/client/src/types/cloudEvents.ts +78 -0
  134. package/client/src/vite-env.d.ts +7 -0
  135. package/client/tsconfig.json +1 -1
  136. package/client/vite.config.js +62 -2
  137. package/install.ps1 +1 -1
  138. package/install.sh +1 -1
  139. package/machina/commands/build.py +51 -7
  140. package/machina/pyproject.toml +4 -0
  141. package/machina/supervisor.py +12 -2
  142. package/machina/tree.py +71 -21
  143. package/package.json +4 -4
  144. package/scripts/install.js +16 -1
  145. package/server/config/ai_cli_providers.json +54 -0
  146. package/server/config/credential_providers.json +109 -2
  147. package/server/config/llm_defaults.json +24 -0
  148. package/server/config/model_registry.json +338 -499
  149. package/server/config/node_allowlist.json +16 -1
  150. package/server/config/pricing.json +8 -0
  151. package/server/constants.py +38 -15
  152. package/server/core/container.py +2 -2
  153. package/server/core/credentials_database.py +35 -2
  154. package/server/core/logging.py +4 -3
  155. package/server/main.py +99 -13
  156. package/server/models/node_metadata.py +1 -0
  157. package/server/nodejs/package.json +8 -6
  158. package/server/nodejs/src/index.ts +22 -5
  159. package/server/nodes/README.md +31 -4
  160. package/server/nodes/agent/_inline.py +2 -0
  161. package/server/nodes/agent/_specialized.py +6 -3
  162. package/server/nodes/agent/ai_agent.py +13 -3
  163. package/server/nodes/agent/chat_agent.py +6 -3
  164. package/server/nodes/agent/claude_code_agent.py +287 -75
  165. package/server/nodes/agent/codex_agent.py +239 -0
  166. package/server/nodes/agent/deep_agent.py +3 -3
  167. package/server/nodes/agent/rlm_agent.py +3 -3
  168. package/server/nodes/android/__init__.py +31 -1
  169. package/server/nodes/android/_base.py +9 -5
  170. package/server/{services/android_service.py → nodes/android/_dispatcher.py} +2 -2
  171. package/server/nodes/android/_handlers.py +154 -0
  172. package/server/nodes/android/_option_loaders.py +44 -0
  173. package/server/nodes/android/_refresh.py +127 -0
  174. package/server/{services/android → nodes/android/_relay}/client.py +4 -4
  175. package/server/{routers/android.py → nodes/android/_router.py} +27 -8
  176. package/server/nodes/browser/browser.py +2 -2
  177. package/server/nodes/code/_base.py +6 -2
  178. package/server/nodes/code/_claude_code.py +134 -0
  179. package/server/nodes/document/embedding_generator.py +3 -3
  180. package/server/nodes/document/http_scraper.py +3 -3
  181. package/server/nodes/document/vector_store.py +5 -5
  182. package/server/nodes/email/__init__.py +11 -1
  183. package/server/nodes/email/_filters.py +21 -0
  184. package/server/{services/himalaya_service.py → nodes/email/_himalaya.py} +6 -10
  185. package/server/{services/email_service.py → nodes/email/_service.py} +9 -13
  186. package/server/nodes/email/email_read.py +1 -1
  187. package/server/nodes/email/email_receive.py +54 -5
  188. package/server/nodes/email/email_send.py +1 -1
  189. package/server/nodes/filesystem/shell.py +24 -1
  190. package/server/nodes/google/__init__.py +55 -1
  191. package/server/{services/handlers/google_auth.py → nodes/google/_auth_helper.py} +8 -5
  192. package/server/nodes/google/_base.py +2 -2
  193. package/server/nodes/google/_credentials.py +5 -5
  194. package/server/nodes/google/_filters.py +25 -0
  195. package/server/nodes/google/_handlers.py +57 -0
  196. package/server/{services/google_oauth.py → nodes/google/_oauth.py} +195 -162
  197. package/server/nodes/google/_option_loaders.py +107 -0
  198. package/server/nodes/google/_refresh.py +66 -0
  199. package/server/nodes/google/_router.py +131 -0
  200. package/server/nodes/google/gmail_receive.py +41 -4
  201. package/server/nodes/groups.py +1 -0
  202. package/server/nodes/location/_credentials.py +45 -1
  203. package/server/{services/maps.py → nodes/location/_service.py} +18 -3
  204. package/server/nodes/location/gmaps_create.py +4 -4
  205. package/server/nodes/location/gmaps_locations.py +4 -4
  206. package/server/nodes/location/gmaps_nearby_places.py +4 -4
  207. package/server/nodes/model/_base.py +8 -3
  208. package/server/nodes/model/_credentials.py +96 -8
  209. package/server/nodes/model/_local_validator.py +345 -0
  210. package/server/nodes/model/lmstudio_chat_model.py +23 -0
  211. package/server/nodes/model/ollama_chat_model.py +25 -0
  212. package/server/nodes/proxy/_usage.py +2 -2
  213. package/server/nodes/proxy/proxy_config.py +14 -14
  214. package/server/nodes/proxy/proxy_request.py +4 -4
  215. package/server/nodes/scraper/_credentials.py +29 -1
  216. package/server/nodes/scraper/apify_actor.py +9 -9
  217. package/server/nodes/scraper/crawlee_scraper.py +5 -5
  218. package/server/nodes/search/brave_search.py +4 -0
  219. package/server/nodes/search/perplexity_search.py +9 -0
  220. package/server/nodes/search/serper_search.py +3 -0
  221. package/server/nodes/skill/simple_memory.py +12 -0
  222. package/server/nodes/social/_base.py +2 -2
  223. package/server/nodes/stripe/__init__.py +46 -0
  224. package/server/nodes/stripe/_credentials.py +33 -0
  225. package/server/nodes/stripe/_handlers.py +270 -0
  226. package/server/nodes/stripe/_install.py +127 -0
  227. package/server/nodes/stripe/_source.py +174 -0
  228. package/server/nodes/stripe/stripe_action.py +81 -0
  229. package/server/nodes/stripe/stripe_receive.py +92 -0
  230. package/server/nodes/telegram/_credentials.py +52 -1
  231. package/server/nodes/telegram/_handlers.py +19 -18
  232. package/server/nodes/telegram/_service.py +134 -32
  233. package/server/nodes/telegram/telegram_send.py +5 -6
  234. package/server/nodes/text/file_handler.py +2 -2
  235. package/server/nodes/text/text_generator.py +2 -2
  236. package/server/nodes/tool/agent_builder.py +630 -0
  237. package/server/nodes/tool/task_manager.py +144 -2
  238. package/server/nodes/twitter/__init__.py +38 -1
  239. package/server/nodes/twitter/_base.py +7 -7
  240. package/server/nodes/twitter/_credentials.py +1 -1
  241. package/server/nodes/twitter/_filters.py +37 -0
  242. package/server/nodes/twitter/_handlers.py +77 -0
  243. package/server/nodes/twitter/_oauth.py +124 -0
  244. package/server/nodes/twitter/_refresh.py +78 -0
  245. package/server/nodes/twitter/_router.py +29 -0
  246. package/server/nodes/twitter/twitter_receive.py +4 -0
  247. package/server/nodes/visuals.json +64 -19
  248. package/server/nodes/whatsapp/__init__.py +45 -5
  249. package/server/nodes/whatsapp/_base.py +3 -3
  250. package/server/nodes/whatsapp/_filters.py +137 -0
  251. package/server/nodes/whatsapp/_handlers.py +167 -0
  252. package/server/nodes/whatsapp/_option_loaders.py +68 -0
  253. package/server/nodes/whatsapp/_refresh.py +62 -0
  254. package/server/nodes/whatsapp/_runtime.py +1 -1
  255. package/server/pyproject.toml +29 -7
  256. package/server/routers/schemas.py +2 -2
  257. package/server/routers/webhook.py +26 -9
  258. package/server/routers/websocket.py +149 -810
  259. package/server/services/ai.py +89 -8
  260. package/server/services/auth.py +220 -43
  261. package/server/services/claude_oauth.py +126 -100
  262. package/server/services/cli_agent/__init__.py +78 -0
  263. package/server/services/cli_agent/_handlers.py +237 -0
  264. package/server/services/cli_agent/config.py +112 -0
  265. package/server/services/cli_agent/factory.py +48 -0
  266. package/server/services/cli_agent/lockfile.py +141 -0
  267. package/server/services/cli_agent/mcp_server.py +482 -0
  268. package/server/services/cli_agent/protocol.py +173 -0
  269. package/server/services/cli_agent/providers/__init__.py +9 -0
  270. package/server/services/cli_agent/providers/anthropic_claude.py +419 -0
  271. package/server/services/cli_agent/providers/google_gemini.py +80 -0
  272. package/server/services/cli_agent/providers/openai_codex.py +310 -0
  273. package/server/services/cli_agent/service.py +607 -0
  274. package/server/services/cli_agent/session.py +618 -0
  275. package/server/services/cli_agent/types.py +227 -0
  276. package/server/services/cli_agent/workflow_tools.py +233 -0
  277. package/server/services/credential_registry.py +26 -1
  278. package/server/services/deployment/manager.py +26 -145
  279. package/server/services/deployment/poll_registry.py +59 -0
  280. package/server/services/event_waiter.py +76 -246
  281. package/server/services/events/__init__.py +54 -0
  282. package/server/services/events/cli.py +78 -0
  283. package/server/services/events/daemon.py +163 -0
  284. package/server/services/events/envelope.py +281 -0
  285. package/server/services/events/lifecycle.py +99 -0
  286. package/server/services/events/oauth_lifecycle.py +534 -0
  287. package/server/services/events/polling.py +60 -0
  288. package/server/services/events/push.py +36 -0
  289. package/server/services/events/source.py +63 -0
  290. package/server/services/events/triggers.py +118 -0
  291. package/server/services/events/verifiers/__init__.py +25 -0
  292. package/server/services/events/verifiers/base.py +28 -0
  293. package/server/services/events/verifiers/github.py +25 -0
  294. package/server/services/events/verifiers/hmac_basic.py +32 -0
  295. package/server/services/events/verifiers/standard_webhooks.py +47 -0
  296. package/server/services/events/verifiers/stripe.py +42 -0
  297. package/server/services/events/webhook.py +105 -0
  298. package/server/services/handlers/tools.py +28 -186
  299. package/server/services/llm/config.py +7 -0
  300. package/server/services/llm/factory.py +8 -2
  301. package/server/services/memory/__init__.py +52 -0
  302. package/server/services/memory/jsonl.py +80 -0
  303. package/server/services/memory/markdown.py +65 -0
  304. package/server/services/memory/state.py +112 -0
  305. package/server/services/memory/vector_store.py +40 -0
  306. package/server/services/model_registry.py +76 -0
  307. package/server/services/node_allowlist.py +71 -15
  308. package/server/services/node_executor.py +2 -2
  309. package/server/services/node_output_schemas.py +21 -10
  310. package/server/services/node_spec.py +1 -1
  311. package/server/services/oauth_utils.py +1 -1
  312. package/server/services/plugin/__init__.py +2 -0
  313. package/server/services/plugin/base.py +44 -2
  314. package/server/services/plugin/credential.py +288 -1
  315. package/server/services/plugin/deps.py +105 -0
  316. package/server/services/plugin/edge_walker.py +12 -4
  317. package/server/services/plugin/oauth.py +381 -0
  318. package/server/services/plugin/polling.py +247 -0
  319. package/server/services/plugin/registry.py +145 -0
  320. package/server/services/plugin/singleton.py +65 -0
  321. package/server/services/plugin/ws.py +81 -0
  322. package/server/services/process_service.py +31 -2
  323. package/server/services/status_broadcaster.py +155 -238
  324. package/server/services/temporal/workflow.py +7 -7
  325. package/server/services/workflow.py +21 -3
  326. package/server/services/ws_handler_registry.py +111 -28
  327. package/server/skills/GUIDE.md +16 -1
  328. package/server/skills/assistant/agent-builder-skill/SKILL.md +166 -0
  329. package/server/skills/payments_agent/stripe-skill/SKILL.md +306 -0
  330. package/server/tests/credentials/test_auth_service.py +16 -9
  331. package/server/tests/credentials/test_credential_broadcasts.py +219 -0
  332. package/server/tests/credentials/test_google_oauth.py +6 -6
  333. package/server/tests/credentials/test_oauth_utils.py +1 -1
  334. package/server/tests/credentials/test_twitter_oauth.py +2 -2
  335. package/server/tests/credentials/test_websocket_handlers.py +44 -20
  336. package/server/tests/llm/test_factory.py +1 -0
  337. package/server/tests/llm/test_wiring.py +5 -1
  338. package/server/tests/nodes/_compat.py +24 -24
  339. package/server/tests/nodes/test_agent_builder.py +439 -0
  340. package/server/tests/nodes/test_ai_tools.py +18 -14
  341. package/server/tests/nodes/test_code_fs_process.py +17 -8
  342. package/server/tests/nodes/test_email.py +10 -9
  343. package/server/tests/nodes/test_google_workspace.py +2 -2
  344. package/server/tests/nodes/test_specialized_agents.py +100 -53
  345. package/server/tests/nodes/test_stripe_plugin.py +293 -0
  346. package/server/tests/nodes/test_telegram_social.py +4 -4
  347. package/server/tests/nodes/test_twitter.py +1 -1
  348. package/server/tests/nodes/test_web_automation.py +2 -2
  349. package/server/tests/nodes/test_whatsapp.py +9 -9
  350. package/server/tests/services/cli_agent/__init__.py +0 -0
  351. package/server/tests/services/cli_agent/test_mcp_server.py +432 -0
  352. package/server/tests/services/cli_agent/test_providers.py +358 -0
  353. package/server/tests/services/cli_agent/test_service.py +298 -0
  354. package/server/tests/services/memory/__init__.py +0 -0
  355. package/server/tests/services/memory/test_jsonl.py +188 -0
  356. package/server/tests/services/test_events.py +333 -0
  357. package/server/tests/test_node_spec.py +56 -16
  358. package/server/tests/test_plugin_helpers.py +116 -0
  359. package/server/tests/test_plugin_self_containment.py +486 -0
  360. package/server/tests/test_status_broadcasts.py +425 -0
  361. package/workflows/{AI Assistant_workflow-1777421105154-0m4snkzjf.json → AI Assistant_workflow-1778504793388-ou1m1tz2x.json } +70 -266
  362. package/workflows/{AI Employee_workflow-1777720598005-u4cm858dv.json → AI Employee_example_workflow-1777720598005-u4cm858dv.json } +112 -112
  363. package/workflows/Claude Assistant_workflow-1778380124051-mdibn807c.json +709 -0
  364. package/client/dist/assets/ActionBar-vzPpSR77.js +0 -1
  365. package/client/dist/assets/ApiKeyInput-Ds7AKFe8.js +0 -1
  366. package/client/dist/assets/ApiKeyPanel-gfblELep.js +0 -1
  367. package/client/dist/assets/ApiUsageSection-BMNWTe2r.js +0 -1
  368. package/client/dist/assets/EmailPanel-B1Om64p5.js +0 -1
  369. package/client/dist/assets/OAuthPanel-CXyQYGBz.js +0 -1
  370. package/client/dist/assets/QrPairingPanel-BgNuI1we.js +0 -1
  371. package/client/dist/assets/RateLimitSection-YYK8sx1T.js +0 -1
  372. package/client/dist/assets/StatusCard-DuYA5hJR.js +0 -1
  373. package/client/dist/assets/index-D9tZfgvi.js +0 -363
  374. package/client/dist/assets/index-al7snTkG.css +0 -1
  375. package/client/src/components/credentials/providers.tsx +0 -177
  376. package/server/routers/google.py +0 -277
  377. package/server/routers/maps.py +0 -142
  378. package/server/routers/twitter.py +0 -365
  379. package/server/services/claude_code_service.py +0 -106
  380. package/server/services/memory.py +0 -159
  381. package/server/services/node_option_loaders/__init__.py +0 -77
  382. package/server/services/node_option_loaders/android_loaders.py +0 -55
  383. package/server/services/node_option_loaders/google_loaders.py +0 -97
  384. package/server/services/node_option_loaders/whatsapp_loaders.py +0 -69
  385. package/server/services/twitter_oauth.py +0 -411
  386. package/server/services/websocket_client.py +0 -29
  387. /package/server/{services/android → nodes/android/_relay}/__init__.py +0 -0
  388. /package/server/{services/android → nodes/android/_relay}/broadcaster.py +0 -0
  389. /package/server/{services/android → nodes/android/_relay}/manager.py +0 -0
  390. /package/server/{services/android → nodes/android/_relay}/protocol.py +0 -0
  391. /package/server/{services/browser_service.py → nodes/browser/_service.py} +0 -0
  392. /package/server/{services/whatsapp_service.py → nodes/whatsapp/_service.py} +0 -0
  393. /package/server/skills/{task_agent → assistant}/write-todos-skill/SKILL.md +0 -0
@@ -1,4 +1,13 @@
1
- """Task Manager Tool — Wave 11.C migration."""
1
+ """Task Manager Tool — Wave 11.C migration; body inlined Wave 11.I (M.O).
2
+
3
+ Inspects the in-memory delegation registry that lives on
4
+ ``services.handlers.tools`` (``_delegated_tasks`` for in-flight asyncio
5
+ tasks, ``_delegation_results`` for completed-but-still-cached results,
6
+ plus ``get_delegated_task_status`` which adds a DB-fallback layer for
7
+ older runs). The plugin doesn't own that state — the delegation
8
+ lifecycle in ``tools.py`` does — so we read through to it rather than
9
+ duplicating registry plumbing here.
10
+ """
2
11
 
3
12
  from __future__ import annotations
4
13
 
@@ -6,8 +15,11 @@ from typing import Any, Dict, Literal, Optional
6
15
 
7
16
  from pydantic import BaseModel, ConfigDict, Field
8
17
 
18
+ from core.logging import get_logger
9
19
  from services.plugin import NodeContext, Operation, TaskQueue, ToolNode
10
20
 
21
+ logger = get_logger(__name__)
22
+
11
23
 
12
24
  class TaskManagerParams(BaseModel):
13
25
  action: Literal["create", "list", "complete", "delete", "update"] = "create"
@@ -49,7 +61,137 @@ class TaskManagerNode(ToolNode):
49
61
 
50
62
  @Operation("manage")
51
63
  async def manage(self, ctx: NodeContext, params: TaskManagerParams) -> Any:
52
- from services.handlers.tools import _execute_task_manager
53
64
  return await _execute_task_manager(
54
65
  params.model_dump(), {"node_id": ctx.node_id, "workspace_dir": ctx.workspace_dir or ""},
55
66
  )
67
+
68
+
69
+ async def _execute_task_manager(
70
+ tool_args: Dict[str, Any],
71
+ config: Dict[str, Any],
72
+ ) -> Dict[str, Any]:
73
+ """Execute task manager operations.
74
+
75
+ Dual-purpose: works as AI tool (LLM fills args) or workflow node
76
+ (uses params).
77
+
78
+ Operations:
79
+ - ``list_tasks``: list all active and completed delegated tasks
80
+ - ``get_task``: detailed status for one task
81
+ - ``mark_done``: drop a task from active tracking
82
+ """
83
+ # Read-through to the delegation registry owned by tools.py. Keeping
84
+ # the singletons there is intentional -- the entire delegation
85
+ # lifecycle (spawn / cleanup / refcount / DB persistence) lives in
86
+ # tools.py and is genuine cross-cutting framework state.
87
+ from services.handlers.tools import (
88
+ _delegated_tasks,
89
+ _delegation_results,
90
+ get_delegated_task_status,
91
+ )
92
+
93
+ params = config.get("parameters", {})
94
+ operation = tool_args.get("operation") or params.get("operation", "list_tasks")
95
+ task_id = tool_args.get("task_id") or params.get("task_id")
96
+ status_filter = tool_args.get("status_filter") or params.get("status_filter")
97
+ database = config.get("database")
98
+
99
+ logger.debug(
100
+ f"[TaskManager] Operation: {operation}, task_id: {task_id}, filter: {status_filter}"
101
+ )
102
+
103
+ if operation == "list_tasks":
104
+ tasks = []
105
+
106
+ # Active tasks from asyncio.Task tracking.
107
+ for tid, task in _delegated_tasks.items():
108
+ if task.done():
109
+ try:
110
+ if task.cancelled():
111
+ status = "cancelled"
112
+ elif task.exception():
113
+ status = "error"
114
+ else:
115
+ status = "completed"
116
+ except Exception:
117
+ status = "completed"
118
+ else:
119
+ status = "running"
120
+
121
+ tasks.append({"task_id": tid, "status": status, "active": True})
122
+
123
+ # Completed tasks from in-memory cache.
124
+ for tid, result in _delegation_results.items():
125
+ if tid not in [t["task_id"] for t in tasks]:
126
+ tasks.append({
127
+ "task_id": tid,
128
+ "status": result.get("status", "completed"),
129
+ "agent_name": result.get("agent_name"),
130
+ "result_summary": str(result.get("result", ""))[:200],
131
+ "active": False,
132
+ })
133
+
134
+ if status_filter:
135
+ tasks = [t for t in tasks if t.get("status") == status_filter]
136
+
137
+ return {
138
+ "success": True,
139
+ "operation": "list_tasks",
140
+ "tasks": tasks,
141
+ "count": len(tasks),
142
+ "running": sum(1 for t in tasks if t.get("status") == "running"),
143
+ "completed": sum(1 for t in tasks if t.get("status") == "completed"),
144
+ "errors": sum(1 for t in tasks if t.get("status") == "error"),
145
+ }
146
+
147
+ if operation == "get_task":
148
+ if not task_id:
149
+ return {"success": False, "error": "task_id is required for get_task operation"}
150
+
151
+ # 3-layer lookup (live tasks -> memory cache -> DB).
152
+ result = await get_delegated_task_status(task_ids=[task_id], database=database)
153
+ tasks = result.get("tasks", [])
154
+
155
+ if not tasks:
156
+ return {
157
+ "success": False,
158
+ "error": f"Task {task_id} not found",
159
+ "task_id": task_id,
160
+ }
161
+
162
+ task_info = tasks[0]
163
+ return {
164
+ "success": True,
165
+ "operation": "get_task",
166
+ "task_id": task_id,
167
+ "status": task_info.get("status"),
168
+ "agent_name": task_info.get("agent_name"),
169
+ "result": task_info.get("result"),
170
+ "error": task_info.get("error"),
171
+ }
172
+
173
+ if operation == "mark_done":
174
+ if not task_id:
175
+ return {"success": False, "error": "task_id is required for mark_done operation"}
176
+
177
+ removed = False
178
+ if task_id in _delegated_tasks:
179
+ del _delegated_tasks[task_id]
180
+ removed = True
181
+ if task_id in _delegation_results:
182
+ del _delegation_results[task_id]
183
+ removed = True
184
+
185
+ return {
186
+ "success": True,
187
+ "operation": "mark_done",
188
+ "task_id": task_id,
189
+ "removed": removed,
190
+ "message": (
191
+ f"Task {task_id} marked as done and removed from tracking"
192
+ if removed
193
+ else f"Task {task_id} was not in active tracking"
194
+ ),
195
+ }
196
+
197
+ return {"success": False, "error": f"Unknown operation: {operation}"}
@@ -1 +1,38 @@
1
- """Plugins for the 'twitter' palette group. See ../__init__.py for the package layout."""
1
+ """Plugins for the 'twitter' palette group.
2
+
3
+ Self-contained plugin folder (Wave 11.H pattern). Owns:
4
+
5
+ - Action / trigger nodes — ``twitter_send.py``, ``twitter_search.py``,
6
+ ``twitter_user.py``, ``twitter_receive.py`` (auto-registered via
7
+ ``BaseNode.__init_subclass__``).
8
+ - ``_credentials.py`` — :class:`TwitterCredential` (OAuth2).
9
+ - ``_oauth.py`` — OAuth 2.0 PKCE flow client (formerly
10
+ ``services/twitter_oauth.py``).
11
+ - ``_handlers.py`` — 3 WebSocket handlers
12
+ (``twitter_oauth_login`` / ``twitter_oauth_status`` /
13
+ ``twitter_logout``).
14
+ - ``_router.py`` — HTTP OAuth callback (``/api/twitter/callback``).
15
+
16
+ Two self-registration calls below — the central WS dispatcher and the
17
+ FastAPI app pick up the plugin's surface without ever importing this
18
+ module by name.
19
+ """
20
+
21
+ from services.event_waiter import register_filter_builder
22
+ from services.status_broadcaster import register_service_refresh
23
+ from services.ws_handler_registry import register_router, register_ws_handlers
24
+
25
+ from . import _router
26
+ from ._filters import build_filter as build_twitter_filter
27
+ from ._handlers import WS_HANDLERS
28
+ from ._refresh import refresh_twitter_status
29
+
30
+ # WebSocket handlers (3 message types) and the OAuth-callback HTTP
31
+ # router self-register so ``routers/websocket.py`` and ``main.py``
32
+ # stay free of per-plugin imports. Plus the service-status refresh
33
+ # callback (Wave 11.I, milestone J) and the trigger filter builder
34
+ # (milestone K).
35
+ register_ws_handlers(WS_HANDLERS)
36
+ register_router(_router.router, name="twitter")
37
+ register_service_refresh(refresh_twitter_status)
38
+ register_filter_builder("twitterReceive", build_twitter_filter)
@@ -28,12 +28,12 @@ async def track_twitter_usage(
28
28
  node_id: str, action: str, resource_count: int, context: Dict[str, Any],
29
29
  ) -> Dict[str, float]:
30
30
  """Record a Twitter API call in ``api_usage_metrics``."""
31
- from core.container import container
31
+ from services.plugin.deps import get_database
32
32
 
33
33
  pricing = get_pricing_service()
34
34
  cost_data = pricing.calculate_api_cost('twitter', action, resource_count)
35
35
 
36
- db = container.database()
36
+ db = get_database()
37
37
  await db.save_api_usage_metric({
38
38
  'session_id': context.get('session_id', 'default'),
39
39
  'node_id': node_id,
@@ -49,10 +49,10 @@ async def track_twitter_usage(
49
49
 
50
50
  async def get_twitter_client():
51
51
  """Build an XDK Client from the stored OAuth2 access token."""
52
- from core.container import container
52
+ from services.plugin.deps import get_auth_service
53
53
  from xdk import Client
54
54
 
55
- auth_service = container.auth_service()
55
+ auth_service = get_auth_service()
56
56
  tokens = await auth_service.get_oauth_tokens("twitter", customer_id="owner")
57
57
  if not tokens or not tokens.get("access_token"):
58
58
  raise RuntimeError("Twitter not connected. Please authenticate via Credentials.")
@@ -61,11 +61,11 @@ async def get_twitter_client():
61
61
 
62
62
  async def refresh_and_get_client():
63
63
  """Refresh the OAuth2 token and return a new client."""
64
- from core.container import container
65
- from services.twitter_oauth import TwitterOAuth
64
+ from nodes.twitter._oauth import TwitterOAuth
65
+ from services.plugin.deps import get_auth_service
66
66
  from xdk import Client
67
67
 
68
- auth_service = container.auth_service()
68
+ auth_service = get_auth_service()
69
69
  tokens = await auth_service.get_oauth_tokens("twitter", customer_id="owner")
70
70
  refresh_token = tokens.get("refresh_token", "") if tokens else ""
71
71
  if not refresh_token:
@@ -4,7 +4,7 @@ Used by the four twitter plugins in this folder (twitter_send, twitter_search,
4
4
  twitter_user, twitter_receive).
5
5
 
6
6
  OAuth 2.0 with PKCE. The refresh flow is non-trivial (custom code exchange
7
- in :mod:`services.twitter_oauth`), so :meth:`build_client` returns an
7
+ in :mod:`nodes.twitter._oauth`), so :meth:`build_client` returns an
8
8
  authenticated XDK ``Client`` — that's what the four twitter plugins
9
9
  actually hand to the XDK API methods. Use :func:`nodes.twitter._base.call_with_retry`
10
10
  on top; it transparently refreshes on 401/403 via this class.
@@ -0,0 +1,37 @@
1
+ """Twitter event-trigger filter builder (Wave 11.I, milestone K).
2
+
3
+ Moved verbatim from ``services/event_waiter.build_twitter_filter``.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from typing import Callable, Dict
9
+
10
+
11
+ def build_filter(params: Dict) -> Callable[[Dict], bool]:
12
+ """Build filter function for Twitter events.
13
+
14
+ Filters by:
15
+
16
+ - ``trigger_type`` -- ``mentions`` / ``search`` / ``user_timeline``
17
+ - ``search_query`` -- search query for ``search``
18
+ - ``user_id`` -- user ID for ``user_timeline``
19
+ """
20
+ trigger_type = params.get('trigger_type', 'mentions')
21
+ search_query = params.get('search_query', '')
22
+ user_id = params.get('user_id', '')
23
+
24
+ def matches(data: Dict) -> bool:
25
+ event_type = data.get('trigger_type', '')
26
+ if trigger_type != 'all' and event_type != trigger_type:
27
+ return False
28
+ if trigger_type == 'search' and search_query:
29
+ event_query = data.get('query', '')
30
+ if search_query.lower() not in event_query.lower():
31
+ return False
32
+ if trigger_type == 'user_timeline' and user_id:
33
+ if data.get('user_id') != user_id:
34
+ return False
35
+ return True
36
+
37
+ return matches
@@ -0,0 +1,77 @@
1
+ """Twitter / X WebSocket handlers — factory-built (Wave 11.I, S).
2
+
3
+ The 3 handlers (``twitter_oauth_login`` / ``twitter_oauth_status`` /
4
+ ``twitter_logout``) come from
5
+ :func:`services.events.oauth_lifecycle.make_oauth_lifecycle_handlers`.
6
+ The factory takes care of credential loading, redirect-URI derivation,
7
+ silent token refresh on status, revoke + remove + broadcast on
8
+ logout. Plugin-specific bits supplied via kwargs:
9
+
10
+ * ``oauth_factory`` -- async builder that constructs
11
+ :class:`TwitterOAuth` per call, pulling stored client_id /
12
+ client_secret from ``auth_service``.
13
+ * ``user_info_to_subject`` -- ``f"@{username}"`` (X has no email).
14
+ * ``extra_logout`` -- drops legacy API-key entries left behind by the
15
+ pre-OAuth layout.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ from typing import Any, Dict, Optional
21
+
22
+ from services.events.oauth_lifecycle import make_oauth_lifecycle_handlers
23
+
24
+ from ._oauth import TwitterOAuth
25
+
26
+
27
+ async def _twitter_oauth_factory(
28
+ *, redirect_uri: Optional[str] = None, **_kwargs,
29
+ ) -> TwitterOAuth:
30
+ """Build a :class:`TwitterOAuth` from stored client credentials."""
31
+ from services.plugin.deps import get_auth_service
32
+
33
+ auth_service = get_auth_service()
34
+ client_id = await auth_service.get_api_key("twitter_client_id") or ""
35
+ client_secret = await auth_service.get_api_key("twitter_client_secret")
36
+ return TwitterOAuth(
37
+ client_id=client_id,
38
+ client_secret=client_secret,
39
+ redirect_uri=redirect_uri or "",
40
+ )
41
+
42
+
43
+ def _user_info_to_handle(info: Dict[str, Any]) -> str:
44
+ return f"@{info.get('username', 'unknown')}"
45
+
46
+
47
+ async def _drop_legacy_api_key_entries() -> None:
48
+ """Remove stale API-key entries from the pre-OAuth layout.
49
+
50
+ Pre-Wave-11.I some flows mistakenly stored access / refresh tokens
51
+ as API keys. The OAuth tokens table is the canonical home now;
52
+ these orphans are cleaned on every logout for safety.
53
+ """
54
+ from services.plugin.deps import get_auth_service
55
+
56
+ auth_service = get_auth_service()
57
+ for key in ("twitter_access_token", "twitter_refresh_token", "twitter_user_info"):
58
+ try:
59
+ await auth_service.remove_api_key(key)
60
+ except Exception:
61
+ pass
62
+
63
+
64
+ WS_HANDLERS = make_oauth_lifecycle_handlers(
65
+ provider="twitter",
66
+ oauth_factory=_twitter_oauth_factory,
67
+ user_info_to_subject=_user_info_to_handle,
68
+ extra_logout=_drop_legacy_api_key_entries,
69
+ )
70
+
71
+ # Module-level aliases so the contract tests in
72
+ # ``tests/credentials/test_websocket_handlers.py`` can import the
73
+ # handlers by name. The dispatch table itself is what
74
+ # ``register_ws_handlers`` consumes.
75
+ handle_twitter_oauth_login = WS_HANDLERS["twitter_oauth_login"]
76
+ handle_twitter_oauth_status = WS_HANDLERS["twitter_oauth_status"]
77
+ handle_twitter_logout = WS_HANDLERS["twitter_logout"]
@@ -0,0 +1,124 @@
1
+ """Twitter / X OAuth 2.0 PKCE client.
2
+
3
+ Wave 11.I, milestone S: subclasses :class:`OAuth2PKCEClient` from
4
+ :mod:`services.plugin.oauth`. The base class owns the PKCE state
5
+ store, code-verifier generation, code exchange, token refresh, and
6
+ revocation. This file declares X-specific endpoints + scopes +
7
+ ``fetch_user_info`` translation.
8
+
9
+ Pre-S the file hand-rolled all of that (~410 LOC). The only Twitter-
10
+ specific behaviour now lives in this small subclass.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from typing import Any, Dict, List, Optional
16
+
17
+ import httpx
18
+
19
+ from core.logging import get_logger
20
+ from services.plugin.oauth import OAuth2PKCEClient, OAuthStateStore
21
+
22
+ logger = get_logger(__name__)
23
+
24
+ # X API OAuth 2.0 endpoints. Updated URLs per latest docs. Public so
25
+ # the contract tests in tests/credentials/test_twitter_oauth.py can
26
+ # pin them via respx mocks.
27
+ AUTHORIZATION_URL = "https://x.com/i/oauth2/authorize"
28
+ TOKEN_URL = "https://api.x.com/2/oauth2/token"
29
+ REVOKE_URL = "https://api.x.com/2/oauth2/revoke"
30
+ USER_INFO_URL = "https://api.x.com/2/users/me"
31
+
32
+ # Required scopes for full Twitter integration.
33
+ # See: https://docs.x.com/fundamentals/authentication/oauth-2-0/authorization-code
34
+ _DEFAULT_SCOPES = [
35
+ "tweet.read",
36
+ "tweet.write",
37
+ "users.read",
38
+ "follows.read",
39
+ "like.read",
40
+ "like.write",
41
+ "offline.access", # enables refresh tokens
42
+ ]
43
+
44
+
45
+ class TwitterOAuth(OAuth2PKCEClient):
46
+ """X (Twitter) OAuth 2.0 PKCE client."""
47
+
48
+ provider = "twitter"
49
+ authorization_endpoint = AUTHORIZATION_URL
50
+ token_endpoint = TOKEN_URL
51
+ revocation_endpoint = REVOKE_URL
52
+
53
+ # Plugin-scoped state store -- isolated from Google's instance.
54
+ state_store = OAuthStateStore()
55
+
56
+ DEFAULT_SCOPES = _DEFAULT_SCOPES
57
+
58
+ def __init__(
59
+ self,
60
+ client_id: str,
61
+ redirect_uri: str,
62
+ client_secret: Optional[str] = None,
63
+ scopes: Optional[List[str]] = None,
64
+ ) -> None:
65
+ super().__init__(
66
+ client_id=client_id,
67
+ redirect_uri=redirect_uri,
68
+ client_secret=client_secret,
69
+ scopes=scopes,
70
+ )
71
+
72
+ # Back-compat alias for the contract tests in
73
+ # tests/credentials/test_twitter_oauth.py -- the unified protocol
74
+ # method is :meth:`fetch_user_info`.
75
+ async def get_user_info(self, access_token: str) -> Dict[str, Any]:
76
+ return await self.fetch_user_info(access_token)
77
+
78
+ async def fetch_user_info(self, access_token: str) -> Dict[str, Any]:
79
+ """Translate X's ``/users/me`` response into the unified shape."""
80
+ try:
81
+ async with httpx.AsyncClient(timeout=30.0) as client:
82
+ response = await client.get(
83
+ USER_INFO_URL,
84
+ params={
85
+ "user.fields": "id,name,username,profile_image_url,verified",
86
+ },
87
+ headers={"Authorization": f"Bearer {access_token}"},
88
+ )
89
+ except httpx.HTTPError as exc:
90
+ logger.error(f"[twitter] HTTP error getting user info: {exc}")
91
+ return {"success": False, "error": str(exc)}
92
+
93
+ if response.status_code != 200:
94
+ error_data = response.json() if response.text else {}
95
+ return {
96
+ "success": False,
97
+ "error": error_data.get("detail")
98
+ or error_data.get("title", "Failed to get user info"),
99
+ }
100
+
101
+ user = response.json().get("data", {})
102
+ return {
103
+ "success": True,
104
+ "id": user.get("id"),
105
+ "username": user.get("username"),
106
+ "name": user.get("name"),
107
+ "profile_image_url": user.get("profile_image_url"),
108
+ "verified": user.get("verified", False),
109
+ }
110
+
111
+
112
+ # Module-level alias for the contract tests' ``_oauth_states.clear()``
113
+ # pattern -- this is the same dict the class-level state store wraps,
114
+ # so clearing one clears the other.
115
+ _oauth_states = TwitterOAuth.state_store._states
116
+
117
+
118
+ __all__ = [
119
+ "TwitterOAuth",
120
+ "AUTHORIZATION_URL",
121
+ "TOKEN_URL",
122
+ "REVOKE_URL",
123
+ "USER_INFO_URL",
124
+ ]
@@ -0,0 +1,78 @@
1
+ """Twitter service-status refresh callback (Wave 11.I, milestone J).
2
+
3
+ Moved from ``services/status_broadcaster._refresh_twitter_status``.
4
+ Plugin packages register their own callback via
5
+ ``status_broadcaster.register_service_refresh``; the broadcaster no
6
+ longer hardcodes a per-service refresh.
7
+
8
+ Reads OAuth tokens via ``auth_service.get_oauth_tokens("twitter")`` and
9
+ mirrors the result into the broadcaster cache. No plugin internals
10
+ are reached -- pure auth-service read + broadcast. The function still
11
+ runs in the broadcaster's TaskGroup so OTel spans aggregate the same
12
+ way they did pre-migration.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import logging
18
+ from typing import TYPE_CHECKING
19
+
20
+ from opentelemetry import trace
21
+
22
+ if TYPE_CHECKING:
23
+ from services.status_broadcaster import StatusBroadcaster
24
+
25
+ logger = logging.getLogger(__name__)
26
+ tracer = trace.get_tracer(__name__)
27
+
28
+
29
+ async def refresh_twitter_status(broadcaster: "StatusBroadcaster") -> None:
30
+ """Refresh Twitter cache + broadcast. One pass per
31
+ ``_refresh_all_services`` cycle.
32
+ """
33
+ with tracer.start_as_current_span("broadcaster.refresh_twitter") as span:
34
+ try:
35
+ from services.plugin.deps import get_auth_service
36
+
37
+ auth_service = get_auth_service()
38
+ tokens = await auth_service.get_oauth_tokens(
39
+ "twitter", customer_id="owner"
40
+ )
41
+ if not tokens or not tokens.get("access_token"):
42
+ broadcaster._status["twitter"] = {
43
+ "connected": False,
44
+ "username": None,
45
+ "user_id": None,
46
+ "name": None,
47
+ "profile_image_url": None,
48
+ }
49
+ else:
50
+ # User info is stored in the OAuth token record.
51
+ # email field carries the ``@username`` form.
52
+ email = tokens.get("email", "")
53
+ name = tokens.get("name", "")
54
+ username = email.lstrip("@") if email.startswith("@") else email
55
+ broadcaster._status["twitter"] = {
56
+ "connected": True,
57
+ "username": username or None,
58
+ "user_id": None,
59
+ "name": name or None,
60
+ "profile_image_url": None,
61
+ }
62
+ logger.debug(
63
+ "[StatusBroadcaster] Twitter status: connected as @%s",
64
+ username,
65
+ )
66
+
67
+ await broadcaster.broadcast({
68
+ "type": "twitter_status",
69
+ "data": broadcaster._status["twitter"],
70
+ })
71
+ span.set_attribute(
72
+ "connected", bool(broadcaster._status["twitter"]["connected"])
73
+ )
74
+ except Exception as exc: # noqa: BLE001 -- mirror pre-migration behaviour
75
+ span.record_exception(exc)
76
+ logger.debug(
77
+ "[StatusBroadcaster] Could not refresh Twitter status: %s", exc
78
+ )
@@ -0,0 +1,29 @@
1
+ """Twitter / X OAuth callback router — factory-built (Wave 11.I, S).
2
+
3
+ The single ``GET /api/twitter/callback`` route comes from
4
+ :func:`services.events.oauth_lifecycle.make_oauth_callback_router`.
5
+ Pre-S the file hand-rolled the callback + status + logout REST routes
6
+ (~370 LOC). Status + logout were duplicates of the WS handlers in
7
+ ``_handlers.py`` and have been retired -- the WS path is canonical.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from typing import Any, Dict
13
+
14
+ from services.events.oauth_lifecycle import make_oauth_callback_router
15
+
16
+ from ._handlers import _twitter_oauth_factory
17
+
18
+
19
+ def _user_info_to_email(info: Dict[str, Any]) -> str:
20
+ """X has no email field -- use ``@username`` as the identifier."""
21
+ return f"@{info.get('username', 'unknown')}"
22
+
23
+
24
+ router = make_oauth_callback_router(
25
+ provider="twitter",
26
+ oauth_factory=_twitter_oauth_factory,
27
+ user_info_to_email=_user_info_to_email,
28
+ color_hex="#00ba7c", # X brand green for the success page
29
+ )
@@ -39,6 +39,10 @@ class TwitterReceiveNode(TriggerNode):
39
39
  group = ("social", "trigger")
40
40
  description = "Trigger workflow on Twitter mentions, search results, or timeline updates (polling-based)"
41
41
  component_kind = "trigger"
42
+ # Wave 11.I, milestone K: ``event_type`` ClassVar lets
43
+ # ``event_waiter._auto_populate_from_plugins`` backfill
44
+ # TRIGGER_REGISTRY without a hardcoded entry in event_waiter.
45
+ event_type = "twitter_event_received"
42
46
  handles = (
43
47
  {"name": "output-main", "kind": "output", "position": "right",
44
48
  "label": "Output", "role": "main"},