clawbench-cli 0.1.2__py3-none-any.whl

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 (226) hide show
  1. clawbench/__init__.py +35 -0
  2. clawbench/__main__.py +8 -0
  3. clawbench/batch.py +619 -0
  4. clawbench/cli.py +397 -0
  5. clawbench/data/chrome-extension/README.md +127 -0
  6. clawbench/data/chrome-extension/background.js +50 -0
  7. clawbench/data/chrome-extension/content.js +70 -0
  8. clawbench/data/chrome-extension/manifest.json +25 -0
  9. clawbench/data/chrome-extension/setup.sh +27 -0
  10. clawbench/data/chrome-extension/stealth.js +200 -0
  11. clawbench/data/docker/Dockerfile +51 -0
  12. clawbench/data/docker/entrypoint.sh +394 -0
  13. clawbench/data/docker/setup-openclaw.sh +112 -0
  14. clawbench/data/eval/README.md +95 -0
  15. clawbench/data/eval/agentic_eval.md +53 -0
  16. clawbench/data/extension-server/.python-version +1 -0
  17. clawbench/data/extension-server/README.md +54 -0
  18. clawbench/data/extension-server/pyproject.toml +7 -0
  19. clawbench/data/extension-server/server.py +360 -0
  20. clawbench/data/extension-server/uv.lock +644 -0
  21. clawbench/data/models/model.schema.json +44 -0
  22. clawbench/data/models/models.example.yaml +16 -0
  23. clawbench/data/shared/alex_green_personal_info.json +451 -0
  24. clawbench/data/test-cases/001-daily-life-food-uber-eats/task.json +25 -0
  25. clawbench/data/test-cases/002-daily-life-food-doordash/task.json +25 -0
  26. clawbench/data/test-cases/004-daily-life-food-instacart/extra_info/grocery_list.json +36 -0
  27. clawbench/data/test-cases/004-daily-life-food-instacart/task.json +30 -0
  28. clawbench/data/test-cases/006-daily-life-food-uber-eats/task.json +24 -0
  29. clawbench/data/test-cases/007-daily-life-food-instacart/extra_info/meal_plan.json +21 -0
  30. clawbench/data/test-cases/007-daily-life-food-instacart/task.json +30 -0
  31. clawbench/data/test-cases/011-daily-life-housing-zillow/task.json +25 -0
  32. clawbench/data/test-cases/015-daily-life-housing-craigslist/extra_info/listing_details.json +26 -0
  33. clawbench/data/test-cases/015-daily-life-housing-craigslist/task.json +30 -0
  34. clawbench/data/test-cases/035-daily-life-health-medical-betterhelp/task.json +25 -0
  35. clawbench/data/test-cases/041-daily-life-pets-rover/task.json +25 -0
  36. clawbench/data/test-cases/043-daily-life-pets-rover/extra_info/pet_info.json +12 -0
  37. clawbench/data/test-cases/043-daily-life-pets-rover/task.json +30 -0
  38. clawbench/data/test-cases/045-daily-life-personal-care-booksy/task.json +25 -0
  39. clawbench/data/test-cases/047-daily-life-personal-care-taskrabbit/extra_info/address_info.json +7 -0
  40. clawbench/data/test-cases/047-daily-life-personal-care-taskrabbit/task.json +30 -0
  41. clawbench/data/test-cases/086-job-search-hr-cv-autofill-greenhouse-meta/extra_info/job_links.json +5 -0
  42. clawbench/data/test-cases/086-job-search-hr-cv-autofill-greenhouse-meta/task.json +30 -0
  43. clawbench/data/test-cases/089-job-search-hr-cv-autofill-simplify-jobs/extra_info/job_links.json +5 -0
  44. clawbench/data/test-cases/089-job-search-hr-cv-autofill-simplify-jobs/task.json +30 -0
  45. clawbench/data/test-cases/091-job-search-hr-job-apply-indeed/task.json +25 -0
  46. clawbench/data/test-cases/120-office-secretary-tasks-email-mgmt-purelymail/task.json +28 -0
  47. clawbench/data/test-cases/121-office-secretary-tasks-email-mgmt-purelymail/task.json +28 -0
  48. clawbench/data/test-cases/128-office-secretary-tasks-email-mgmt-purelymail/task.json +28 -0
  49. clawbench/data/test-cases/134-office-secretary-tasks-calendar-calendly/task.json +25 -0
  50. clawbench/data/test-cases/137-office-secretary-tasks-calendar-doodle/extra_info/meeting_details.json +30 -0
  51. clawbench/data/test-cases/137-office-secretary-tasks-calendar-doodle/task.json +30 -0
  52. clawbench/data/test-cases/139-office-secretary-tasks-calendar-calendly/task.json +25 -0
  53. clawbench/data/test-cases/142-office-secretary-tasks-collab-trello/extra_info/task_list.json +29 -0
  54. clawbench/data/test-cases/142-office-secretary-tasks-collab-trello/task.json +30 -0
  55. clawbench/data/test-cases/179-dev-tech-github-ops-github/extra_info/config.json +13 -0
  56. clawbench/data/test-cases/179-dev-tech-github-ops-github/task.json +30 -0
  57. clawbench/data/test-cases/180-dev-tech-github-ops-github/task.json +25 -0
  58. clawbench/data/test-cases/215-academia-research-paper-tables-overleaf/extra_info/raw_results.json +47 -0
  59. clawbench/data/test-cases/215-academia-research-paper-tables-overleaf/task.json +30 -0
  60. clawbench/data/test-cases/242-academia-research-research-tools-overleaf/task.json +25 -0
  61. clawbench/data/test-cases/246-academia-research-research-tools-zotero/task.json +25 -0
  62. clawbench/data/test-cases/247-academia-research-research-tools-semantic-scholar/task.json +25 -0
  63. clawbench/data/test-cases/265-education-learning-general-coursera/task.json +25 -0
  64. clawbench/data/test-cases/266-education-learning-general-leetcode/extra_info/solution_code.py +9 -0
  65. clawbench/data/test-cases/266-education-learning-general-leetcode/task.json +30 -0
  66. clawbench/data/test-cases/273-education-learning-general-edx/task.json +25 -0
  67. clawbench/data/test-cases/274-education-learning-general-udemy/task.json +25 -0
  68. clawbench/data/test-cases/279-travel-general-airbnb/task.json +25 -0
  69. clawbench/data/test-cases/280-travel-general-booking-com/task.json +25 -0
  70. clawbench/data/test-cases/363-entertainment-hobbies-general-ticketmaster/task.json +25 -0
  71. clawbench/data/test-cases/369-entertainment-hobbies-general-goodreads/extra_info/book_list.json +14 -0
  72. clawbench/data/test-cases/369-entertainment-hobbies-general-goodreads/task.json +30 -0
  73. clawbench/data/test-cases/372-entertainment-hobbies-general-eventbrite/extra_info/event_details.json +10 -0
  74. clawbench/data/test-cases/372-entertainment-hobbies-general-eventbrite/task.json +30 -0
  75. clawbench/data/test-cases/403-personal-management-account-security-1password-web/extra_info/credentials.json +34 -0
  76. clawbench/data/test-cases/403-personal-management-account-security-1password-web/task.json +30 -0
  77. clawbench/data/test-cases/413-personal-management-personal-tools-todoist/extra_info/task_list.json +52 -0
  78. clawbench/data/test-cases/413-personal-management-personal-tools-todoist/task.json +30 -0
  79. clawbench/data/test-cases/468-rating-voting-general-glassdoor/extra_info/interview_experience.json +10 -0
  80. clawbench/data/test-cases/468-rating-voting-general-glassdoor/task.json +30 -0
  81. clawbench/data/test-cases/469-rating-voting-general-tripadvisor/extra_info/review_content.json +6 -0
  82. clawbench/data/test-cases/469-rating-voting-general-tripadvisor/task.json +30 -0
  83. clawbench/data/test-cases/470-rating-voting-general-trustpilot/extra_info/review_content.json +6 -0
  84. clawbench/data/test-cases/470-rating-voting-general-trustpilot/task.json +30 -0
  85. clawbench/data/test-cases/474-rating-voting-general-capterra/task.json +25 -0
  86. clawbench/data/test-cases/475-rating-voting-general-g2/task.json +25 -0
  87. clawbench/data/test-cases/482-creation-init-general-confluence/extra_info/content.json +3 -0
  88. clawbench/data/test-cases/482-creation-init-general-confluence/task.json +30 -0
  89. clawbench/data/test-cases/483-creation-init-general-airtable/task.json +25 -0
  90. clawbench/data/test-cases/484-creation-init-general-clickup/task.json +28 -0
  91. clawbench/data/test-cases/485-creation-init-general-webflow/task.json +25 -0
  92. clawbench/data/test-cases/486-creation-init-general-mailchimp/extra_info/content.json +3 -0
  93. clawbench/data/test-cases/486-creation-init-general-mailchimp/task.json +30 -0
  94. clawbench/data/test-cases/487-creation-init-general-typeform/extra_info/survey_questions.json +85 -0
  95. clawbench/data/test-cases/487-creation-init-general-typeform/task.json +30 -0
  96. clawbench/data/test-cases/488-creation-init-general-substack/extra_info/content.json +3 -0
  97. clawbench/data/test-cases/488-creation-init-general-substack/task.json +30 -0
  98. clawbench/data/test-cases/489-creation-init-general-ghost/extra_info/content.json +3 -0
  99. clawbench/data/test-cases/489-creation-init-general-ghost/task.json +30 -0
  100. clawbench/data/test-cases/501-creation-init-general-asana/extra_info/project_description.json +8 -0
  101. clawbench/data/test-cases/501-creation-init-general-asana/task.json +33 -0
  102. clawbench/data/test-cases/529-daily-life-shopping-delivery-king-arthur-baking/task.json +25 -0
  103. clawbench/data/test-cases/533-daily-life-utilities-inmyarea/task.json +25 -0
  104. clawbench/data/test-cases/535-daily-life-home-home-depot/task.json +25 -0
  105. clawbench/data/test-cases/537-daily-life-food-crumbl/task.json +25 -0
  106. clawbench/data/test-cases/539-daily-life-health-jefit/task.json +25 -0
  107. clawbench/data/test-cases/542-daily-life-pets-wag/task.json +25 -0
  108. clawbench/data/test-cases/551-finance-investment-crypto-wallet-trezor/task.json +25 -0
  109. clawbench/data/test-cases/552-finance-investment-business-payment-plooto/task.json +25 -0
  110. clawbench/data/test-cases/555-finance-investment-insurance-insureon/task.json +25 -0
  111. clawbench/data/test-cases/559-finance-investment-crowdfunding-frontfundr/task.json +25 -0
  112. clawbench/data/test-cases/564-daily-life-event-registration-race-roster/task.json +25 -0
  113. clawbench/data/test-cases/565-job-search-hr-job-search-jopwell/task.json +25 -0
  114. clawbench/data/test-cases/566-job-search-hr-job-search-ziprecruiter/extra_info/listing_details.json +26 -0
  115. clawbench/data/test-cases/566-job-search-hr-job-search-ziprecruiter/task.json +30 -0
  116. clawbench/data/test-cases/569-job-search-hr-job-search-careerbuilder/task.json +25 -0
  117. clawbench/data/test-cases/570-job-search-hr-job-search-hired/task.json +25 -0
  118. clawbench/data/test-cases/571-job-search-hr-recruitment-mgmt-workable/extra_info/listing_details.json +26 -0
  119. clawbench/data/test-cases/571-job-search-hr-recruitment-mgmt-workable/task.json +30 -0
  120. clawbench/data/test-cases/576-office-secretary-tasks-reports-ftc-reportfraud/task.json +25 -0
  121. clawbench/data/test-cases/583-office-secretary-tasks-support-tickets-freshdesk/task.json +25 -0
  122. clawbench/data/test-cases/598-academia-research-legal-docs-formswift/task.json +25 -0
  123. clawbench/data/test-cases/606-education-learning-kids-courses-outschool/task.json +25 -0
  124. clawbench/data/test-cases/607-education-learning-art-courses-creativebug/task.json +25 -0
  125. clawbench/data/test-cases/609-education-learning-meditation-spirit-rock-meditation-center/task.json +25 -0
  126. clawbench/data/test-cases/615-travel-flights-spirit-airlines/task.json +25 -0
  127. clawbench/data/test-cases/618-travel-train-bus-12go-asia/task.json +25 -0
  128. clawbench/data/test-cases/625-travel-camping-outdoor-parks-canada-reservations/task.json +25 -0
  129. clawbench/data/test-cases/626-travel-bus-flixbus/task.json +25 -0
  130. clawbench/data/test-cases/627-travel-flights-momondo/task.json +25 -0
  131. clawbench/data/test-cases/632-shopping-commerce-beauty-care-olaplex/task.json +25 -0
  132. clawbench/data/test-cases/634-shopping-commerce-apparel-dooney-bourke/task.json +25 -0
  133. clawbench/data/test-cases/635-shopping-commerce-gifts-uncommon-goods/task.json +25 -0
  134. clawbench/data/test-cases/636-shopping-commerce-auto-parts-rockauto/task.json +25 -0
  135. clawbench/data/test-cases/638-shopping-commerce-print-custom-vistaprint/task.json +25 -0
  136. clawbench/data/test-cases/639-shopping-commerce-luxury-mansur-gavriel/task.json +25 -0
  137. clawbench/data/test-cases/671-entertainment-gaming-humble-bundle/task.json +25 -0
  138. clawbench/data/test-cases/672-entertainment-hobbies-anime-streaming-crunchyroll/task.json +25 -0
  139. clawbench/data/test-cases/674-entertainment-hobbies-masterclass-masterclass/task.json +25 -0
  140. clawbench/data/test-cases/676-government-civic-legal-docs-legalnature/task.json +25 -0
  141. clawbench/data/test-cases/685-personal-management-budget-mgmt-everydollar/task.json +25 -0
  142. clawbench/data/test-cases/687-personal-management-vpn-subscription-ipvanish/task.json +25 -0
  143. clawbench/data/test-cases/688-personal-management-insurance-compare-insurify/task.json +25 -0
  144. clawbench/data/test-cases/695-automation-workflows-recurring-order-stumptown-coffee/task.json +25 -0
  145. clawbench/data/test-cases/697-automation-workflows-recurring-order-bean-box/task.json +25 -0
  146. clawbench/data/test-cases/699-automation-workflows-recurring-order-mistobox/task.json +25 -0
  147. clawbench/data/test-cases/700-deletion-revocation-data-deletion-deleteme/task.json +25 -0
  148. clawbench/data/test-cases/705-rating-voting-wine-review-vivino/task.json +25 -0
  149. clawbench/data/test-cases/706-rating-voting-beer-review-beeradvocate/task.json +25 -0
  150. clawbench/data/test-cases/707-rating-voting-social-wine-untappd/task.json +25 -0
  151. clawbench/data/test-cases/708-rating-voting-professor-review-ratemyprofessors/task.json +28 -0
  152. clawbench/data/test-cases/709-rating-voting-service-review-angi/task.json +25 -0
  153. clawbench/data/test-cases/710-creation-init-interior-design-roomsketcher/task.json +25 -0
  154. clawbench/data/test-cases/711-creation-init-color-design-coolors/task.json +25 -0
  155. clawbench/data/test-cases/712-creation-init-website-create-squarespace/task.json +25 -0
  156. clawbench/data/test-cases/713-creation-init-website-build-wix/task.json +25 -0
  157. clawbench/data/test-cases/735-home-services-maintenance-house-cleaning-bark/task.json +25 -0
  158. clawbench/data/test-cases/736-home-services-maintenance-plumbing-ace-hardware/task.json +25 -0
  159. clawbench/data/test-cases/737-home-services-maintenance-kitchen-remodel-lowes/task.json +25 -0
  160. clawbench/data/test-cases/738-home-services-maintenance-equipment-install-amazon-home-services/task.json +25 -0
  161. clawbench/data/test-cases/750-automotive-vehicle-services-car-insurance-compare-kanetix/task.json +25 -0
  162. clawbench/data/test-cases/751-automotive-vehicle-services-car-lease-sixt/task.json +25 -0
  163. clawbench/data/test-cases/754-automotive-vehicle-services-used-car-listing-autotrader/task.json +25 -0
  164. clawbench/data/test-cases/763-automotive-vehicle-services-car-lease-autoslash/task.json +25 -0
  165. clawbench/data/test-cases/766-nonprofit-charity-donation-doctors-without-borders-msf/task.json +25 -0
  166. clawbench/data/test-cases/768-nonprofit-charity-community-crowdfund-ioby/task.json +25 -0
  167. clawbench/data/test-cases/770-nonprofit-charity-volunteer-apply-on-make-a-wish-foundation-website-complete-and-submit-a-volunteer-application-form-selecting-the-wish-granter-role-and-entering-city-phoenix-az/task.json +25 -0
  168. clawbench/data/test-cases/774-nonprofit-charity-nonprofit-job-apply-charity-village/task.json +25 -0
  169. clawbench/data/test-cases/776-nonprofit-charity-volunteer-signup-idealist/task.json +25 -0
  170. clawbench/data/test-cases/778-nonprofit-charity-donation-globalgiving/extra_info/payment_info.json +3 -0
  171. clawbench/data/test-cases/778-nonprofit-charity-donation-globalgiving/task.json +30 -0
  172. clawbench/data/test-cases/780-beauty-personal-care-skincare-purchase-soko-glam/extra_info/address_info.json +4 -0
  173. clawbench/data/test-cases/780-beauty-personal-care-skincare-purchase-soko-glam/task.json +30 -0
  174. clawbench/data/test-cases/781-beauty-personal-care-beauty-booking-bluemercury/extra_info/email_info.json +3 -0
  175. clawbench/data/test-cases/781-beauty-personal-care-beauty-booking-bluemercury/task.json +30 -0
  176. clawbench/data/test-cases/782-beauty-personal-care-skincare-purchase-paulas-choice/task.json +24 -0
  177. clawbench/data/test-cases/783-beauty-personal-care-beauty-booking-ulta-beauty/task.json +24 -0
  178. clawbench/data/test-cases/785-beauty-personal-care-skincare-curology/task.json +25 -0
  179. clawbench/data/test-cases/788-beauty-personal-care-makeup-the-ordinary/task.json +25 -0
  180. clawbench/data/test-cases/789-beauty-personal-care-makeup-fenty-beauty/task.json +25 -0
  181. clawbench/data/test-cases/793-beauty-personal-care-beauty-retail-mac-cosmetics/task.json +25 -0
  182. clawbench/data/test-cases/794-beauty-personal-care-salon-booking-styleseat/task.json +25 -0
  183. clawbench/data/test-cases/795-pet-animal-care-pet-adoption-aspca/task.json +25 -0
  184. clawbench/data/test-cases/796-pet-animal-care-pet-supplies-grooming-petsmart/extra_info/pet_info.json +12 -0
  185. clawbench/data/test-cases/796-pet-animal-care-pet-supplies-grooming-petsmart/task.json +30 -0
  186. clawbench/data/test-cases/799-pet-animal-care-pet-insurance-aspca-pet-health-insurance/task.json +25 -0
  187. clawbench/data/test-cases/801-pet-animal-care-pet-friendly-travel-bringfido/task.json +25 -0
  188. clawbench/data/test-cases/803-pet-animal-care-pet-medical-pawp/extra_info/pet_info.json +12 -0
  189. clawbench/data/test-cases/803-pet-animal-care-pet-medical-pawp/task.json +30 -0
  190. clawbench/data/test-cases/807-pet-animal-care-pet-dna-embark/task.json +25 -0
  191. clawbench/data/test-cases/809-pet-animal-care-pet-adopt-petfinder/task.json +28 -0
  192. clawbench/data/test-cases/812-pet-animal-care-pet-subscription-ollie/task.json +25 -0
  193. clawbench/data/test-cases/815-personal-management-records-mgmt-myheritage/task.json +25 -0
  194. clawbench/data/test-cases/821-education-learning-reading-self-study-blinkist/task.json +25 -0
  195. clawbench/data/test-cases/861-entertainment-hobbies-movies-cineplex/task.json +25 -0
  196. clawbench/data/test-cases/862-entertainment-hobbies-movies-amc-theatres/task.json +25 -0
  197. clawbench/data/test-cases/864-entertainment-hobbies-show-tickets-ticketmaster/task.json +25 -0
  198. clawbench/data/test-cases/865-travel-outdoor-hipcamp/task.json +25 -0
  199. clawbench/data/test-cases/867-entertainment-hobbies-movies-fandango/task.json +25 -0
  200. clawbench/data/test-cases/872-daily-life-food-opentable/task.json +25 -0
  201. clawbench/data/test-cases/873-daily-life-food-resy/task.json +28 -0
  202. clawbench/data/test-cases/876-entertainment-hobbies-show-tickets-vivid-seats/task.json +25 -0
  203. clawbench/data/test-cases/877-entertainment-hobbies-show-tickets-stubhub/task.json +25 -0
  204. clawbench/data/test-cases/878-travel-outdoor-ontario-parks/task.json +25 -0
  205. clawbench/data/test-cases/883-education-learning-hobby-class-sur-la-table/task.json +25 -0
  206. clawbench/data/test-cases/884-entertainment-hobbies-experience-breakout-games/task.json +25 -0
  207. clawbench/data/test-cases/885-entertainment-hobbies-experience-bowlero/task.json +25 -0
  208. clawbench/data/test-cases/886-entertainment-hobbies-experience-topgolf/task.json +25 -0
  209. clawbench/data/test-cases/lite.json +226 -0
  210. clawbench/data/test-cases/lite.schema.json +105 -0
  211. clawbench/data/test-cases/task.schema.json +132 -0
  212. clawbench/data/tools/build_clawbench_lite_enc.py +161 -0
  213. clawbench/doctor.py +171 -0
  214. clawbench/engine.py +180 -0
  215. clawbench/generate_resume_pdf.py +140 -0
  216. clawbench/hf_upload.py +78 -0
  217. clawbench/image.py +127 -0
  218. clawbench/paths.py +150 -0
  219. clawbench/resume_template.json +104 -0
  220. clawbench/run.py +942 -0
  221. clawbench/tui.py +1401 -0
  222. clawbench_cli-0.1.2.dist-info/METADATA +770 -0
  223. clawbench_cli-0.1.2.dist-info/RECORD +226 -0
  224. clawbench_cli-0.1.2.dist-info/WHEEL +4 -0
  225. clawbench_cli-0.1.2.dist-info/entry_points.txt +4 -0
  226. clawbench_cli-0.1.2.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,27 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ EXT_DIR="$(cd "$(dirname "$0")" && pwd)"
