openhands-automation 1.0.0a1__tar.gz

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 (279) hide show
  1. openhands_automation-1.0.0a1/.dockerignore +32 -0
  2. openhands_automation-1.0.0a1/.github/workflows/ci.yml +56 -0
  3. openhands_automation-1.0.0a1/.github/workflows/ghcr-build.yml +115 -0
  4. openhands_automation-1.0.0a1/.github/workflows/pr-review-by-openhands.yml +48 -0
  5. openhands_automation-1.0.0a1/.github/workflows/pypi-release.yml +40 -0
  6. openhands_automation-1.0.0a1/.github/workflows/tag-image.yml +58 -0
  7. openhands_automation-1.0.0a1/.github/workflows/tests.yml +49 -0
  8. openhands_automation-1.0.0a1/.github/workflows/trigger-deploy-preview.yml +235 -0
  9. openhands_automation-1.0.0a1/.gitignore +193 -0
  10. openhands_automation-1.0.0a1/.pre-commit-config.yaml +45 -0
  11. openhands_automation-1.0.0a1/.python-version +1 -0
  12. openhands_automation-1.0.0a1/AGENTS.md +213 -0
  13. openhands_automation-1.0.0a1/LICENSE +21 -0
  14. openhands_automation-1.0.0a1/Makefile +96 -0
  15. openhands_automation-1.0.0a1/PKG-INFO +128 -0
  16. openhands_automation-1.0.0a1/README.md +97 -0
  17. openhands_automation-1.0.0a1/alembic.ini +37 -0
  18. openhands_automation-1.0.0a1/containers/Dockerfile +45 -0
  19. openhands_automation-1.0.0a1/frontend/.env.example +3 -0
  20. openhands_automation-1.0.0a1/frontend/.eslintrc +73 -0
  21. openhands_automation-1.0.0a1/frontend/.gitignore +10 -0
  22. openhands_automation-1.0.0a1/frontend/.husky/pre-commit +2 -0
  23. openhands_automation-1.0.0a1/frontend/.prettierrc.json +3 -0
  24. openhands_automation-1.0.0a1/frontend/README.md +234 -0
  25. openhands_automation-1.0.0a1/frontend/__mocks__/zustand.ts +60 -0
  26. openhands_automation-1.0.0a1/frontend/commitlint.config.cjs +3 -0
  27. openhands_automation-1.0.0a1/frontend/package-lock.json +12444 -0
  28. openhands_automation-1.0.0a1/frontend/package.json +109 -0
  29. openhands_automation-1.0.0a1/frontend/playwright.config.ts +25 -0
  30. openhands_automation-1.0.0a1/frontend/public/mockServiceWorker.js +349 -0
  31. openhands_automation-1.0.0a1/frontend/react-router.config.ts +28 -0
  32. openhands_automation-1.0.0a1/frontend/scripts/check-translation-completeness.cjs +102 -0
  33. openhands_automation-1.0.0a1/frontend/scripts/dev-proxy.mjs +101 -0
  34. openhands_automation-1.0.0a1/frontend/scripts/make-i18n-translations.cjs +51 -0
  35. openhands_automation-1.0.0a1/frontend/src/__tests__/api/automation-service.test.ts +180 -0
  36. openhands_automation-1.0.0a1/frontend/src/__tests__/api/openhands-service.test.ts +61 -0
  37. openhands_automation-1.0.0a1/frontend/src/__tests__/components/automations/automation-card.test.tsx +136 -0
  38. openhands_automation-1.0.0a1/frontend/src/__tests__/components/automations/automation-group.test.tsx +53 -0
  39. openhands_automation-1.0.0a1/frontend/src/__tests__/components/automations/create-instructions.test.tsx +123 -0
  40. openhands_automation-1.0.0a1/frontend/src/__tests__/components/automations/delete-confirmation-modal.test.tsx +71 -0
  41. openhands_automation-1.0.0a1/frontend/src/__tests__/components/automations/detail/active-status-badge.test.tsx +15 -0
  42. openhands_automation-1.0.0a1/frontend/src/__tests__/components/automations/detail/activity-log-item.test.tsx +156 -0
  43. openhands_automation-1.0.0a1/frontend/src/__tests__/components/automations/detail/activity-log-section.test.tsx +98 -0
  44. openhands_automation-1.0.0a1/frontend/src/__tests__/components/automations/detail/activity-section.test.tsx +79 -0
  45. openhands_automation-1.0.0a1/frontend/src/__tests__/components/automations/detail/back-link.test.tsx +29 -0
  46. openhands_automation-1.0.0a1/frontend/src/__tests__/components/automations/detail/branch-badge.test.tsx +15 -0
  47. openhands_automation-1.0.0a1/frontend/src/__tests__/components/automations/detail/config-field.test.tsx +29 -0
  48. openhands_automation-1.0.0a1/frontend/src/__tests__/components/automations/detail/configuration-section.test.tsx +76 -0
  49. openhands_automation-1.0.0a1/frontend/src/__tests__/components/automations/detail/detail-header.test.tsx +119 -0
  50. openhands_automation-1.0.0a1/frontend/src/__tests__/components/automations/detail/not-found-state.test.tsx +24 -0
  51. openhands_automation-1.0.0a1/frontend/src/__tests__/components/automations/detail/plugin-chip.test.tsx +15 -0
  52. openhands_automation-1.0.0a1/frontend/src/__tests__/components/automations/detail/plugins-section.test.tsx +21 -0
  53. openhands_automation-1.0.0a1/frontend/src/__tests__/components/automations/detail/prompt-section.test.tsx +21 -0
  54. openhands_automation-1.0.0a1/frontend/src/__tests__/components/automations/detail/run-status-badge.test.tsx +28 -0
  55. openhands_automation-1.0.0a1/frontend/src/__tests__/components/automations/detail/section-card.test.tsx +17 -0
  56. openhands_automation-1.0.0a1/frontend/src/__tests__/components/automations/empty-state.test.tsx +66 -0
  57. openhands_automation-1.0.0a1/frontend/src/__tests__/components/automations/error-state.test.tsx +23 -0
  58. openhands_automation-1.0.0a1/frontend/src/__tests__/components/automations/kebab-menu.test.tsx +63 -0
  59. openhands_automation-1.0.0a1/frontend/src/__tests__/components/automations/metadata-chip.test.tsx +17 -0
  60. openhands_automation-1.0.0a1/frontend/src/__tests__/components/automations/permission-gating.test.tsx +141 -0
  61. openhands_automation-1.0.0a1/frontend/src/__tests__/components/automations/search-input.test.tsx +28 -0
  62. openhands_automation-1.0.0a1/frontend/src/__tests__/components/automations/toggle-switch.test.tsx +38 -0
  63. openhands_automation-1.0.0a1/frontend/src/__tests__/components/reauth-modal.test.tsx +12 -0
  64. openhands_automation-1.0.0a1/frontend/src/__tests__/components/shared/modals/modal-backdrop.test.tsx +76 -0
  65. openhands_automation-1.0.0a1/frontend/src/__tests__/components/shared/modals/modal-body.test.tsx +61 -0
  66. openhands_automation-1.0.0a1/frontend/src/__tests__/hooks/use-auto-login.test.ts +102 -0
  67. openhands_automation-1.0.0a1/frontend/src/__tests__/hooks/use-cross-tab-state.test.ts +54 -0
  68. openhands_automation-1.0.0a1/frontend/src/__tests__/hooks/use-escape-key.test.ts +53 -0
  69. openhands_automation-1.0.0a1/frontend/src/__tests__/hooks/use-has-permission.test.ts +83 -0
  70. openhands_automation-1.0.0a1/frontend/src/__tests__/hooks/use-is-authed.test.ts +75 -0
  71. openhands_automation-1.0.0a1/frontend/src/__tests__/hooks/use-language-sync.test.ts +142 -0
  72. openhands_automation-1.0.0a1/frontend/src/__tests__/hooks/use-me.test.ts +61 -0
  73. openhands_automation-1.0.0a1/frontend/src/__tests__/hooks/use-org-sync.test.ts +129 -0
  74. openhands_automation-1.0.0a1/frontend/src/__tests__/hooks/use-theme-sync.test.ts +114 -0
  75. openhands_automation-1.0.0a1/frontend/src/__tests__/routes/automation-detail.test.tsx +277 -0
  76. openhands_automation-1.0.0a1/frontend/src/__tests__/routes/automations-list.test.tsx +242 -0
  77. openhands_automation-1.0.0a1/frontend/src/__tests__/routes/root-layout.test.tsx +173 -0
  78. openhands_automation-1.0.0a1/frontend/src/__tests__/stores/user-store.test.ts +42 -0
  79. openhands_automation-1.0.0a1/frontend/src/__tests__/utils/generate-auth-url.test.ts +88 -0
  80. openhands_automation-1.0.0a1/frontend/src/__tests__/utils/local-storage.test.ts +60 -0
  81. openhands_automation-1.0.0a1/frontend/src/api/automation-service.ts +71 -0
  82. openhands_automation-1.0.0a1/frontend/src/api/axios-clients.ts +21 -0
  83. openhands_automation-1.0.0a1/frontend/src/api/openhands-service.ts +16 -0
  84. openhands_automation-1.0.0a1/frontend/src/assets/branding/openhands-logo.svg +9 -0
  85. openhands_automation-1.0.0a1/frontend/src/components/.gitkeep +0 -0
  86. openhands_automation-1.0.0a1/frontend/src/components/automations/automation-card-skeleton.tsx +19 -0
  87. openhands_automation-1.0.0a1/frontend/src/components/automations/automation-card.tsx +102 -0
  88. openhands_automation-1.0.0a1/frontend/src/components/automations/automation-group.tsx +40 -0
  89. openhands_automation-1.0.0a1/frontend/src/components/automations/create-instructions.tsx +119 -0
  90. openhands_automation-1.0.0a1/frontend/src/components/automations/delete-confirmation-modal.tsx +70 -0
  91. openhands_automation-1.0.0a1/frontend/src/components/automations/detail/active-status-badge.tsx +24 -0
  92. openhands_automation-1.0.0a1/frontend/src/components/automations/detail/activity-log-item.tsx +69 -0
  93. openhands_automation-1.0.0a1/frontend/src/components/automations/detail/activity-log-section.tsx +78 -0
  94. openhands_automation-1.0.0a1/frontend/src/components/automations/detail/activity-section.tsx +77 -0
  95. openhands_automation-1.0.0a1/frontend/src/components/automations/detail/back-link.tsx +18 -0
  96. openhands_automation-1.0.0a1/frontend/src/components/automations/detail/branch-badge.tsx +11 -0
  97. openhands_automation-1.0.0a1/frontend/src/components/automations/detail/config-field.tsx +17 -0
  98. openhands_automation-1.0.0a1/frontend/src/components/automations/detail/configuration-section.tsx +85 -0
  99. openhands_automation-1.0.0a1/frontend/src/components/automations/detail/detail-header.tsx +67 -0
  100. openhands_automation-1.0.0a1/frontend/src/components/automations/detail/detail-skeleton.tsx +28 -0
  101. openhands_automation-1.0.0a1/frontend/src/components/automations/detail/not-found-state.tsx +23 -0
  102. openhands_automation-1.0.0a1/frontend/src/components/automations/detail/plugin-chip.tsx +11 -0
  103. openhands_automation-1.0.0a1/frontend/src/components/automations/detail/plugins-section.tsx +26 -0
  104. openhands_automation-1.0.0a1/frontend/src/components/automations/detail/prompt-section.tsx +25 -0
  105. openhands_automation-1.0.0a1/frontend/src/components/automations/detail/run-status-badge.tsx +58 -0
  106. openhands_automation-1.0.0a1/frontend/src/components/automations/detail/section-card.tsx +17 -0
  107. openhands_automation-1.0.0a1/frontend/src/components/automations/empty-state.tsx +22 -0
  108. openhands_automation-1.0.0a1/frontend/src/components/automations/error-state.tsx +27 -0
  109. openhands_automation-1.0.0a1/frontend/src/components/automations/kebab-menu.tsx +69 -0
  110. openhands_automation-1.0.0a1/frontend/src/components/automations/metadata-chip.tsx +13 -0
  111. openhands_automation-1.0.0a1/frontend/src/components/automations/search-input.tsx +25 -0
  112. openhands_automation-1.0.0a1/frontend/src/components/automations/status-badge.tsx +11 -0
  113. openhands_automation-1.0.0a1/frontend/src/components/automations/toggle-switch.tsx +33 -0
  114. openhands_automation-1.0.0a1/frontend/src/components/error-boundary.tsx +48 -0
  115. openhands_automation-1.0.0a1/frontend/src/components/reauth-modal.tsx +22 -0
  116. openhands_automation-1.0.0a1/frontend/src/components/shared/modals/modal-backdrop.tsx +35 -0
  117. openhands_automation-1.0.0a1/frontend/src/components/shared/modals/modal-body.tsx +32 -0
  118. openhands_automation-1.0.0a1/frontend/src/constants/.gitkeep +0 -0
  119. openhands_automation-1.0.0a1/frontend/src/entry.client.tsx +37 -0
  120. openhands_automation-1.0.0a1/frontend/src/hooks/.gitkeep +0 -0
  121. openhands_automation-1.0.0a1/frontend/src/hooks/use-auth-url.ts +8 -0
  122. openhands_automation-1.0.0a1/frontend/src/hooks/use-auto-login.ts +59 -0
  123. openhands_automation-1.0.0a1/frontend/src/hooks/use-automation-detail.ts +23 -0
  124. openhands_automation-1.0.0a1/frontend/src/hooks/use-automations.ts +35 -0
  125. openhands_automation-1.0.0a1/frontend/src/hooks/use-cross-tab-state.ts +48 -0
  126. openhands_automation-1.0.0a1/frontend/src/hooks/use-escape-key.ts +26 -0
  127. openhands_automation-1.0.0a1/frontend/src/hooks/use-has-permission.ts +9 -0
  128. openhands_automation-1.0.0a1/frontend/src/hooks/use-is-authed.ts +25 -0
  129. openhands_automation-1.0.0a1/frontend/src/hooks/use-language-sync.ts +54 -0
  130. openhands_automation-1.0.0a1/frontend/src/hooks/use-me.ts +18 -0
  131. openhands_automation-1.0.0a1/frontend/src/hooks/use-org-sync.ts +40 -0
  132. openhands_automation-1.0.0a1/frontend/src/hooks/use-theme-sync.ts +39 -0
  133. openhands_automation-1.0.0a1/frontend/src/i18n/index.ts +38 -0
  134. openhands_automation-1.0.0a1/frontend/src/i18n/translation.json +1005 -0
  135. openhands_automation-1.0.0a1/frontend/src/icons/.gitkeep +0 -0
  136. openhands_automation-1.0.0a1/frontend/src/icons/activity.svg +3 -0
  137. openhands_automation-1.0.0a1/frontend/src/icons/bell.svg +3 -0
  138. openhands_automation-1.0.0a1/frontend/src/icons/calendar.svg +3 -0
  139. openhands_automation-1.0.0a1/frontend/src/icons/check-circle.svg +3 -0
  140. openhands_automation-1.0.0a1/frontend/src/icons/chevron-down.svg +3 -0
  141. openhands_automation-1.0.0a1/frontend/src/icons/chevron-left.svg +3 -0
  142. openhands_automation-1.0.0a1/frontend/src/icons/clock.svg +3 -0
  143. openhands_automation-1.0.0a1/frontend/src/icons/cog.svg +4 -0
  144. openhands_automation-1.0.0a1/frontend/src/icons/database.svg +3 -0
  145. openhands_automation-1.0.0a1/frontend/src/icons/exclamation-circle.svg +3 -0
  146. openhands_automation-1.0.0a1/frontend/src/icons/folder.svg +3 -0
  147. openhands_automation-1.0.0a1/frontend/src/icons/git-branch.svg +6 -0
  148. openhands_automation-1.0.0a1/frontend/src/icons/kebab-vertical.svg +3 -0
  149. openhands_automation-1.0.0a1/frontend/src/icons/power.svg +3 -0
  150. openhands_automation-1.0.0a1/frontend/src/icons/puzzle.svg +3 -0
  151. openhands_automation-1.0.0a1/frontend/src/icons/search.svg +3 -0
  152. openhands_automation-1.0.0a1/frontend/src/icons/sparkle.svg +3 -0
  153. openhands_automation-1.0.0a1/frontend/src/icons/svg.d.ts +6 -0
  154. openhands_automation-1.0.0a1/frontend/src/icons/target.svg +3 -0
  155. openhands_automation-1.0.0a1/frontend/src/icons/terminal.svg +3 -0
  156. openhands_automation-1.0.0a1/frontend/src/icons/trash.svg +3 -0
  157. openhands_automation-1.0.0a1/frontend/src/icons/x-circle.svg +3 -0
  158. openhands_automation-1.0.0a1/frontend/src/icons/x-mark.svg +3 -0
  159. openhands_automation-1.0.0a1/frontend/src/index.css +40 -0
  160. openhands_automation-1.0.0a1/frontend/src/mocks/auth-handlers.ts +41 -0
  161. openhands_automation-1.0.0a1/frontend/src/mocks/automation-handlers.ts +123 -0
  162. openhands_automation-1.0.0a1/frontend/src/mocks/automation-runs.mock.ts +61 -0
  163. openhands_automation-1.0.0a1/frontend/src/mocks/automations.mock.ts +127 -0
  164. openhands_automation-1.0.0a1/frontend/src/mocks/browser.ts +4 -0
  165. openhands_automation-1.0.0a1/frontend/src/mocks/handlers.ts +4 -0
  166. openhands_automation-1.0.0a1/frontend/src/mocks/node.ts +4 -0
  167. openhands_automation-1.0.0a1/frontend/src/query-client-config.ts +62 -0
  168. openhands_automation-1.0.0a1/frontend/src/root.tsx +35 -0
  169. openhands_automation-1.0.0a1/frontend/src/routes/automation-detail.tsx +93 -0
  170. openhands_automation-1.0.0a1/frontend/src/routes/automations-list.tsx +144 -0
  171. openhands_automation-1.0.0a1/frontend/src/routes/root-layout.tsx +93 -0
  172. openhands_automation-1.0.0a1/frontend/src/routes.ts +13 -0
  173. openhands_automation-1.0.0a1/frontend/src/stores/.gitkeep +0 -0
  174. openhands_automation-1.0.0a1/frontend/src/stores/user-store.ts +22 -0
  175. openhands_automation-1.0.0a1/frontend/src/tailwind.css +52 -0
  176. openhands_automation-1.0.0a1/frontend/src/types/.gitkeep +0 -0
  177. openhands_automation-1.0.0a1/frontend/src/types/automation.ts +48 -0
  178. openhands_automation-1.0.0a1/frontend/src/types/user.ts +9 -0
  179. openhands_automation-1.0.0a1/frontend/src/utils/custom-toast-handlers.tsx +42 -0
  180. openhands_automation-1.0.0a1/frontend/src/utils/generate-auth-url.ts +43 -0
  181. openhands_automation-1.0.0a1/frontend/src/utils/local-storage.ts +36 -0
  182. openhands_automation-1.0.0a1/frontend/src/utils/permissions.ts +8 -0
  183. openhands_automation-1.0.0a1/frontend/src/utils/toast-duration.ts +27 -0
  184. openhands_automation-1.0.0a1/frontend/src/utils/utils.ts +6 -0
  185. openhands_automation-1.0.0a1/frontend/tailwind.config.js +65 -0
  186. openhands_automation-1.0.0a1/frontend/tsconfig.json +30 -0
  187. openhands_automation-1.0.0a1/frontend/vite.config.ts +83 -0
  188. openhands_automation-1.0.0a1/frontend/vitest.setup.ts +39 -0
  189. openhands_automation-1.0.0a1/migrations/env.py +152 -0
  190. openhands_automation-1.0.0a1/migrations/script.py.mako +25 -0
  191. openhands_automation-1.0.0a1/migrations/versions/001_initial_schema.py +129 -0
  192. openhands_automation-1.0.0a1/migrations/versions/002_tarball_uploads.py +64 -0
  193. openhands_automation-1.0.0a1/migrations/versions/003_event_triggers.py +92 -0
  194. openhands_automation-1.0.0a1/migrations/versions/004_add_prompt_column.py +29 -0
  195. openhands_automation-1.0.0a1/openhands/automation/__init__.py +3 -0
  196. openhands_automation-1.0.0a1/openhands/automation/app.py +306 -0
  197. openhands_automation-1.0.0a1/openhands/automation/auth.py +372 -0
  198. openhands_automation-1.0.0a1/openhands/automation/backends/__init__.py +67 -0
  199. openhands_automation-1.0.0a1/openhands/automation/backends/base.py +145 -0
  200. openhands_automation-1.0.0a1/openhands/automation/backends/cloud.py +302 -0
  201. openhands_automation-1.0.0a1/openhands/automation/backends/local.py +180 -0
  202. openhands_automation-1.0.0a1/openhands/automation/config.py +573 -0
  203. openhands_automation-1.0.0a1/openhands/automation/constants.py +83 -0
  204. openhands_automation-1.0.0a1/openhands/automation/db.py +210 -0
  205. openhands_automation-1.0.0a1/openhands/automation/dispatcher.py +429 -0
  206. openhands_automation-1.0.0a1/openhands/automation/event_router.py +192 -0
  207. openhands_automation-1.0.0a1/openhands/automation/event_schemas/__init__.py +126 -0
  208. openhands_automation-1.0.0a1/openhands/automation/event_schemas/custom.py +108 -0
  209. openhands_automation-1.0.0a1/openhands/automation/event_schemas/detection.py +136 -0
  210. openhands_automation-1.0.0a1/openhands/automation/event_schemas/github.py +483 -0
  211. openhands_automation-1.0.0a1/openhands/automation/exceptions.py +29 -0
  212. openhands_automation-1.0.0a1/openhands/automation/execution.py +568 -0
  213. openhands_automation-1.0.0a1/openhands/automation/filter_eval.py +192 -0
  214. openhands_automation-1.0.0a1/openhands/automation/logger.py +115 -0
  215. openhands_automation-1.0.0a1/openhands/automation/models.py +312 -0
  216. openhands_automation-1.0.0a1/openhands/automation/preset_router.py +531 -0
  217. openhands_automation-1.0.0a1/openhands/automation/presets/__init__.py +9 -0
  218. openhands_automation-1.0.0a1/openhands/automation/presets/plugin/sdk_main.py +328 -0
  219. openhands_automation-1.0.0a1/openhands/automation/presets/plugin/setup.sh +23 -0
  220. openhands_automation-1.0.0a1/openhands/automation/presets/prompt/sdk_main.py +315 -0
  221. openhands_automation-1.0.0a1/openhands/automation/presets/prompt/setup.sh +23 -0
  222. openhands_automation-1.0.0a1/openhands/automation/router.py +347 -0
  223. openhands_automation-1.0.0a1/openhands/automation/scheduler.py +202 -0
  224. openhands_automation-1.0.0a1/openhands/automation/schemas.py +613 -0
  225. openhands_automation-1.0.0a1/openhands/automation/storage/__init__.py +18 -0
  226. openhands_automation-1.0.0a1/openhands/automation/storage/factory.py +36 -0
  227. openhands_automation-1.0.0a1/openhands/automation/storage/file_store.py +64 -0
  228. openhands_automation-1.0.0a1/openhands/automation/storage/google_cloud.py +195 -0
  229. openhands_automation-1.0.0a1/openhands/automation/storage/local.py +125 -0
  230. openhands_automation-1.0.0a1/openhands/automation/storage/s3.py +340 -0
  231. openhands_automation-1.0.0a1/openhands/automation/trigger_matcher.py +89 -0
  232. openhands_automation-1.0.0a1/openhands/automation/uploads.py +323 -0
  233. openhands_automation-1.0.0a1/openhands/automation/utils/__init__.py +24 -0
  234. openhands_automation-1.0.0a1/openhands/automation/utils/agent_server.py +161 -0
  235. openhands_automation-1.0.0a1/openhands/automation/utils/api_key.py +98 -0
  236. openhands_automation-1.0.0a1/openhands/automation/utils/cron.py +143 -0
  237. openhands_automation-1.0.0a1/openhands/automation/utils/log_context.py +28 -0
  238. openhands_automation-1.0.0a1/openhands/automation/utils/run.py +238 -0
  239. openhands_automation-1.0.0a1/openhands/automation/utils/sandbox.py +212 -0
  240. openhands_automation-1.0.0a1/openhands/automation/utils/tarball_validation.py +170 -0
  241. openhands_automation-1.0.0a1/openhands/automation/utils/time.py +15 -0
  242. openhands_automation-1.0.0a1/openhands/automation/utils/webhook.py +235 -0
  243. openhands_automation-1.0.0a1/openhands/automation/watchdog.py +284 -0
  244. openhands_automation-1.0.0a1/openhands/automation/webhook_router.py +275 -0
  245. openhands_automation-1.0.0a1/pyproject.toml +130 -0
  246. openhands_automation-1.0.0a1/scripts/test_automation.py +160 -0
  247. openhands_automation-1.0.0a1/scripts/test_tarball/main.py +130 -0
  248. openhands_automation-1.0.0a1/scripts/test_tarball/setup.sh +16 -0
  249. openhands_automation-1.0.0a1/tests/__init__.py +0 -0
  250. openhands_automation-1.0.0a1/tests/conftest.py +205 -0
  251. openhands_automation-1.0.0a1/tests/test_auth.py +884 -0
  252. openhands_automation-1.0.0a1/tests/test_backends.py +428 -0
  253. openhands_automation-1.0.0a1/tests/test_config.py +392 -0
  254. openhands_automation-1.0.0a1/tests/test_db.py +264 -0
  255. openhands_automation-1.0.0a1/tests/test_disable_automation.py +520 -0
  256. openhands_automation-1.0.0a1/tests/test_dispatcher.py +551 -0
  257. openhands_automation-1.0.0a1/tests/test_event_router.py +400 -0
  258. openhands_automation-1.0.0a1/tests/test_event_schemas.py +896 -0
  259. openhands_automation-1.0.0a1/tests/test_execution.py +300 -0
  260. openhands_automation-1.0.0a1/tests/test_filter_eval.py +282 -0
  261. openhands_automation-1.0.0a1/tests/test_frontend.py +188 -0
  262. openhands_automation-1.0.0a1/tests/test_health.py +42 -0
  263. openhands_automation-1.0.0a1/tests/test_local_mode.py +332 -0
  264. openhands_automation-1.0.0a1/tests/test_openapi.py +30 -0
  265. openhands_automation-1.0.0a1/tests/test_preset_router.py +1133 -0
  266. openhands_automation-1.0.0a1/tests/test_router.py +1438 -0
  267. openhands_automation-1.0.0a1/tests/test_scheduler.py +945 -0
  268. openhands_automation-1.0.0a1/tests/test_storage.py +240 -0
  269. openhands_automation-1.0.0a1/tests/test_storage_integration.py +252 -0
  270. openhands_automation-1.0.0a1/tests/test_storage_local.py +362 -0
  271. openhands_automation-1.0.0a1/tests/test_storage_s3.py +399 -0
  272. openhands_automation-1.0.0a1/tests/test_storage_s3_integration.py +324 -0
  273. openhands_automation-1.0.0a1/tests/test_tarball_validation.py +333 -0
  274. openhands_automation-1.0.0a1/tests/test_uploads.py +192 -0
  275. openhands_automation-1.0.0a1/tests/test_uploads_integration.py +227 -0
  276. openhands_automation-1.0.0a1/tests/test_watchdog.py +318 -0
  277. openhands_automation-1.0.0a1/tests/test_webhook_router.py +292 -0
  278. openhands_automation-1.0.0a1/tests/test_webhook_utils.py +322 -0
  279. openhands_automation-1.0.0a1/uv.lock +4109 -0