5
+
6
+ # Detect Chrome binary
7
+ if [[ "$OSTYPE" == "darwin"* ]]; then
8
+ CHROME="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
9
+ elif command -v google-chrome-stable &>/dev/null; then
10
+ CHROME="google-chrome-stable"
11
+ elif command -v google-chrome &>/dev/null; then
12
+ CHROME="google-chrome"
13
+ elif command -v chromium-browser &>/dev/null; then
14
+ CHROME="chromium-browser"
15
+ elif command -v chromium &>/dev/null; then
16
+ CHROME="chromium"
17
+ else
18
+ echo "Chrome not found" && exit 1
19
+ fi
20
+
21
+ "$CHROME" \
22
+ --no-first-run \
23
+ --disable-default-apps \
24
+ --remote-debugging-port=9222 \
25
+ --load-extension="$EXT_DIR" \
26
+ --disable-extensions-except="$EXT_DIR" \
27
+ "$@"
@@ -0,0 +1,200 @@
1
+ /**
2
+ * stealth.js — Anti-bot-detection patches.
3
+ *
4
+ * Injected at document_start in the MAIN world (see manifest.json).
5
+ * Must run before any page script to reliably override browser fingerprints.
6
+ */
7
+
8
+ (function () {
9
+ "use strict";
10
+
11
+ // 1. navigator.webdriver → false (real Chrome returns false, not undefined)
12
+ try {
13
+ Object.defineProperty(navigator, "webdriver", {
14
+ get: () => false,
15
+ configurable: true,
16
+ });
17
+ } catch (_) {}
18
+
19
+ // 2. navigator.languages — ensure realistic value
20
+ try {
21
+ Object.defineProperty(navigator, "languages", {
22
+ get: () => ["en-US", "en"],
23
+ configurable: true,
24
+ });
25
+ } catch (_) {}
26
+
27
+ // 3. navigator.plugins — fake standard Chrome plugins
28
+ try {
29
+ const fakePlugins = [
30
+ { name: "Chrome PDF Plugin", filename: "internal-pdf-viewer", description: "Portable Document Format" },
31
+ { name: "Chrome PDF Viewer", filename: "mhjfbmdgcfjbbpaeojofohoefgiehjai", description: "" },
32
+ { name: "Native Client", filename: "internal-nacl-plugin", description: "" },
33
+ ];
34
+
35
+ const pluginArray = Object.create(PluginArray.prototype);
36
+ fakePlugins.forEach((p, i) => {
37
+ const plugin = Object.create(Plugin.prototype);
38
+ Object.defineProperties(plugin, {
39
+ name: { get: () => p.name },
40
+ filename: { get: () => p.filename },
41
+ description: { get: () => p.description },
42
+ length: { get: () => 0 },
43
+ });
44
+ Object.defineProperty(pluginArray, i, { get: () => plugin, enumerable: true });
45
+ });
46
+ Object.defineProperty(pluginArray, "length", { get: () => fakePlugins.length });
47
+ pluginArray.item = (i) => pluginArray[i] || null;
48
+ pluginArray.namedItem = (name) => {
49
+ const idx = fakePlugins.findIndex((p) => p.name === name);
50
+ return idx >= 0 ? pluginArray[idx] : null;
51
+ };
52
+ pluginArray.refresh = () => {};
53
+ pluginArray[Symbol.iterator] = function* () {
54
+ for (let i = 0; i < fakePlugins.length; i++) yield pluginArray[i];
55
+ };
56
+
57
+ Object.defineProperty(navigator, "plugins", {
58
+ get: () => pluginArray,
59
+ configurable: true,
60
+ });
61
+ } catch (_) {}
62
+
63
+ // 4. navigator.mimeTypes — match the fake plugins
64
+ try {
65
+ const fakeMimeTypes = [
66
+ { type: "application/pdf", suffixes: "pdf", description: "Portable Document Format" },
67
+ { type: "application/x-google-chrome-pdf", suffixes: "pdf", description: "Portable Document Format" },
68
+ ];
69
+
70
+ const mimeTypeArray = Object.create(MimeTypeArray.prototype);
71
+ fakeMimeTypes.forEach((m, i) => {
72
+ const mimeType = Object.create(MimeType.prototype);
73
+ Object.defineProperties(mimeType, {
74
+ type: { get: () => m.type },
75
+ suffixes: { get: () => m.suffixes },
76
+ description: { get: () => m.description },
77
+ enabledPlugin: { get: () => null },
78
+ });
79
+ Object.defineProperty(mimeTypeArray, i, { get: () => mimeType, enumerable: true });
80
+ });
81
+ Object.defineProperty(mimeTypeArray, "length", { get: () => fakeMimeTypes.length });
82
+ mimeTypeArray.item = (i) => mimeTypeArray[i] || null;
83
+ mimeTypeArray.namedItem = (name) => {
84
+ const idx = fakeMimeTypes.findIndex((m) => m.type === name);
85
+ return idx >= 0 ? mimeTypeArray[idx] : null;
86
+ };
87
+ mimeTypeArray[Symbol.iterator] = function* () {
88
+ for (let i = 0; i < fakeMimeTypes.length; i++) yield mimeTypeArray[i];
89
+ };
90
+
91
+ Object.defineProperty(navigator, "mimeTypes", {
92
+ get: () => mimeTypeArray,
93
+ configurable: true,
94
+ });
95
+ } catch (_) {}
96
+
97
+ // 5. WebGL renderer/vendor spoofing
98
+ try {
99
+ const WEBGL_VENDOR = "Google Inc. (Google)";
100
+ const WEBGL_RENDERER =
101
+ "ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver)";
102
+
103
+ const originalGetParameter = WebGLRenderingContext.prototype.getParameter;
104
+ WebGLRenderingContext.prototype.getParameter = function (param) {
105
+ if (param === 0x9245) return WEBGL_VENDOR;
106
+ if (param === 0x9246) return WEBGL_RENDERER;
107
+ return originalGetParameter.call(this, param);
108
+ };
109
+
110
+ if (typeof WebGL2RenderingContext !== "undefined") {
111
+ const originalGetParameter2 = WebGL2RenderingContext.prototype.getParameter;
112
+ WebGL2RenderingContext.prototype.getParameter = function (param) {
113
+ if (param === 0x9245) return WEBGL_VENDOR;
114
+ if (param === 0x9246) return WEBGL_RENDERER;
115
+ return originalGetParameter2.call(this, param);
116
+ };
117
+ }
118
+ } catch (_) {}
119
+
120
+ // 6. Permissions API — notifications should return 'prompt', not 'denied'
121
+ try {
122
+ const originalQuery = Permissions.prototype.query;
123
+ Permissions.prototype.query = function (descriptor) {
124
+ if (descriptor && descriptor.name === "notifications") {
125
+ return Promise.resolve({ state: "prompt", onchange: null });
126
+ }
127
+ return originalQuery.call(this, descriptor);
128
+ };
129
+ } catch (_) {}
130
+
131
+ try {
132
+ Object.defineProperty(Notification, "permission", {
133
+ get: () => "default",
134
+ configurable: true,
135
+ });
136
+ } catch (_) {}
137
+
138
+ // 7. window.chrome.runtime — ensure it exists
139
+ try {
140
+ if (!window.chrome) {
141
+ window.chrome = {};
142
+ }
143
+ if (!window.chrome.runtime) {
144
+ window.chrome.runtime = {
145
+ connect: function () { return {}; },
146
+ sendMessage: function () {},
147
+ };
148
+ }
149
+ } catch (_) {}
150
+
151
+ // 8. Remove Chromedriver artifacts
152
+ try {
153
+ for (const prop of Object.getOwnPropertyNames(document)) {
154
+ if (prop.startsWith("$cdc_") || prop.startsWith("cdc_")) {
155
+ delete document[prop];
156
+ }
157
+ }
158
+ } catch (_) {}
159
+
160
+ // 9. navigator.hardwareConcurrency — ensure a reasonable value
161
+ try {
162
+ if (navigator.hardwareConcurrency < 4) {
163
+ Object.defineProperty(navigator, "hardwareConcurrency", {
164
+ get: () => 8,
165
+ configurable: true,
166
+ });
167
+ }
168
+ } catch (_) {}
169
+
170
+ // 10. navigator.deviceMemory — ensure a reasonable value
171
+ try {
172
+ if (!navigator.deviceMemory || navigator.deviceMemory < 4) {
173
+ Object.defineProperty(navigator, "deviceMemory", {
174
+ get: () => 8,
175
+ configurable: true,
176
+ });
177
+ }
178
+ } catch (_) {}
179
+
180
+ // 11. Patch iframe contentWindow.navigator to match parent
181
+ try {
182
+ const originalCreateElement = document.createElement.bind(document);
183
+ document.createElement = function (tagName, options) {
184
+ const el = originalCreateElement(tagName, options);
185
+ if (tagName.toLowerCase() === "iframe") {
186
+ el.addEventListener("load", function () {
187
+ try {
188
+ if (el.contentWindow && el.contentWindow.navigator) {
189
+ Object.defineProperty(el.contentWindow.navigator, "webdriver", {
190
+ get: () => false,
191
+ configurable: true,
192
+ });
193
+ }
194
+ } catch (_) {}
195
+ });
196
+ }
197
+ return el;
198
+ };
199
+ } catch (_) {}
200
+ })();
@@ -0,0 +1,51 @@
1
+ FROM python:3.12-slim
2
+
3
+ # Rootless Podman without subuid: only UID/GID 0 exist in the namespace.
4
+ # 1) Disable apt sandbox (apt tries to switch to _apt user → setgroups fails)
5
+ # 2) Wrap dpkg-statoverride so chown to non-root groups (e.g. messagebus) doesn't abort
6
+ # 3) Restore real dpkg-statoverride after install
7
+ RUN echo 'APT::Sandbox::User "root";' > /etc/apt/apt.conf.d/01disable-sandbox \
8
+ && mv /usr/bin/dpkg-statoverride /usr/bin/dpkg-statoverride.real \
9
+ && printf '#!/bin/sh\n/usr/bin/dpkg-statoverride.real "$@" 2>/dev/null || true\n' \
10
+ > /usr/bin/dpkg-statoverride && chmod +x /usr/bin/dpkg-statoverride \
11
+ && apt-get update && apt-get install -y --no-install-recommends \
12
+ chromium xvfb ffmpeg socat curl git x11vnc xclip \
13
+ libegl1 libgbm1 \
14
+ fonts-noto-color-emoji fonts-noto-cjk \
15
+ && mv /usr/bin/dpkg-statoverride.real /usr/bin/dpkg-statoverride \
16
+ && apt-get clean && rm -rf /var/lib/apt/lists/*
17
+
18
+ # noVNC + websockify for human mode (browser-based VNC client)
19
+ RUN git clone --depth 1 --branch v1.6.0 https://github.com/novnc/noVNC.git /opt/novnc \
20
+ && git clone --depth 1 --branch v0.13.0 https://github.com/novnc/websockify.git /opt/novnc/utils/websockify
21
+
22
+ COPY --from=node:24-slim /usr/local/bin/node /usr/local/bin/node
23
+ COPY --from=node:24-slim /usr/local/lib/node_modules /usr/local/lib/node_modules
24
+ RUN ln -s /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm \
25
+ && ln -s /usr/local/lib/node_modules/npm/bin/npx-cli.js /usr/local/bin/npx
26
+
27
+ # Pin openclaw version — patched below for #47879 (--autoConnect → --browserUrl)
28
+ RUN PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 npm install -g openclaw@2026.3.13
29
+
30
+ # Patch: replace --autoConnect with --browserUrl http://127.0.0.1:9222 in all dist files
31
+ # See https://github.com/openclaw/openclaw/issues/47879
32
+ RUN find /usr/local/lib/node_modules/openclaw/dist -name '*.js' -exec \
33
+ sed -i 's/"--autoConnect"/"--browserUrl","http:\/\/127.0.0.1:9222"/g' {} +
34
+
35
+ COPY --from=ghcr.io/astral-sh/uv:0.11.6 /uv /usr/local/bin/uv
36
+
37
+ WORKDIR /app
38
+ COPY extension-server/ ./extension-server/
39
+ RUN cd extension-server && UV_PYTHON_PREFERENCE=only-system uv sync
40
+
41
+ COPY chrome-extension/ ./chrome-extension/
42
+
43
+ COPY setup-openclaw.sh /setup-openclaw.sh
44
+ RUN chmod +x /setup-openclaw.sh
45
+
46
+ COPY entrypoint.sh /entrypoint.sh
47
+ RUN chmod +x /entrypoint.sh
48
+
49
+ EXPOSE 6080 7878 9223
50
+
51
+ ENTRYPOINT ["/entrypoint.sh"]
@@ -0,0 +1,394 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ # Ensure /data exists for recording output and diagnostic logs
5
+ mkdir -p /data
6
+
7
+ # Start virtual display
8
+ Xvfb :99 -screen 0 1920x1080x24 &
9
+ export DISPLAY=:99
10
+ sleep 1
11
+
12
+ # Start the server
13
+ cd /app/extension-server
14
+ uv run uvicorn server:app --host 0.0.0.0 --port 7878 &
15
+ sleep 1
16
+
17
+ # Start Chrome with extension (realistic profile to reduce bot detection)
18
+ mkdir -p /tmp/chrome-profile/Default
19
+
20
+ cat > /tmp/chrome-profile/Default/Preferences <<'PREFS'
21
+ {
22
+ "credentials_enable_service": false,
23
+ "profile": {
24
+ "password_manager_enabled": false,
25
+ "password_manager_leak_detection": false,
26
+ "name": "Default",
27
+ "created_by_version": "131.0.6778.139",
28
+ "content_settings": {
29
+ "exceptions": {}
30
+ }
31
+ },
32
+ "browser": {
33
+ "has_seen_welcome_page": true,
34
+ "check_default_browser": false,
35
+ "window_placement": {
36
+ "bottom": 1080,
37
+ "left": 0,
38
+ "maximized": false,
39
+ "right": 1920,
40
+ "top": 0,
41
+ "work_area_bottom": 1080,
42
+ "work_area_left": 0,
43
+ "work_area_right": 1920,
44
+ "work_area_top": 0
45
+ }
46
+ },
47
+ "dns_prefetching": {
48
+ "enabled": true
49
+ },
50
+ "safebrowsing": {
51
+ "enabled": true
52
+ },
53
+ "search": {
54
+ "suggest_enabled": true
55
+ },
56
+ "translate": {
57
+ "enabled": false
58
+ },
59
+ "intl": {
60
+ "accept_languages": "en-US,en"
61
+ },
62
+ "distribution": {
63
+ "import_bookmarks": false,
64
+ "skip_first_run_ui": true
65
+ }
66
+ }
67
+ PREFS
68
+
69
+ cat > /tmp/chrome-profile/Default/Bookmarks <<'BOOKMARKS'
70
+ {
71
+ "checksum": "b5f7e1a2c3d4e5f6a7b8c9d0e1f2a3b4",
72
+ "roots": {
73
+ "bookmark_bar": {
74
+ "children": [
75
+ {
76
+ "date_added": "13350000000000000",
77
+ "date_last_used": "0",
78
+ "guid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
79
+ "name": "Google",
80
+ "type": "url",
81
+ "url": "https://www.google.com/"
82
+ },
83
+ {
84
+ "date_added": "13350000000000000",
85
+ "date_last_used": "0",
86
+ "guid": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
87
+ "name": "YouTube",
88
+ "type": "url",
89
+ "url": "https://www.youtube.com/"
90
+ },
91
+ {
92
+ "date_added": "13350000000000000",
93
+ "date_last_used": "0",
94
+ "guid": "c3d4e5f6-a7b8-9012-cdef-123456789012",
95
+ "name": "Wikipedia",
96
+ "type": "url",
97
+ "url": "https://en.wikipedia.org/"
98
+ }
99
+ ],
100
+ "date_added": "13350000000000000",
101
+ "date_last_used": "0",
102
+ "date_modified": "13350000000000000",
103
+ "guid": "00000000-0000-4000-a000-000000000001",
104
+ "name": "Bookmarks bar",
105
+ "type": "folder"
106
+ },
107
+ "other": {
108
+ "children": [],
109
+ "date_added": "13350000000000000",
110
+ "date_last_used": "0",
111
+ "date_modified": "0",
112
+ "guid": "00000000-0000-4000-a000-000000000002",
113
+ "name": "Other bookmarks",
114
+ "type": "folder"
115
+ },
116
+ "synced": {
117
+ "children": [],
118
+ "date_added": "13350000000000000",
119
+ "date_last_used": "0",
120
+ "date_modified": "0",
121
+ "guid": "00000000-0000-4000-a000-000000000003",
122
+ "name": "Mobile bookmarks",
123
+ "type": "folder"
124
+ }
125
+ },
126
+ "version": 1
127
+ }
128
+ BOOKMARKS
129
+
130
+ cat > /tmp/chrome-profile/'Local State' <<'LOCALSTATE'
131
+ {
132
+ "browser": {
133
+ "enabled_labs_experiments": []
134
+ },
135
+ "profile": {
136
+ "info_cache": {
137
+ "Default": {
138
+ "active_time": 1710000000,
139
+ "is_consented_primary_account": false,
140
+ "name": "Person 1"
141
+ }
142
+ }
143
+ },
144
+ "user_experience_metrics": {
145
+ "reporting_enabled": false
146
+ }
147
+ }
148
+ LOCALSTATE
149
+
150
+ chromium \
151
+ --window-size=1920,1080 \
152
+ --window-position=0,0 \
153
+ --no-first-run \
154
+ --disable-default-apps \
155
+ --no-sandbox \
156
+ --disable-infobars \
157
+ --disable-dev-shm-usage \
158
+ --disable-blink-features=AutomationControlled \
159
+ --use-gl=angle --use-angle=swiftshader \
160
+ --enable-unsafe-swiftshader \
161
+ --enable-webgl \
162
+ --password-store=basic \
163
+ --use-mock-keychain \
164
+ --disable-sync \
165
+ --disable-features=PasswordLeakDetection,PasswordManager \
166
+ --user-data-dir=/tmp/chrome-profile \
167
+ --remote-debugging-port=9222 \
168
+ --remote-debugging-address=127.0.0.1 \
169
+ --remote-allow-origins=* \
170
+ --load-extension=/app/chrome-extension \
171
+ --disable-extensions-except=/app/chrome-extension \
172
+ about:blank &
173
+
174
+ # Forward CDP port to all interfaces
175
+ sleep 2
176
+ socat TCP-LISTEN:9223,fork,reuseaddr,bind=0.0.0.0 TCP:127.0.0.1:9222 &
177
+
178
+ # Always start noVNC so users can watch the browser in any mode.
179
+ # In human mode x11vnc also fires disconnect hooks for the watchdog.
180
+ echo "Starting noVNC..."
181
+ VNC_GONE_HOOK=""
182
+ VNC_ACCEPT_HOOK=""
183
+ if [ "$HUMAN_MODE" = "1" ]; then
184
+ VNC_GONE_HOOK="-gone touch /data/.vnc-disconnected"
185
+ VNC_ACCEPT_HOOK="-afteraccept rm -f /data/.vnc-disconnected"
186
+ fi
187
+ x11vnc -display :99 -nopw -shared -forever -rfbport 5900 -xkb \
188
+ $VNC_GONE_HOOK $VNC_ACCEPT_HOOK &
189
+ sleep 1
190
+
191
+ /opt/novnc/utils/novnc_proxy --vnc localhost:5900 --listen 6080 &
192
+ sleep 1
193
+ echo "============================================"
194
+ echo "noVNC ready: http://localhost:6080/vnc.html"
195
+ echo "============================================"
196
+
197
+ # Human mode: wait for VNC disconnect or eval match
198
+ if [ "$HUMAN_MODE" = "1" ]; then
199
+ echo "Human mode active."
200
+ if [ -n "$INSTRUCTION" ]; then
201
+ echo ""
202
+ echo "TASK: $INSTRUCTION"
203
+ echo ""
204
+ fi
205
+
206
+ # Human watchdog: no idle detection (humans pause to think)
207
+ MAX_WAIT=${TIME_LIMIT_S:-1800}
208
+ ELAPSED=0
209
+ DISCONNECT_WAIT=0
210
+ STOP_REASON=""
211
+
212
+ while [ "$ELAPSED" -lt "$MAX_WAIT" ]; do
213
+ sleep 5
214
+ ELAPSED=$((ELAPSED + 5))
215
+
216
+ # Check if eval interceptor matched
217
+ if [ -f /data/.stop-requested ]; then
218
+ echo "Stop requested by server (eval matched)."
219
+ STOP_REASON="eval_matched"
220
+ break
221
+ fi
222
+
223
+ # Check if VNC client disconnected (with 15s grace period for reconnect)
224
+ if [ -f /data/.vnc-disconnected ]; then
225
+ DISCONNECT_WAIT=$((DISCONNECT_WAIT + 5))
226
+ if [ "$DISCONNECT_WAIT" -ge 15 ]; then
227
+ echo "VNC client disconnected for ${DISCONNECT_WAIT}s, assuming done."
228
+ STOP_REASON="vnc_disconnected"
229
+ break
230
+ fi
231
+ else
232
+ DISCONNECT_WAIT=0
233
+ fi
234
+ done
235
+
236
+ if [ -z "$STOP_REASON" ]; then
237
+ echo "Time limit (${MAX_WAIT}s) exceeded."
238
+ STOP_REASON="time_limit_exceeded"
239
+ fi
240
+
241
+ echo "$STOP_REASON" > /data/.stop-reason
242
+
243
+ # Finalize bookkeeping (eval promotion, etc.) — recording keeps running
244
+ curl -sf -X POST http://localhost:7878/api/stop || true
245
+ rm -f /data/.stop-requested /data/.vnc-disconnected
246
+
247
+ # Grace period: keep recording for 15s to capture end state
248
+ echo "Human finished, recording grace period (15s)..."
249
+ sleep 15
250
+
251
+ # Stop recording
252
+ echo "Stopping recording..."
253
+ curl -sf -X POST http://localhost:7878/api/stop-recording || true
254
+ sleep 2
255
+ echo "Done."
256
+ exit 0
257
+ fi
258
+
259
+ # If no INSTRUCTION provided, skip OpenClaw and keep container alive for external use
260
+ if [ -z "$INSTRUCTION" ]; then
261
+ echo "No INSTRUCTION set, running in manual mode (no OpenClaw agent)."
262
+ wait -n
263
+ exit 0
264
+ fi
265
+
266
+ # Generate a shared gateway token so gateway and agent can authenticate
267
+ OPENCLAW_GATEWAY_TOKEN="$(head -c 32 /dev/urandom | od -A n -t x1 | tr -d ' \n')"
268
+ export OPENCLAW_GATEWAY_TOKEN
269
+
270
+ # Generate OpenClaw config from env vars
271
+ /setup-openclaw.sh
272
+
273
+ # Copy /my-info/ into the OpenClaw workspace so the agent can access it via ./my-info/
274
+ WORKSPACE=/root/workspace
275
+ mkdir -p "$WORKSPACE"
276
+ if [ -d /my-info ]; then
277
+ cp -r /my-info "$WORKSPACE/my-info"
278
+ echo "Copied /my-info to $WORKSPACE/my-info"
279
+ fi
280
+
281
+ # Wait for Chrome CDP to be ready
282
+ echo "Waiting for Chrome CDP..."
283
+ for i in $(seq 1 30); do
284
+ if curl -sf http://127.0.0.1:9222/json/version > /dev/null 2>&1; then
285
+ echo "Chrome CDP ready"
286
+ break
287
+ fi
288
+ if [ "$i" -eq 30 ]; then
289
+ echo "Chrome CDP not ready after 30s, aborting"
290
+ echo "chrome_cdp_timeout" > /data/.stop-reason
291
+ exit 1
292
+ fi
293
+ sleep 1
294
+ done
295
+
296
+ # Start OpenClaw gateway (log to /data for post-mortem debugging)
297
+ openclaw gateway run > /data/gateway.log 2>&1 &
298
+ GATEWAY_PID=$!
299
+ sleep 3
300
+
301
+ # Check gateway is alive
302
+ if ! kill -0 $GATEWAY_PID 2>/dev/null; then
303
+ echo "ERROR: OpenClaw gateway died on startup. Log:"
304
+ cat /data/gateway.log
305
+ echo "gateway_failed" > /data/.stop-reason
306
+ exit 1
307
+ fi
308
+ echo "OpenClaw gateway running (pid=$GATEWAY_PID)"
309
+
310
+ # Run the agent from workspace directory (where my-info/ lives)
311
+ echo "Starting OpenClaw agent from $WORKSPACE..."
312
+ TIMEOUT_MS=$(( ${TIME_LIMIT_S:-1800} * 1000 ))
313
+ AGENT_CMD=(openclaw agent --session-id clawbench --message "$INSTRUCTION" --thinking "${THINKING_LEVEL:-medium}" --timeout "$TIMEOUT_MS" --local)
314
+ echo "Agent command: ${AGENT_CMD[*]}"
315
+ if [ -n "$TEMPERATURE" ]; then
316
+ AGENT_CMD+=(--temperature "$TEMPERATURE")
317
+ fi
318
+ if [ -n "$MAX_TOKENS" ]; then
319
+ AGENT_CMD+=(--max-tokens "$MAX_TOKENS")
320
+ fi
321
+ cd "$WORKSPACE"
322
+ "${AGENT_CMD[@]}" > /data/agent.log 2>&1 &
323
+ AGENT_PID=$!
324
+
325
+ # Watchdog: wait for agent activity, then detect idle (no new actions for 300s)
326
+ IDLE_THRESHOLD=300
327
+ MAX_WAIT=${TIME_LIMIT_S:-1800}
328
+ ELAPSED=0
329
+ LAST_SIZE=0
330
+ IDLE=0
331
+ STOP_REASON=""
332
+
333
+ while kill -0 $AGENT_PID 2>/dev/null && [ "$ELAPSED" -lt "$MAX_WAIT" ]; do
334
+ sleep 5
335
+ ELAPSED=$((ELAPSED + 5))
336
+
337
+ # Check if server requested stop (eval interceptor matched)
338
+ if [ -f /data/.stop-requested ]; then
339
+ echo "Stop requested by server (eval matched), killing agent."
340
+ STOP_REASON="eval_matched"
341
+ break
342
+ fi
343
+
344
+ CURRENT_SIZE=$(wc -c < /data/actions.jsonl 2>/dev/null || echo 0)
345
+
346
+ if [ "$CURRENT_SIZE" -gt 0 ] && [ "$CURRENT_SIZE" -eq "$LAST_SIZE" ]; then
347
+ IDLE=$((IDLE + 5))
348
+ if [ "$IDLE" -ge "$IDLE_THRESHOLD" ]; then
349
+ echo "Agent idle for ${IDLE_THRESHOLD}s, assuming done."
350
+ STOP_REASON="agent_idle"
351
+ break
352
+ fi
353
+ else
354
+ IDLE=0
355
+ fi
356
+ LAST_SIZE=$CURRENT_SIZE
357
+ done
358
+
359
+ # Determine stop reason if not set (loop exited without breaking)
360
+ if [ -z "$STOP_REASON" ]; then
361
+ if ! kill -0 $AGENT_PID 2>/dev/null; then
362
+ STOP_REASON="agent_exited"
363
+ else
364
+ echo "Time limit (${MAX_WAIT}s) exceeded, killing agent."
365
+ STOP_REASON="time_limit_exceeded"
366
+ fi
367
+ fi
368
+
369
+ echo "$STOP_REASON" > /data/.stop-reason
370
+
371
+ # Kill all openclaw processes
372
+ kill $AGENT_PID 2>/dev/null || true
373
+ kill $GATEWAY_PID 2>/dev/null || true
374
+ pkill -f "openclaw" 2>/dev/null || true
375
+ sleep 2
376
+
377
+ # Copy OpenClaw session transcript to /data
378
+ cp /root/.openclaw/agents/main/sessions/clawbench.jsonl /data/agent-messages.jsonl 2>/dev/null || true
379
+
380
+ # Finalize bookkeeping (eval promotion, etc.) — recording keeps running
381
+ curl -sf -X POST http://localhost:7878/api/stop || true
382
+
383
+ # Clean up internal marker (created by /api/stop)
384
+ rm -f /data/.stop-requested
385
+
386
+ # Grace period: keep recording for 15s after agent is killed to capture end result
387
+ echo "Agent finished, recording grace period (15s)..."
388
+ sleep 15
389
+
390
+ # Stop recording
391
+ echo "Stopping recording..."
392
+ curl -sf -X POST http://localhost:7878/api/stop-recording || true
393
+ sleep 2
394
+ echo "Done."