@@ -0,0 +1,32 @@
1
+ # Version control
2
+ .git
3
+ .gitignore
4
+
5
+ # CI/CD
6
+ .github
7
+
8
+ # Python
9
+ .venv
10
+ __pycache__
11
+ *.pyc
12
+ .pytest_cache
13
+ .ruff_cache
14
+ .mypy_cache
15
+
16
+ # Frontend (built inside Docker via multi-stage)
17
+ frontend/node_modules
18
+ frontend/build
19
+ frontend/coverage
20
+ frontend/test-results
21
+ frontend/playwright-report
22
+ frontend/.react-router
23
+
24
+ # IDE / editor
25
+ .vscode
26
+ .idea
27
+ *.swp
28
+
29
+ # Misc
30
+ .pr
31
+ .stakpak
32
+ .pre-commit-config.yaml
@@ -0,0 +1,56 @@
1
+ ---
2
+ # .github/workflows/precommit.yml
3
+ name: ci
4
+
5
+ on:
6
+ push:
7
+ branches: [main]
8
+ pull_request:
9
+ branches: ['**']
10
+
11
+ jobs:
12
+ backend:
13
+ runs-on: ubuntu-24.04
14
+
15
+ steps:
16
+ - name: Checkout code
17
+ uses: actions/checkout@v5
18
+
19
+ - name: Set up Python
20
+ uses: actions/setup-python@v6
21
+ with:
22
+ python-version: '3.12'
23
+
24
+ - name: Install uv
25
+ uses: astral-sh/setup-uv@v7
26
+
27
+ - name: Install dependencies
28
+ run: uv sync --frozen --group dev
29
+
30
+ - name: Run pre-commit (all files)
31
+ run: uv run pre-commit run --all-files --show-diff-on-failure
32
+
33
+ frontend:
34
+ runs-on: ubuntu-24.04
35
+ defaults:
36
+ run:
37
+ working-directory: frontend
38
+ steps:
39
+ - uses: actions/checkout@v5
40
+
41
+ - name: Setup Node
42
+ uses: actions/setup-node@v3
43
+ with:
44
+ node-version: 24
45
+
46
+ - name: NPM dependencies
47
+ run: npm ci
48
+
49
+ - name: Lint
50
+ run: npm run lint
51
+
52
+ - name: Test
53
+ run: npm run test
54
+
55
+ - name: Build
56
+ run: npm run build
@@ -0,0 +1,115 @@
1
+ ---
2
+ # Workflow that builds and pushes the automation docker image to ghcr.io
3
+ name: Docker
4
+
5
+ on:
6
+ push:
7
+ branches:
8
+ - main
9
+ tags:
10
+ - '*'
11
+ pull_request:
12
+ workflow_dispatch:
13
+ inputs:
14
+ reason:
15
+ description: Reason for manual trigger
16
+ required: true
17
+ default: ''
18
+
19
+ # If triggered by a PR, it will be in the same group. However, each commit on main will be in its own unique group
20
+ concurrency:
21
+ group: ${{ github.workflow }}-${{ (github.head_ref && github.ref) || github.run_id }}
22
+ cancel-in-progress: true
23
+
24
+ env:
25
+ REGISTRY: ghcr.io
26
+ IMAGE_NAME: openhands/automation
27
+ RELEVANT_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
28
+
29
+ jobs:
30
+ ghcr_build:
31
+ name: Build and Push Automation Image
32
+ runs-on: ubuntu-24.04
33
+ permissions:
34
+ contents: read
35
+ packages: write
36
+ steps:
37
+ - name: Checkout
38
+ uses: actions/checkout@v4
39
+ with:
40
+ ref: ${{ github.event.pull_request.head.sha }}
41
+
42
+ - name: Set up Docker Buildx
43
+ uses: docker/setup-buildx-action@v3
44
+ with:
45
+ driver-opts: network=host
46
+
47
+ - name: Login to GHCR
48
+ uses: docker/login-action@v3
49
+ with:
50
+ registry: ${{ env.REGISTRY }}
51
+ username: ${{ github.repository_owner }}
52
+ password: ${{ secrets.GITHUB_TOKEN }}
53
+
54
+ - name: Get short SHA
55
+ id: short_sha
56
+ run: echo "SHORT_SHA=$(echo ${{ env.RELEVANT_SHA }} | cut -c1-7)" >> $GITHUB_OUTPUT
57
+
58
+ - name: Extract metadata (tags, labels) for Docker
59
+ id: meta
60
+ uses: docker/metadata-action@v5
61
+ with:
62
+ images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
63
+ tags: |
64
+ type=ref,event=branch
65
+ type=ref,event=pr
66
+ type=sha
67
+ type=sha,format=long
68
+ type=semver,pattern={{version}}
69
+ type=semver,pattern={{major}}.{{minor}}
70
+ type=semver,pattern={{major}}
71
+ flavor: |
72
+ latest=auto
73
+ prefix=
74
+ suffix=
75
+ env:
76
+ DOCKER_METADATA_PR_HEAD_SHA: true
77
+
78
+ - name: Build and push Docker image
79
+ if: '!github.event.pull_request.head.repo.fork'
80
+ uses: docker/build-push-action@v5
81
+ with:
82
+ context: .
83
+ file: containers/Dockerfile
84
+ push: true
85
+ tags: ${{ steps.meta.outputs.tags }}
86
+ labels: ${{ steps.meta.outputs.labels }}
87
+ platforms: linux/amd64
88
+ cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache
89
+ cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache,mode=max
90
+ provenance: true
91
+ sbom: true
92
+
93
+ # Forked repos can't push to GHCR, so we just build to verify the image builds
94
+ - name: Build Docker image (fork)
95
+ if: github.event.pull_request.head.repo.fork
96
+ uses: docker/build-push-action@v5
97
+ with:
98
+ context: .
99
+ file: containers/Dockerfile
100
+ push: false
101
+ tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.RELEVANT_SHA }}
102
+ platforms: linux/amd64
103
+
104
+ trigger-deploy-preview:
105
+ name: Trigger Deploy Preview
106
+ needs: ghcr_build
107
+ # Only trigger for PRs from non-fork repos (fork check mirrors the push condition in ghcr_build)
108
+ if: github.event_name == 'pull_request' && !github.event.pull_request.head.repo.fork
109
+ uses: ./.github/workflows/trigger-deploy-preview.yml
110
+ with:
111
+ pr_number: ${{ github.event.pull_request.number }}
112
+ head_sha: ${{ github.event.pull_request.head.sha }}
113
+ pr_title: ${{ github.event.pull_request.title }}
114
+ secrets:
115
+ ALLHANDS_BOT_GITHUB_PAT: ${{ secrets.ALLHANDS_BOT_GITHUB_PAT }}
@@ -0,0 +1,48 @@
1
+ ---
2
+ name: PR Review by OpenHands
3
+
4
+ on:
5
+ # TEMPORARY MITIGATION (Clinejection hardening)
6
+ #
7
+ # We temporarily avoid `pull_request_target` here. We'll restore it after the PR review
8
+ # workflow is fully hardened for untrusted execution.
9
+ pull_request:
10
+ types: [opened, ready_for_review, labeled, review_requested]
11
+
12
+ permissions:
13
+ contents: read
14
+ pull-requests: write
15
+ issues: write
16
+
17
+ jobs:
18
+ pr-review:
19
+ # Note: fork PRs will not have access to repository secrets under `pull_request`.
20
+ # Skip forks to avoid noisy failures until we restore a hardened `pull_request_target` flow.
21
+ if: |
22
+ github.event.pull_request.head.repo.full_name == github.repository &&
23
+ (
24
+ (github.event.action == 'opened' && github.event.pull_request.draft == false) ||
25
+ github.event.action == 'ready_for_review' ||
26
+ (github.event.action == 'labeled' && github.event.label.name == 'review-this') ||
27
+ (
28
+ github.event.action == 'review_requested' &&
29
+ (
30
+ github.event.requested_reviewer.login == 'openhands-agent' ||
31
+ github.event.requested_reviewer.login == 'all-hands-bot'
32
+ )
33
+ )
34
+ )
35
+ concurrency:
36
+ group: pr-review-${{ github.event.pull_request.number }}
37
+ cancel-in-progress: true
38
+ runs-on: ubuntu-24.04
39
+ steps:
40
+ - name: Run PR Review
41
+ uses: OpenHands/extensions/plugins/pr-review@main
42
+ with:
43
+ llm-model: litellm_proxy/claude-sonnet-4-5-20250929
44
+ llm-base-url: https://llm-proxy.app.all-hands.dev
45
+ review-style: roasted
46
+ llm-api-key: ${{ secrets.LLM_API_KEY }}
47
+ github-token: ${{ secrets.ALLHANDS_BOT_GITHUB_PAT }}
48
+ lmnr-api-key: ${{ secrets.LMNR_SKILLS_API_KEY }}
@@ -0,0 +1,40 @@
1
+ ---
2
+ # Publishes the openhands-automation PyPI package using trusted publishing (OIDC).
3
+ # Triggered when a tag is pushed or manually via workflow_dispatch.
4
+ # Requires trusted publishing to be configured in PyPI project settings.
5
+ name: Publish PyPI Package
6
+
7
+ on:
8
+ push:
9
+ tags:
10
+ - '*'
11
+ workflow_dispatch:
12
+ inputs:
13
+ reason:
14
+ description: Reason for manual trigger
15
+ required: true
16
+ default: ''
17
+
18
+ jobs:
19
+ release:
20
+ runs-on: ubuntu-24.04
21
+ permissions:
22
+ id-token: write
23
+ contents: read
24
+ steps:
25
+ - name: Checkout
26
+ uses: actions/checkout@v4
27
+
28
+ - name: Install uv
29
+ uses: astral-sh/setup-uv@v7
30
+ with:
31
+ version: latest
32
+
33
+ - name: Set up Python
34
+ run: uv python install 3.12
35
+
36
+ - name: Build package
37
+ run: uv build
38
+
39
+ - name: Publish to PyPI
40
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,58 @@
1
+ ---
2
+ # Adds a git-tag name to existing Docker images.
3
+ # Triggered when a tag is pushed: finds the images built at the tag's commit
4
+ # (tagged `sha-<full>`) and adds the tag name as an alias for the same manifest.
5
+ # Semver tags (X.Y.Z) also get X.Y, X, and latest aliases.
6
+ # No rebuild — pure registry-side retag via `docker buildx imagetools create`.
7
+ name: Tag Docker images
8
+
9
+ on:
10
+ push:
11
+ tags:
12
+ - '*'
13
+
14
+ jobs:
15
+ retag:
16
+ runs-on: ubuntu-24.04
17
+ permissions:
18
+ packages: write
19
+ steps:
20
+ - name: Login to GHCR
21
+ uses: docker/login-action@v3
22
+ with:
23
+ registry: ghcr.io
24
+ username: ${{ github.repository_owner }}
25
+ password: ${{ secrets.GITHUB_TOKEN }}
26
+
27
+ - name: Set up Docker Buildx
28
+ uses: docker/setup-buildx-action@v3
29
+
30
+ - name: Compute tags
31
+ id: meta
32
+ uses: docker/metadata-action@v5
33
+ with:
34
+ images: ghcr.io/openhands/automation
35
+ flavor: latest=auto
36
+ tags: |
37
+ type=ref,event=tag
38
+ type=semver,pattern={{version}}
39
+ type=semver,pattern={{major}}.{{minor}}
40
+ type=semver,pattern={{major}}
41
+
42
+ - name: Add tags to existing image
43
+ env:
44
+ SRC: ghcr.io/openhands/automation:sha-${{ github.sha }}
45
+ TAGS: ${{ steps.meta.outputs.tags }}
46
+ shell: bash
47
+ run: |
48
+ set -euo pipefail
49
+ if ! docker buildx imagetools inspect "$SRC" > /dev/null 2>&1; then
50
+ echo "::error::Source image $SRC does not exist. The Docker workflow for commit ${{ github.sha }} may not have completed successfully. Re-run this workflow once the build finishes."
51
+ exit 1
52
+ fi
53
+ args=()
54
+ while IFS= read -r tag; do
55
+ [[ -z "$tag" ]] && continue
56
+ args+=(-t "$tag")
57
+ done <<< "$TAGS"
58
+ docker buildx imagetools create "${args[@]}" "$SRC"
@@ -0,0 +1,49 @@
1
+ ---
2
+ name: Run tests
3
+
4
+ on:
5
+ push:
6
+ branches: [main]
7
+ pull_request:
8
+ branches: ['**']
9
+
10
+ permissions:
11
+ contents: write
12
+ pull-requests: write
13
+
14
+ jobs:
15
+ unit-tests:
16
+ runs-on: ubuntu-24.04
17
+ steps:
18
+ - name: Checkout
19
+ uses: actions/checkout@v5
20
+
21
+ - name: Install uv
22
+ uses: astral-sh/setup-uv@v7
23
+ with:
24
+ enable-cache: true
25
+ python-version: '3.12'
26
+
27
+ - name: Install deps
28
+ run: uv sync --frozen --group dev
29
+
30
+ - name: Run unit tests with coverage
31
+ run: |
32
+ rm -f .coverage
33
+ CI=true uv run python -m pytest -vvs \
34
+ --cov=openhands/automation \
35
+ --cov-report=term-missing \
36
+ --cov-report=xml:coverage.xml \
37
+ --cov-fail-under=0 \
38
+ tests/
39
+
40
+ - name: Pytest coverage PR comment
41
+ if: always() && github.event_name == 'pull_request'
42
+ continue-on-error: true
43
+ uses: MishaKav/pytest-coverage-comment@v1
44
+ with:
45
+ github-token: ${{ secrets.GITHUB_TOKEN }}
46
+ pytest-xml-coverage-path: coverage.xml
47
+ title: Coverage Report
48
+ create-new-comment: false
49
+ hide-report: false
@@ -0,0 +1,235 @@
1
+ ---
2
+ # Workflow that creates/updates a deploy repo PR for automation PRs
3
+ # Called by ghcr-build.yml after Docker build completes successfully
4
+ name: Trigger Deploy Preview
5
+
6
+ on:
7
+ workflow_call:
8
+ inputs:
9
+ pr_number:
10
+ description: The automation PR number
11
+ required: true
12
+ type: number
13
+ head_sha:
14
+ description: The commit SHA to deploy
15
+ required: true
16
+ type: string
17
+ pr_title:
18
+ description: The PR title
19
+ required: true
20
+ type: string
21
+ secrets:
22
+ ALLHANDS_BOT_GITHUB_PAT:
23
+ required: true
24
+
25
+ concurrency:
26
+ group: deploy-preview-${{ inputs.pr_number }}
27
+ cancel-in-progress: false
28
+
29
+ env:
30
+ DEPLOY_REPO: OpenHands/deploy
31
+ BASE_BRANCH: main
32
+
33
+ jobs:
34
+ trigger-deploy:
35
+ name: Create Deploy Preview PR
36
+ runs-on: ubuntu-24.04
37
+ permissions:
38
+ contents: read
39
+ pull-requests: write
40
+ steps:
41
+ - name: Checkout deploy repo
42
+ uses: actions/checkout@v4
43
+ with:
44
+ repository: ${{ env.DEPLOY_REPO }}
45
+ ref: ${{ env.BASE_BRANCH }}
46
+ token: ${{ secrets.ALLHANDS_BOT_GITHUB_PAT }}
47
+ fetch-depth: 0
48
+ path: deploy-repo
49
+
50
+ - name: Install yq
51
+ run: |
52
+ YQ_VERSION="v4.52.4"
53
+ YQ_CHECKSUM="0c4d965ea944b64b8fddaf7f27779ee3034e5693263786506ccd1c120f184e8c"
54
+ wget -qO /tmp/yq "https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/yq_linux_amd64"
55
+ echo "${YQ_CHECKSUM} /tmp/yq" | sha256sum -c -
56
+ sudo mv /tmp/yq /usr/local/bin/yq
57
+ sudo chmod +x /usr/local/bin/yq
58
+
59
+ - name: Update AUTOMATION_SHA and push
60
+ id: deploy-pr
61
+ run: |
62
+ cd deploy-repo
63
+ BRANCH_NAME="au-pr-${{ inputs.pr_number }}"
64
+ BASE_BRANCH="${{ env.BASE_BRANCH }}"
65
+
66
+ # Configure git
67
+ git config user.name "all-hands-bot"
68
+ git config user.email "all-hands-bot@all-hands.dev"
69
+
70
+ # Fetch all branches
71
+ git fetch origin "$BASE_BRANCH"
72
+ git fetch origin "$BRANCH_NAME" 2>/dev/null || true
73
+
74
+ # Check if branch already exists remotely
75
+ if git rev-parse --verify "origin/$BRANCH_NAME" >/dev/null 2>&1; then
76
+ echo "Branch $BRANCH_NAME exists remotely, checking out and rebasing"
77
+ git checkout -B "$BRANCH_NAME" "origin/$BRANCH_NAME"
78
+ # Rebase onto latest base branch to pick up any upstream changes
79
+ git rebase "origin/$BASE_BRANCH" || {
80
+ echo "Rebase failed, aborting and resetting to base"
81
+ git rebase --abort
82
+ git reset --hard "origin/$BASE_BRANCH"
83
+ }
84
+ else
85
+ echo "Branch $BRANCH_NAME does not exist, creating from $BASE_BRANCH"
86
+ git checkout -B "$BRANCH_NAME" "origin/$BASE_BRANCH"
87
+ fi
88
+
89
+ # Verify deploy.yaml exists
90
+ if [ ! -f .github/workflows/deploy.yaml ]; then
91
+ echo "Error: .github/workflows/deploy.yaml not found" >&2
92
+ exit 1
93
+ fi
94
+
95
+ # Update AUTOMATION_SHA in deploy.yaml
96
+ HEAD_SHA="${{ inputs.head_sha }}"
97
+ echo "Updating AUTOMATION_SHA to $HEAD_SHA"
98
+ if ! yq e -i ".env.AUTOMATION_SHA = \"${HEAD_SHA}\"" .github/workflows/deploy.yaml; then
99
+ echo "Error: Failed to update deploy.yaml with yq" >&2
100
+ exit 1
101
+ fi
102
+
103
+ # Check if there are changes to commit
104
+ if git diff --quiet; then
105
+ echo "No changes to commit - SHA already up to date"
106
+ echo "has_changes=false" >> $GITHUB_OUTPUT
107
+ else
108
+ echo "Changes detected, committing"
109
+ git add .github/workflows/deploy.yaml
110
+ git commit -m "Update AUTOMATION_SHA to $HEAD_SHA
111
+
112
+ Automation PR: https://github.com/${{ github.repository }}/pull/${{ inputs.pr_number }}
113
+ Commit: $HEAD_SHA"
114
+ echo "has_changes=true" >> $GITHUB_OUTPUT
115
+ fi
116
+
117
+ # Push the branch (force-with-lease is safer than force for rebased branches)
118
+ git push --force-with-lease origin "$BRANCH_NAME"
119
+
120
+ echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT
121
+
122
+ - name: Create or get deploy PR
123
+ id: manage-pr
124
+ env:
125
+ GH_TOKEN: ${{ secrets.ALLHANDS_BOT_GITHUB_PAT }}
126
+ BRANCH_NAME: ${{ steps.deploy-pr.outputs.branch_name }}
127
+ PR_TITLE: ${{ inputs.pr_title }}
128
+ run: |
129
+ cd deploy-repo
130
+
131
+ # Check if PR already exists for this branch
132
+ if ! EXISTING_PR=$(gh pr list --head "$BRANCH_NAME" --base "${{ env.BASE_BRANCH }}" --json number,url --jq '.[0]'); then
133
+ echo "Error: Failed to query existing PRs" >&2
134
+ exit 1
135
+ fi
136
+
137
+ if [ -n "$EXISTING_PR" ] && [ "$EXISTING_PR" != "null" ]; then
138
+ PR_URL=$(echo "$EXISTING_PR" | jq -r '.url')
139
+ echo "PR already exists: $PR_URL"
140
+ echo "deploy_pr_url=$PR_URL" >> $GITHUB_OUTPUT
141
+ else
142
+ # Create PR body using heredoc to avoid shell injection from PR title
143
+ PR_BODY=$(cat <<EOF
144
+ **Preview branch for Automation PR #${{ inputs.pr_number }}**
145
+
146
+ This PR was automatically created to test automation changes.
147
+
148
+ **Changes:**
149
+ - \`AUTOMATION_SHA\` → \`${{ inputs.head_sha }}\`
150
+
151
+ **Source PR:** https://github.com/${{ github.repository }}/pull/${{ inputs.pr_number }}
152
+ **Source PR Title:** ${PR_TITLE}
153
+ EOF
154
+ )
155
+
156
+ # Create new PR against automations-project branch
157
+ if ! PR_URL=$(gh pr create \
158
+ --title "Preview: Automation PR #${{ inputs.pr_number }}" \
159
+ --body "$PR_BODY" \
160
+ --base "${{ env.BASE_BRANCH }}" \
161
+ --head "$BRANCH_NAME"); then
162
+ echo "Error: Failed to create PR" >&2
163
+ exit 1
164
+ fi
165
+
166
+ echo "Created new PR: $PR_URL"
167
+ echo "deploy_pr_url=$PR_URL" >> $GITHUB_OUTPUT
168
+ fi
169
+
170
+ - name: Comment on automation PR
171
+ uses: actions/github-script@v7
172
+ env:
173
+ DEPLOY_PR_URL: ${{ steps.manage-pr.outputs.deploy_pr_url }}
174
+ HAS_CHANGES: ${{ steps.deploy-pr.outputs.has_changes }}
175
+ with:
176
+ script: |
177
+ const prNumber = ${{ inputs.pr_number }};
178
+ const deployPrUrl = process.env.DEPLOY_PR_URL;
179
+ const headSha = '${{ inputs.head_sha }}';
180
+ const hasChanges = process.env.HAS_CHANGES === 'true';
181
+
182
+ const etTime = new Intl.DateTimeFormat('en-US', {
183
+ timeZone: 'America/New_York',
184
+ year: 'numeric',
185
+ month: 'short',
186
+ day: '2-digit',
187
+ hour: '2-digit',
188
+ minute: '2-digit',
189
+ second: '2-digit',
190
+ hour12: true
191
+ }).format(new Date());
192
+
193
+ const statusMessage = hasChanges
194
+ ? 'A deploy preview has been created/updated for this PR.'
195
+ : 'No changes needed - deploy preview is already up to date.';
196
+
197
+ const commentBody = `🚀 **Deploy Preview PR Created/Updated**
198
+
199
+ ${statusMessage}
200
+
201
+ **Deploy PR:** ${deployPrUrl}
202
+ **Automation SHA:** \`${headSha}\`
203
+ **Last updated:** ${etTime} ET
204
+
205
+ Once the deploy PR's CI passes, the automation service will be deployed to the feature environment.`;
206
+
207
+ // Find existing bot comment
208
+ const { data: comments } = await github.rest.issues.listComments({
209
+ issue_number: prNumber,
210
+ owner: context.repo.owner,
211
+ repo: context.repo.repo
212
+ });
213
+
214
+ const botComment = comments.find(comment =>
215
+ comment.user.login === 'github-actions[bot]' &&
216
+ comment.body.includes('Deploy Preview PR Created/Updated')
217
+ );
218
+
219
+ if (botComment) {
220
+ await github.rest.issues.updateComment({
221
+ comment_id: botComment.id,
222
+ owner: context.repo.owner,
223
+ repo: context.repo.repo,
224
+ body: commentBody
225
+ });
226
+ console.log('Updated existing comment');
227
+ } else {
228
+ await github.rest.issues.createComment({
229
+ issue_number: prNumber,
230
+ owner: context.repo.owner,
231
+ repo: context.repo.repo,
232
+ body: commentBody
233
+ });
234
+ console.log('Created new comment');
235
+ }