@playdrop/playdrop-cli 0.16.12 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (364) hide show
  1. package/config/client-meta.json +4 -4
  2. package/dist/apiClient.d.ts +1 -2
  3. package/dist/apiClient.js +1 -16
  4. package/dist/appUrls.js +4 -0
  5. package/dist/apps/build.d.ts +12 -4
  6. package/dist/apps/build.js +55 -22
  7. package/dist/apps/index.d.ts +2 -1
  8. package/dist/apps/index.js +4 -1
  9. package/dist/apps/loadCheck.d.ts +5 -1
  10. package/dist/apps/loadCheck.js +52 -18
  11. package/dist/apps/serverBuild.d.ts +17 -0
  12. package/dist/apps/serverBuild.js +155 -0
  13. package/dist/apps/staticHtml.d.ts +1 -0
  14. package/dist/apps/staticHtml.js +19 -10
  15. package/dist/apps/upload.d.ts +12 -2
  16. package/dist/apps/upload.js +127 -46
  17. package/dist/catalogue.d.ts +23 -1
  18. package/dist/catalogue.js +126 -100
  19. package/dist/commandContext.d.ts +6 -3
  20. package/dist/commandContext.js +108 -18
  21. package/dist/commands/build.js +0 -7
  22. package/dist/commands/captureListing.d.ts +3 -0
  23. package/dist/commands/captureListing.js +48 -4
  24. package/dist/commands/chat.d.ts +4 -7
  25. package/dist/commands/chat.js +165 -77
  26. package/dist/commands/check.js +34 -1
  27. package/dist/commands/create.js +30 -263
  28. package/dist/commands/dev.js +37 -3
  29. package/dist/commands/devBrowser.js +2 -2
  30. package/dist/commands/devGameServer.d.ts +16 -0
  31. package/dist/commands/devGameServer.js +237 -0
  32. package/dist/commands/devRuntimeAssets.d.ts +1 -0
  33. package/dist/commands/devRuntimeAssets.js +2 -0
  34. package/dist/commands/devServer.d.ts +3 -0
  35. package/dist/commands/devServer.js +111 -5
  36. package/dist/commands/generation.d.ts +33 -4
  37. package/dist/commands/generation.js +304 -115
  38. package/dist/commands/login.js +23 -7
  39. package/dist/commands/review.d.ts +1 -1
  40. package/dist/commands/review.js +68 -95
  41. package/dist/commands/source.d.ts +19 -0
  42. package/dist/commands/source.js +142 -0
  43. package/dist/commands/tweaks.js +5 -11
  44. package/dist/commands/upload-content.d.ts +1 -1
  45. package/dist/commands/upload-content.js +16 -12
  46. package/dist/commands/upload.d.ts +17 -1
  47. package/dist/commands/upload.js +47 -10
  48. package/dist/commands/worker/archive-staging.d.ts +1 -0
  49. package/dist/commands/worker/archive-staging.js +13 -2
  50. package/dist/commands/worker/e2e-fixtures.d.ts +32 -0
  51. package/dist/commands/worker/e2e-fixtures.js +466 -0
  52. package/dist/commands/worker/game-server-infra.d.ts +80 -0
  53. package/dist/commands/worker/game-server-infra.js +595 -0
  54. package/dist/commands/worker/game-source-git.d.ts +126 -0
  55. package/dist/commands/worker/game-source-git.js +780 -0
  56. package/dist/commands/worker/generation.d.ts +94 -0
  57. package/dist/commands/worker/generation.js +1458 -0
  58. package/dist/commands/worker/provider-telemetry.js +1 -1
  59. package/dist/commands/worker/runtime.d.ts +8 -10
  60. package/dist/commands/worker/runtime.js +53 -48
  61. package/dist/commands/worker.d.ts +79 -19
  62. package/dist/commands/worker.js +1796 -419
  63. package/dist/config.d.ts +2 -0
  64. package/dist/config.js +16 -3
  65. package/dist/environment.d.ts +0 -1
  66. package/dist/environment.js +0 -8
  67. package/dist/index.js +92 -22
  68. package/dist/workerAppProject.d.ts +1 -1
  69. package/dist/workerAppProject.js +15 -2
  70. package/node_modules/@playdrop/api-client/dist/client.d.ts +143 -39
  71. package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
  72. package/node_modules/@playdrop/api-client/dist/client.js +33 -0
  73. package/node_modules/@playdrop/api-client/dist/core/request.d.ts +1 -0
  74. package/node_modules/@playdrop/api-client/dist/core/request.d.ts.map +1 -1
  75. package/node_modules/@playdrop/api-client/dist/core/request.js +17 -11
  76. package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.d.ts +51 -6
  77. package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.d.ts.map +1 -1
  78. package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.js +338 -41
  79. package/node_modules/@playdrop/api-client/dist/domains/ai.d.ts +24 -1
  80. package/node_modules/@playdrop/api-client/dist/domains/ai.d.ts.map +1 -1
  81. package/node_modules/@playdrop/api-client/dist/domains/ai.js +20 -0
  82. package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts +13 -8
  83. package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts.map +1 -1
  84. package/node_modules/@playdrop/api-client/dist/domains/apps.js +158 -90
  85. package/node_modules/@playdrop/api-client/dist/domains/assets.d.ts +4 -0
  86. package/node_modules/@playdrop/api-client/dist/domains/assets.d.ts.map +1 -1
  87. package/node_modules/@playdrop/api-client/dist/domains/assets.js +27 -0
  88. package/node_modules/@playdrop/api-client/dist/domains/chat.d.ts +152 -21
  89. package/node_modules/@playdrop/api-client/dist/domains/chat.d.ts.map +1 -1
  90. package/node_modules/@playdrop/api-client/dist/domains/chat.js +477 -100
  91. package/node_modules/@playdrop/api-client/dist/domains/player-meta.d.ts +2 -1
  92. package/node_modules/@playdrop/api-client/dist/domains/player-meta.d.ts.map +1 -1
  93. package/node_modules/@playdrop/api-client/dist/domains/player-meta.js +9 -0
  94. package/node_modules/@playdrop/api-client/dist/domains/playtrade.d.ts +1 -2
  95. package/node_modules/@playdrop/api-client/dist/domains/playtrade.d.ts.map +1 -1
  96. package/node_modules/@playdrop/api-client/dist/domains/playtrade.js +0 -11
  97. package/node_modules/@playdrop/api-client/dist/domains/social.d.ts +29 -0
  98. package/node_modules/@playdrop/api-client/dist/domains/social.d.ts.map +1 -0
  99. package/node_modules/@playdrop/api-client/dist/domains/social.js +79 -0
  100. package/node_modules/@playdrop/api-client/dist/index.d.ts +81 -30
  101. package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
  102. package/node_modules/@playdrop/api-client/dist/index.js +217 -56
  103. package/node_modules/@playdrop/api-client/dist/runtime.d.ts +18 -0
  104. package/node_modules/@playdrop/api-client/dist/runtime.d.ts.map +1 -0
  105. package/node_modules/@playdrop/api-client/dist/runtime.js +54 -0
  106. package/node_modules/@playdrop/api-client/package.json +7 -0
  107. package/node_modules/@playdrop/config/client-meta.json +4 -4
  108. package/node_modules/@playdrop/config/dist/src/creator-plans.d.ts +11 -0
  109. package/node_modules/@playdrop/config/dist/src/creator-plans.d.ts.map +1 -1
  110. package/node_modules/@playdrop/config/dist/src/creator-plans.js +13 -1
  111. package/node_modules/@playdrop/config/dist/src/deployment.d.ts +0 -1
  112. package/node_modules/@playdrop/config/dist/src/deployment.d.ts.map +1 -1
  113. package/node_modules/@playdrop/config/dist/src/deployment.js +0 -8
  114. package/node_modules/@playdrop/config/dist/src/public-deployment.d.ts +0 -1
  115. package/node_modules/@playdrop/config/dist/src/public-deployment.d.ts.map +1 -1
  116. package/node_modules/@playdrop/config/dist/src/public-deployment.js +0 -8
  117. package/node_modules/@playdrop/config/dist/src/runtime-service-origins.d.ts +1 -1
  118. package/node_modules/@playdrop/config/dist/src/runtime-service-origins.js +2 -2
  119. package/node_modules/@playdrop/config/dist/src/server/logging.d.ts.map +1 -1
  120. package/node_modules/@playdrop/config/dist/src/server/logging.js +31 -2
  121. package/node_modules/@playdrop/config/dist/test/creator-plans.test.js +10 -0
  122. package/node_modules/@playdrop/config/dist/test/deployment.test.js +0 -4
  123. package/node_modules/@playdrop/config/dist/test/runtime-service-origins.test.js +2 -0
  124. package/node_modules/@playdrop/config/dist/tsconfig.tsbuildinfo +1 -1
  125. package/node_modules/@playdrop/config/package.json +6 -0
  126. package/node_modules/@playdrop/game-source-git/dist/src/archive.d.ts +9 -0
  127. package/node_modules/@playdrop/game-source-git/dist/src/archive.d.ts.map +1 -0
  128. package/node_modules/@playdrop/game-source-git/dist/src/archive.js +107 -0
  129. package/node_modules/@playdrop/game-source-git/dist/src/bundle-storage.d.ts +29 -0
  130. package/node_modules/@playdrop/game-source-git/dist/src/bundle-storage.d.ts.map +1 -0
  131. package/node_modules/@playdrop/game-source-git/dist/src/bundle-storage.js +64 -0
  132. package/node_modules/@playdrop/game-source-git/dist/src/export.d.ts +8 -0
  133. package/node_modules/@playdrop/game-source-git/dist/src/export.d.ts.map +1 -0
  134. package/node_modules/@playdrop/game-source-git/dist/src/export.js +25 -0
  135. package/node_modules/@playdrop/game-source-git/dist/src/git.d.ts +39 -0
  136. package/node_modules/@playdrop/game-source-git/dist/src/git.d.ts.map +1 -0
  137. package/node_modules/@playdrop/game-source-git/dist/src/git.js +195 -0
  138. package/node_modules/@playdrop/game-source-git/dist/src/ignore.d.ts +6 -0
  139. package/node_modules/@playdrop/game-source-git/dist/src/ignore.d.ts.map +1 -0
  140. package/node_modules/@playdrop/game-source-git/dist/src/ignore.js +51 -0
  141. package/node_modules/@playdrop/game-source-git/dist/src/index.d.ts +10 -0
  142. package/node_modules/@playdrop/game-source-git/dist/src/index.d.ts.map +1 -0
  143. package/node_modules/@playdrop/{boxel-core/dist/src/humanoid/r15 → game-source-git/dist/src}/index.js +9 -4
  144. package/node_modules/@playdrop/game-source-git/dist/src/lock.d.ts +17 -0
  145. package/node_modules/@playdrop/game-source-git/dist/src/lock.d.ts.map +1 -0
  146. package/node_modules/@playdrop/game-source-git/dist/src/lock.js +72 -0
  147. package/node_modules/@playdrop/game-source-git/dist/src/materialize.d.ts +10 -0
  148. package/node_modules/@playdrop/game-source-git/dist/src/materialize.d.ts.map +1 -0
  149. package/node_modules/@playdrop/game-source-git/dist/src/materialize.js +99 -0
  150. package/node_modules/@playdrop/game-source-git/dist/src/paths.d.ts +24 -0
  151. package/node_modules/@playdrop/game-source-git/dist/src/paths.d.ts.map +1 -0
  152. package/node_modules/@playdrop/game-source-git/dist/src/paths.js +207 -0
  153. package/node_modules/@playdrop/game-source-git/dist/src/repository.d.ts +66 -0
  154. package/node_modules/@playdrop/game-source-git/dist/src/repository.d.ts.map +1 -0
  155. package/node_modules/@playdrop/game-source-git/dist/src/repository.js +213 -0
  156. package/node_modules/@playdrop/game-source-git/dist/test/core.test.d.ts +2 -0
  157. package/node_modules/@playdrop/game-source-git/dist/test/core.test.d.ts.map +1 -0
  158. package/node_modules/@playdrop/game-source-git/dist/test/core.test.js +441 -0
  159. package/node_modules/@playdrop/game-source-git/dist/tsconfig.tsbuildinfo +1 -0
  160. package/node_modules/@playdrop/game-source-git/package.json +28 -0
  161. package/node_modules/@playdrop/types/dist/agent-task-terminal.d.ts +2 -2
  162. package/node_modules/@playdrop/types/dist/agent-task-terminal.d.ts.map +1 -1
  163. package/node_modules/@playdrop/types/dist/agent-task-terminal.js +15 -9
  164. package/node_modules/@playdrop/types/dist/api.d.ts +730 -166
  165. package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
  166. package/node_modules/@playdrop/types/dist/api.js +65 -18
  167. package/node_modules/@playdrop/types/dist/asset-pack.d.ts +2 -0
  168. package/node_modules/@playdrop/types/dist/asset-pack.d.ts.map +1 -1
  169. package/node_modules/@playdrop/types/dist/asset.d.ts +22 -8
  170. package/node_modules/@playdrop/types/dist/asset.d.ts.map +1 -1
  171. package/node_modules/@playdrop/types/dist/asset.js +26 -26
  172. package/node_modules/@playdrop/types/dist/chat.d.ts +273 -31
  173. package/node_modules/@playdrop/types/dist/chat.d.ts.map +1 -1
  174. package/node_modules/@playdrop/types/dist/chat.js +38 -4
  175. package/node_modules/@playdrop/types/dist/index.d.ts +3 -0
  176. package/node_modules/@playdrop/types/dist/index.d.ts.map +1 -1
  177. package/node_modules/@playdrop/types/dist/index.js +7 -1
  178. package/node_modules/@playdrop/types/dist/public-cache-tags.d.ts +11 -0
  179. package/node_modules/@playdrop/types/dist/public-cache-tags.d.ts.map +1 -0
  180. package/node_modules/@playdrop/types/dist/public-cache-tags.js +78 -0
  181. package/node_modules/@playdrop/types/dist/realtime.d.ts +3 -15
  182. package/node_modules/@playdrop/types/dist/realtime.d.ts.map +1 -1
  183. package/node_modules/@playdrop/types/dist/social.d.ts +64 -0
  184. package/node_modules/@playdrop/types/dist/social.d.ts.map +1 -0
  185. package/node_modules/@playdrop/{boxel-three/dist/src/types.js → types/dist/social.js} +14 -2
  186. package/node_modules/@playdrop/types/dist/spritesheet.d.ts +51 -0
  187. package/node_modules/@playdrop/types/dist/spritesheet.d.ts.map +1 -0
  188. package/node_modules/@playdrop/types/dist/spritesheet.js +73 -0
  189. package/node_modules/@playdrop/types/dist/version.d.ts +86 -58
  190. package/node_modules/@playdrop/types/dist/version.d.ts.map +1 -1
  191. package/node_modules/@playdrop/types/dist/version.js +111 -0
  192. package/node_modules/@playdrop/types/package.json +14 -0
  193. package/node_modules/@playdrop/vox-three/dist/src/index.d.ts +1 -1
  194. package/node_modules/@playdrop/vox-three/dist/src/index.js +1 -1
  195. package/node_modules/@playdrop/vox-three/dist/src/index.js.map +1 -1
  196. package/node_modules/@playdrop/vox-three/dist/src/vox.d.ts +0 -3
  197. package/node_modules/@playdrop/vox-three/dist/src/vox.js +0 -110
  198. package/node_modules/@playdrop/vox-three/dist/src/vox.js.map +1 -1
  199. package/node_modules/@playdrop/vox-three/dist/test/vox.test.js +0 -18
  200. package/node_modules/@playdrop/vox-three/dist/test/vox.test.js.map +1 -1
  201. package/node_modules/@playdrop/vox-three/package.json +8 -4
  202. package/package.json +9 -11
  203. package/dist/assets/model-artifacts.d.ts +0 -8
  204. package/dist/assets/model-artifacts.js +0 -382
  205. package/dist/assets/model-worker.d.ts +0 -22
  206. package/dist/assets/model-worker.js +0 -123
  207. package/node_modules/@playdrop/ai-client/dist/index.d.ts +0 -441
  208. package/node_modules/@playdrop/ai-client/dist/index.d.ts.map +0 -1
  209. package/node_modules/@playdrop/ai-client/dist/index.js +0 -499
  210. package/node_modules/@playdrop/ai-client/package.json +0 -22
  211. package/node_modules/@playdrop/boxel-core/dist/src/entity-cleaner.d.ts +0 -13
  212. package/node_modules/@playdrop/boxel-core/dist/src/entity-cleaner.js +0 -141
  213. package/node_modules/@playdrop/boxel-core/dist/src/entity-cleaner.js.map +0 -1
  214. package/node_modules/@playdrop/boxel-core/dist/src/entity-utils.d.ts +0 -47
  215. package/node_modules/@playdrop/boxel-core/dist/src/entity-utils.js +0 -313
  216. package/node_modules/@playdrop/boxel-core/dist/src/entity-utils.js.map +0 -1
  217. package/node_modules/@playdrop/boxel-core/dist/src/format-utils.d.ts +0 -3
  218. package/node_modules/@playdrop/boxel-core/dist/src/format-utils.js +0 -36
  219. package/node_modules/@playdrop/boxel-core/dist/src/format-utils.js.map +0 -1
  220. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/geometry.d.ts +0 -23
  221. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/geometry.js +0 -11
  222. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/geometry.js.map +0 -1
  223. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/index.d.ts +0 -4
  224. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/index.js +0 -21
  225. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/index.js.map +0 -1
  226. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/common.d.ts +0 -4
  227. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/common.js +0 -24
  228. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/common.js.map +0 -1
  229. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/index.d.ts +0 -3
  230. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/index.js.map +0 -1
  231. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/textured-builder.d.ts +0 -3
  232. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/textured-builder.js +0 -387
  233. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/textured-builder.js.map +0 -1
  234. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/types.d.ts +0 -6
  235. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/types.js +0 -3
  236. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/types.js.map +0 -1
  237. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/voxel-builder.d.ts +0 -3
  238. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/voxel-builder.js +0 -373
  239. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/voxel-builder.js.map +0 -1
  240. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/builder.d.ts +0 -12
  241. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/builder.js +0 -215
  242. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/builder.js.map +0 -1
  243. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/index.d.ts +0 -4
  244. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/index.js +0 -18
  245. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/index.js.map +0 -1
  246. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/scanner.d.ts +0 -42
  247. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/scanner.js +0 -145
  248. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/scanner.js.map +0 -1
  249. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/textures/artifacts.d.ts +0 -26
  250. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/textures/artifacts.js +0 -65
  251. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/textures/artifacts.js.map +0 -1
  252. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/textures/face-map.d.ts +0 -33
  253. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/textures/face-map.js +0 -175
  254. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/textures/face-map.js.map +0 -1
  255. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/templates/humanoid_r15.json +0 -1517
  256. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/templates/humanoid_r6.json +0 -61
  257. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/templates/index.d.ts +0 -10
  258. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/templates/index.js +0 -23
  259. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/templates/index.js.map +0 -1
  260. package/node_modules/@playdrop/boxel-core/dist/src/index.d.ts +0 -19
  261. package/node_modules/@playdrop/boxel-core/dist/src/index.js +0 -36
  262. package/node_modules/@playdrop/boxel-core/dist/src/index.js.map +0 -1
  263. package/node_modules/@playdrop/boxel-core/dist/src/materials.d.ts +0 -26
  264. package/node_modules/@playdrop/boxel-core/dist/src/materials.js +0 -184
  265. package/node_modules/@playdrop/boxel-core/dist/src/materials.js.map +0 -1
  266. package/node_modules/@playdrop/boxel-core/dist/src/palette_tools.d.ts +0 -68
  267. package/node_modules/@playdrop/boxel-core/dist/src/palette_tools.js +0 -488
  268. package/node_modules/@playdrop/boxel-core/dist/src/palette_tools.js.map +0 -1
  269. package/node_modules/@playdrop/boxel-core/dist/src/serialization.d.ts +0 -4
  270. package/node_modules/@playdrop/boxel-core/dist/src/serialization.js +0 -380
  271. package/node_modules/@playdrop/boxel-core/dist/src/serialization.js.map +0 -1
  272. package/node_modules/@playdrop/boxel-core/dist/src/textures.d.ts +0 -29
  273. package/node_modules/@playdrop/boxel-core/dist/src/textures.js +0 -208
  274. package/node_modules/@playdrop/boxel-core/dist/src/textures.js.map +0 -1
  275. package/node_modules/@playdrop/boxel-core/dist/src/transforms/textured-boxes/infer-face.d.ts +0 -9
  276. package/node_modules/@playdrop/boxel-core/dist/src/transforms/textured-boxes/infer-face.js +0 -312
  277. package/node_modules/@playdrop/boxel-core/dist/src/transforms/textured-boxes/infer-face.js.map +0 -1
  278. package/node_modules/@playdrop/boxel-core/dist/src/transforms/textured-boxes/layer-mode.d.ts +0 -9
  279. package/node_modules/@playdrop/boxel-core/dist/src/transforms/textured-boxes/layer-mode.js +0 -154
  280. package/node_modules/@playdrop/boxel-core/dist/src/transforms/textured-boxes/layer-mode.js.map +0 -1
  281. package/node_modules/@playdrop/boxel-core/dist/src/transforms/textured-boxes/merge-overlay.d.ts +0 -2
  282. package/node_modules/@playdrop/boxel-core/dist/src/transforms/textured-boxes/merge-overlay.js +0 -121
  283. package/node_modules/@playdrop/boxel-core/dist/src/transforms/textured-boxes/merge-overlay.js.map +0 -1
  284. package/node_modules/@playdrop/boxel-core/dist/src/transforms/textured-boxes/slice.d.ts +0 -20
  285. package/node_modules/@playdrop/boxel-core/dist/src/transforms/textured-boxes/slice.js +0 -389
  286. package/node_modules/@playdrop/boxel-core/dist/src/transforms/textured-boxes/slice.js.map +0 -1
  287. package/node_modules/@playdrop/boxel-core/dist/src/transforms/upscale.d.ts +0 -3
  288. package/node_modules/@playdrop/boxel-core/dist/src/transforms/upscale.js +0 -206
  289. package/node_modules/@playdrop/boxel-core/dist/src/transforms/upscale.js.map +0 -1
  290. package/node_modules/@playdrop/boxel-core/dist/src/transforms/voxels/slice.d.ts +0 -12
  291. package/node_modules/@playdrop/boxel-core/dist/src/transforms/voxels/slice.js +0 -81
  292. package/node_modules/@playdrop/boxel-core/dist/src/transforms/voxels/slice.js.map +0 -1
  293. package/node_modules/@playdrop/boxel-core/dist/src/transforms/voxels/textured-to-voxel.d.ts +0 -12
  294. package/node_modules/@playdrop/boxel-core/dist/src/transforms/voxels/textured-to-voxel.js +0 -318
  295. package/node_modules/@playdrop/boxel-core/dist/src/transforms/voxels/textured-to-voxel.js.map +0 -1
  296. package/node_modules/@playdrop/boxel-core/dist/src/types.d.ts +0 -168
  297. package/node_modules/@playdrop/boxel-core/dist/src/types.js +0 -3
  298. package/node_modules/@playdrop/boxel-core/dist/src/types.js.map +0 -1
  299. package/node_modules/@playdrop/boxel-core/dist/src/validation.d.ts +0 -24
  300. package/node_modules/@playdrop/boxel-core/dist/src/validation.js +0 -620
  301. package/node_modules/@playdrop/boxel-core/dist/src/validation.js.map +0 -1
  302. package/node_modules/@playdrop/boxel-core/dist/src/voxels/greedy-mesher.d.ts +0 -11
  303. package/node_modules/@playdrop/boxel-core/dist/src/voxels/greedy-mesher.js +0 -135
  304. package/node_modules/@playdrop/boxel-core/dist/src/voxels/greedy-mesher.js.map +0 -1
  305. package/node_modules/@playdrop/boxel-core/dist/src/voxels.d.ts +0 -23
  306. package/node_modules/@playdrop/boxel-core/dist/src/voxels.js +0 -52
  307. package/node_modules/@playdrop/boxel-core/dist/src/voxels.js.map +0 -1
  308. package/node_modules/@playdrop/boxel-core/package.json +0 -19
  309. package/node_modules/@playdrop/boxel-three/dist/src/animations.d.ts +0 -4
  310. package/node_modules/@playdrop/boxel-three/dist/src/animations.js +0 -92
  311. package/node_modules/@playdrop/boxel-three/dist/src/builders.d.ts +0 -8
  312. package/node_modules/@playdrop/boxel-three/dist/src/builders.js +0 -363
  313. package/node_modules/@playdrop/boxel-three/dist/src/context.d.ts +0 -24
  314. package/node_modules/@playdrop/boxel-three/dist/src/context.js +0 -58
  315. package/node_modules/@playdrop/boxel-three/dist/src/exporters/glb.d.ts +0 -100
  316. package/node_modules/@playdrop/boxel-three/dist/src/exporters/glb.js +0 -927
  317. package/node_modules/@playdrop/boxel-three/dist/src/exporters/image.d.ts +0 -18
  318. package/node_modules/@playdrop/boxel-three/dist/src/exporters/image.js +0 -294
  319. package/node_modules/@playdrop/boxel-three/dist/src/index.d.ts +0 -17
  320. package/node_modules/@playdrop/boxel-three/dist/src/index.js +0 -64
  321. package/node_modules/@playdrop/boxel-three/dist/src/instantiate.d.ts +0 -40
  322. package/node_modules/@playdrop/boxel-three/dist/src/instantiate.js +0 -96
  323. package/node_modules/@playdrop/boxel-three/dist/src/nodes.d.ts +0 -8
  324. package/node_modules/@playdrop/boxel-three/dist/src/nodes.js +0 -88
  325. package/node_modules/@playdrop/boxel-three/dist/src/overlays.d.ts +0 -7
  326. package/node_modules/@playdrop/boxel-three/dist/src/overlays.js +0 -123
  327. package/node_modules/@playdrop/boxel-three/dist/src/primitives.d.ts +0 -4
  328. package/node_modules/@playdrop/boxel-three/dist/src/primitives.js +0 -48
  329. package/node_modules/@playdrop/boxel-three/dist/src/scene.d.ts +0 -3
  330. package/node_modules/@playdrop/boxel-three/dist/src/scene.js +0 -168
  331. package/node_modules/@playdrop/boxel-three/dist/src/skinned-mesh.d.ts +0 -29
  332. package/node_modules/@playdrop/boxel-three/dist/src/skinned-mesh.js +0 -623
  333. package/node_modules/@playdrop/boxel-three/dist/src/texture-atlas.d.ts +0 -13
  334. package/node_modules/@playdrop/boxel-three/dist/src/texture-atlas.js +0 -133
  335. package/node_modules/@playdrop/boxel-three/dist/src/textures.d.ts +0 -17
  336. package/node_modules/@playdrop/boxel-three/dist/src/textures.js +0 -216
  337. package/node_modules/@playdrop/boxel-three/dist/src/types.d.ts +0 -112
  338. package/node_modules/@playdrop/boxel-three/dist/src/voxels/faces.d.ts +0 -28
  339. package/node_modules/@playdrop/boxel-three/dist/src/voxels/faces.js +0 -344
  340. package/node_modules/@playdrop/boxel-three/dist/src/voxels/mesher.d.ts +0 -24
  341. package/node_modules/@playdrop/boxel-three/dist/src/voxels/mesher.js +0 -113
  342. package/node_modules/@playdrop/boxel-three/dist/test/export-image.playwright.test.d.ts +0 -1
  343. package/node_modules/@playdrop/boxel-three/dist/test/export-image.playwright.test.js +0 -137
  344. package/node_modules/@playdrop/boxel-three/dist/test/fixtures/render-worker.d.ts +0 -20
  345. package/node_modules/@playdrop/boxel-three/dist/test/fixtures/render-worker.js +0 -85
  346. package/node_modules/@playdrop/boxel-three/dist/test/glb-skinned.test.d.ts +0 -1
  347. package/node_modules/@playdrop/boxel-three/dist/test/glb-skinned.test.js +0 -104
  348. package/node_modules/@playdrop/boxel-three/dist/test/index.test.d.ts +0 -1
  349. package/node_modules/@playdrop/boxel-three/dist/test/index.test.js +0 -30
  350. package/node_modules/@playdrop/boxel-three/dist/test/instantiate.test.d.ts +0 -1
  351. package/node_modules/@playdrop/boxel-three/dist/test/instantiate.test.js +0 -88
  352. package/node_modules/@playdrop/boxel-three/dist/test/overlays.test.d.ts +0 -1
  353. package/node_modules/@playdrop/boxel-three/dist/test/overlays.test.js +0 -42
  354. package/node_modules/@playdrop/boxel-three/dist/test/scene-filter.test.d.ts +0 -1
  355. package/node_modules/@playdrop/boxel-three/dist/test/scene-filter.test.js +0 -73
  356. package/node_modules/@playdrop/boxel-three/dist/test/scene-smoke.test.d.ts +0 -1
  357. package/node_modules/@playdrop/boxel-three/dist/test/scene-smoke.test.js +0 -85
  358. package/node_modules/@playdrop/boxel-three/dist/test/skinned-mesh.test.d.ts +0 -1
  359. package/node_modules/@playdrop/boxel-three/dist/test/skinned-mesh.test.js +0 -73
  360. package/node_modules/@playdrop/boxel-three/dist/test/textured-overlay.test.d.ts +0 -1
  361. package/node_modules/@playdrop/boxel-three/dist/test/textured-overlay.test.js +0 -136
  362. package/node_modules/@playdrop/boxel-three/dist/test/voxels.test.d.ts +0 -1
  363. package/node_modules/@playdrop/boxel-three/dist/test/voxels.test.js +0 -42
  364. package/node_modules/@playdrop/boxel-three/package.json +0 -28
@@ -3,34 +3,41 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.WORKER_TASK_WORKSPACE_RETENTION = exports.WORKER_CONTEXT_PROJECT_VALIDATE_NOT_ALLOWED_MESSAGE = exports.WORKER_CONTEXT_COMMAND_NOT_ALLOWED_MESSAGE = exports.WORKER_SESSION_EXPIRED_MESSAGE = exports.WORKSPACE_ARCHIVE_DOWNLOAD_TIMEOUT_MS = exports.runLoggedProcess = exports.readEnvBoolean = exports.readCodexSandboxMode = exports.readCodexRolloutTokenUsageForThread = exports.extractCodexTokensUsed = exports.extractGrokRunMetadata = exports.extractCodexThreadId = exports.extractClaudeSessionId = exports.extractCodexRolloutTokenUsage = exports.extractAgentTokenUsage = exports.DEFAULT_WORKER_TOKEN_CAP = exports.DEFAULT_CREATOR_UPLOAD_RECOVERY_TIMEOUT_MS = exports.DEFAULT_CREATOR_AGENT_TIMEOUT_MS = exports.DEFAULT_CODEX_TIMEOUT_MS = exports.buildWorkerChildEnv = exports.buildPlaywrightMcpCommand = exports.buildGrokExecArgs = exports.buildCodexExecArgs = exports.buildCleanRoomChildEnv = exports.buildClaudePermissionSettings = exports.buildClaudeExecArgs = exports.assertWorkerTokenUsageWithinCap = exports.extractZipArchive = void 0;
6
+ exports.WORKER_TASK_WORKSPACE_RETENTION = exports.WORKER_CONTEXT_PROJECT_VALIDATE_NOT_ALLOWED_MESSAGE = exports.WORKER_CONTEXT_COMMAND_NOT_ALLOWED_MESSAGE = exports.WORKER_SESSION_EXPIRED_MESSAGE = exports.WORKSPACE_ARCHIVE_DOWNLOAD_TIMEOUT_MS = exports.runLoggedProcess = exports.readEnvBoolean = exports.readCodexRolloutTokenUsageForThread = exports.extractCodexTokensUsed = exports.extractGrokRunMetadata = exports.extractCodexThreadId = exports.extractClaudeSessionId = exports.extractCodexRolloutTokenUsage = exports.extractAgentTokenUsage = exports.DEFAULT_CREATOR_UPLOAD_RECOVERY_TIMEOUT_MS = exports.DEFAULT_CREATOR_AGENT_TIMEOUT_MS = exports.DEFAULT_CODEX_TIMEOUT_MS = exports.buildWorkerChildEnv = exports.buildPlaywrightMcpCommand = exports.buildGrokExecArgs = exports.buildCodexExecArgs = exports.buildCleanRoomChildEnv = exports.buildClaudePermissionSettings = exports.buildClaudeExecArgs = exports.extractZipArchive = void 0;
7
7
  exports.usesCreatorRecovery = usesCreatorRecovery;
8
8
  exports.buildDeterministicReleaseSmokeArtifact = buildDeterministicReleaseSmokeArtifact;
9
9
  exports.buildCreatorRecoveryPrompt = buildCreatorRecoveryPrompt;
10
+ exports.buildMissingAgentOutcomeRecoveryPrompt = buildMissingAgentOutcomeRecoveryPrompt;
11
+ exports.shouldRecoverMissingAgentOutcome = shouldRecoverMissingAgentOutcome;
12
+ exports.missingAgentOutcomeTerminalReason = missingAgentOutcomeTerminalReason;
13
+ exports.creationTimeoutTerminalReason = creationTimeoutTerminalReason;
10
14
  exports.buildGameEvalRetryPrompt = buildGameEvalRetryPrompt;
11
15
  exports.resolveInitialCreatorProgress = resolveInitialCreatorProgress;
12
16
  exports.allocateWorkerDevPort = allocateWorkerDevPort;
13
17
  exports.isWorkerContextCommandAllowed = isWorkerContextCommandAllowed;
14
18
  exports.resolveWorkerContextCommandBlockMessage = resolveWorkerContextCommandBlockMessage;
15
19
  exports.resolveWorkerExecutionTargetFromRole = resolveWorkerExecutionTargetFromRole;
16
- exports.normalizeWorkerTaskAssignmentV2 = normalizeWorkerTaskAssignmentV2;
17
- exports.normalizeWorkerTaskAssignmentV3 = normalizeWorkerTaskAssignmentV3;
18
- exports.normalizeWorkerClaimResponseV3 = normalizeWorkerClaimResponseV3;
20
+ exports.normalizeWorkerGameTaskAssignment = normalizeWorkerGameTaskAssignment;
21
+ exports.normalizeWorkerClaimResponseV5 = normalizeWorkerClaimResponseV5;
19
22
  exports.nextClaimBackoffMs = nextClaimBackoffMs;
20
23
  exports.claimBackoffDelayMs = claimBackoffDelayMs;
21
24
  exports.acknowledgeWorkerTaskClaim = acknowledgeWorkerTaskClaim;
22
25
  exports.containWorkerTaskRun = containWorkerTaskRun;
23
26
  exports.resolveWorkerHomeDir = resolveWorkerHomeDir;
27
+ exports.configureWorkerFal = configureWorkerFal;
24
28
  exports.pruneWorkerTaskWorkspaces = pruneWorkerTaskWorkspaces;
25
29
  exports.prepareClaimedTaskWorkspace = prepareClaimedTaskWorkspace;
26
30
  exports.buildWorkerTaskContextV2 = buildWorkerTaskContextV2;
27
31
  exports.resolveWorkerInstrumentSurface = resolveWorkerInstrumentSurface;
28
32
  exports.hasAgentTaskUploadedArtifact = hasAgentTaskUploadedArtifact;
29
- exports.appendWorkerTranscriptChunks = appendWorkerTranscriptChunks;
33
+ exports.buildWorkerTranscriptUploadHeaders = buildWorkerTranscriptUploadHeaders;
34
+ exports.describeTaskUploadFailure = describeTaskUploadFailure;
35
+ exports.recordTaskUploadFailure = recordTaskUploadFailure;
36
+ exports.readTaskUploadFailureFile = readTaskUploadFailureFile;
37
+ exports.readTaskUploadResultFile = readTaskUploadResultFile;
30
38
  exports.readTaskNextStepsFile = readTaskNextStepsFile;
31
39
  exports.assertTaskUploadResultMatchesContext = assertTaskUploadResultMatchesContext;
32
- exports.stageAssignmentWorkspaceV2 = stageAssignmentWorkspaceV2;
33
- exports.stageAssignmentWorkspaceV3 = stageAssignmentWorkspaceV3;
40
+ exports.stageAssignmentWorkspace = stageAssignmentWorkspace;
34
41
  exports.assertAssignmentPluginBundleExtractionBounds = assertAssignmentPluginBundleExtractionBounds;
35
42
  exports.stageDevPluginWorkingTree = stageDevPluginWorkingTree;
36
43
  exports.stageAssignmentPluginBundle = stageAssignmentPluginBundle;
@@ -61,16 +68,21 @@ exports.buildGrokPromptJson = buildGrokPromptJson;
61
68
  exports.buildGrokIsolationEnv = buildGrokIsolationEnv;
62
69
  exports.resolveHostGrokAuthPath = resolveHostGrokAuthPath;
63
70
  exports.mergeCreatorRecoveryResults = mergeCreatorRecoveryResults;
71
+ exports.mergeMissingAgentOutcomeRecoveryResults = mergeMissingAgentOutcomeRecoveryResults;
64
72
  exports.buildAgentFailureCode = buildAgentFailureCode;
65
73
  exports.classifyAgentAvailabilityFailureReason = classifyAgentAvailabilityFailureReason;
66
74
  exports.describeAgentFailureForEvent = describeAgentFailureForEvent;
75
+ exports.normalizeWorkerFailureErrorCode = normalizeWorkerFailureErrorCode;
67
76
  exports.createLocalPlaydropShim = createLocalPlaydropShim;
68
77
  exports.parseCodexModelCatalog = parseCodexModelCatalog;
78
+ exports.codexFeatureEnabled = codexFeatureEnabled;
69
79
  exports.parseClaudeModelCatalog = parseClaudeModelCatalog;
70
80
  exports.claudeModelProbeReportsSelection = claudeModelProbeReportsSelection;
71
81
  exports.parseGrokModelCatalog = parseGrokModelCatalog;
72
82
  exports.probeWorkerProviderIndependently = probeWorkerProviderIndependently;
73
83
  exports.describeWorkerPreflightReason = describeWorkerPreflightReason;
84
+ exports.falAuthenticationProbeExitCode = falAuthenticationProbeExitCode;
85
+ exports.probeFalAuthentication = probeFalAuthentication;
74
86
  exports.buildWorkerSetupActions = buildWorkerSetupActions;
75
87
  exports.resolveWorkerStateDir = resolveWorkerStateDir;
76
88
  exports.resolveWorkerSupervisorLockFile = resolveWorkerSupervisorLockFile;
@@ -90,6 +102,8 @@ exports.assertLaunchAgentPlistCompatible = assertLaunchAgentPlistCompatible;
90
102
  exports.launchManagedWorker = launchManagedWorker;
91
103
  exports.showWorkerStatus = showWorkerStatus;
92
104
  exports.setupWorker = setupWorker;
105
+ exports.completeWorkerAgentTaskWithRetry = completeWorkerAgentTaskWithRetry;
106
+ exports.classifyWorkerSupervisorFailure = classifyWorkerSupervisorFailure;
93
107
  exports.assignmentRequiresRegisteredBundle = assignmentRequiresRegisteredBundle;
94
108
  exports.startWorker = startWorker;
95
109
  exports.reportTask = reportTask;
@@ -126,16 +140,21 @@ const config_1 = require("../config");
126
140
  const messages_1 = require("../messages");
127
141
  const output_1 = require("../output");
128
142
  const shellProbe_1 = require("../shellProbe");
143
+ const taskSelection_1 = require("../taskSelection");
129
144
  const review_1 = require("./review");
130
145
  const upload_1 = require("./upload");
146
+ const staticHtml_1 = require("../apps/staticHtml");
131
147
  const runtime_1 = require("./worker/runtime");
132
148
  const instrument_evidence_1 = require("./worker/instrument-evidence");
133
149
  const provider_telemetry_1 = require("./worker/provider-telemetry");
134
150
  const archive_staging_1 = require("./worker/archive-staging");
151
+ const game_server_infra_1 = require("./worker/game-server-infra");
152
+ const game_source_git_1 = require("./worker/game-source-git");
153
+ const generation_1 = require("./worker/generation");
154
+ const e2e_fixtures_1 = require("./worker/e2e-fixtures");
135
155
  var archive_staging_2 = require("./worker/archive-staging");
136
156
  Object.defineProperty(exports, "extractZipArchive", { enumerable: true, get: function () { return archive_staging_2.extractZipArchive; } });
137
157
  var runtime_2 = require("./worker/runtime");
138
- Object.defineProperty(exports, "assertWorkerTokenUsageWithinCap", { enumerable: true, get: function () { return runtime_2.assertWorkerTokenUsageWithinCap; } });
139
158
  Object.defineProperty(exports, "buildClaudeExecArgs", { enumerable: true, get: function () { return runtime_2.buildClaudeExecArgs; } });
140
159
  Object.defineProperty(exports, "buildClaudePermissionSettings", { enumerable: true, get: function () { return runtime_2.buildClaudePermissionSettings; } });
141
160
  Object.defineProperty(exports, "buildCleanRoomChildEnv", { enumerable: true, get: function () { return runtime_2.buildCleanRoomChildEnv; } });
@@ -146,7 +165,6 @@ Object.defineProperty(exports, "buildWorkerChildEnv", { enumerable: true, get: f
146
165
  Object.defineProperty(exports, "DEFAULT_CODEX_TIMEOUT_MS", { enumerable: true, get: function () { return runtime_2.DEFAULT_CODEX_TIMEOUT_MS; } });
147
166
  Object.defineProperty(exports, "DEFAULT_CREATOR_AGENT_TIMEOUT_MS", { enumerable: true, get: function () { return runtime_2.DEFAULT_CREATOR_AGENT_TIMEOUT_MS; } });
148
167
  Object.defineProperty(exports, "DEFAULT_CREATOR_UPLOAD_RECOVERY_TIMEOUT_MS", { enumerable: true, get: function () { return runtime_2.DEFAULT_CREATOR_UPLOAD_RECOVERY_TIMEOUT_MS; } });
149
- Object.defineProperty(exports, "DEFAULT_WORKER_TOKEN_CAP", { enumerable: true, get: function () { return runtime_2.DEFAULT_WORKER_TOKEN_CAP; } });
150
168
  Object.defineProperty(exports, "extractAgentTokenUsage", { enumerable: true, get: function () { return runtime_2.extractAgentTokenUsage; } });
151
169
  Object.defineProperty(exports, "extractCodexRolloutTokenUsage", { enumerable: true, get: function () { return runtime_2.extractCodexRolloutTokenUsage; } });
152
170
  Object.defineProperty(exports, "extractClaudeSessionId", { enumerable: true, get: function () { return runtime_2.extractClaudeSessionId; } });
@@ -154,7 +172,6 @@ Object.defineProperty(exports, "extractCodexThreadId", { enumerable: true, get:
154
172
  Object.defineProperty(exports, "extractGrokRunMetadata", { enumerable: true, get: function () { return runtime_2.extractGrokRunMetadata; } });
155
173
  Object.defineProperty(exports, "extractCodexTokensUsed", { enumerable: true, get: function () { return runtime_2.extractCodexTokensUsed; } });
156
174
  Object.defineProperty(exports, "readCodexRolloutTokenUsageForThread", { enumerable: true, get: function () { return runtime_2.readCodexRolloutTokenUsageForThread; } });
157
- Object.defineProperty(exports, "readCodexSandboxMode", { enumerable: true, get: function () { return runtime_2.readCodexSandboxMode; } });
158
175
  Object.defineProperty(exports, "readEnvBoolean", { enumerable: true, get: function () { return runtime_2.readEnvBoolean; } });
159
176
  Object.defineProperty(exports, "runLoggedProcess", { enumerable: true, get: function () { return runtime_2.runLoggedProcess; } });
160
177
  const DEFAULT_POLL_INTERVAL_MS = 1000;
@@ -171,8 +188,7 @@ const FAIL_REPORT_RETRY_DELAY_MS = 2000;
171
188
  const CLAIM_ACK_RETRY_DELAY_MS = 500;
172
189
  const CLAIM_ACK_MAX_ATTEMPTS = 4;
173
190
  exports.WORKSPACE_ARCHIVE_DOWNLOAD_TIMEOUT_MS = 5 * 60000;
174
- const WORKER_TRANSCRIPT_CHUNK_BATCH_SIZE = 100;
175
- const WORKER_WRAPPER_CONTRACT_VERSION = 3;
191
+ const WORKER_WRAPPER_CONTRACT_VERSION = 5;
176
192
  const WORKER_SUPPORTED_KINDS = [
177
193
  "NEW_GAME",
178
194
  "STATIC_GAME",
@@ -181,6 +197,14 @@ const WORKER_SUPPORTED_KINDS = [
181
197
  "GAME_REVIEW",
182
198
  "GAME_EVAL",
183
199
  "RELEASE_SMOKE",
200
+ "TEXT",
201
+ "JSON",
202
+ "IMAGE",
203
+ "MODEL_3D",
204
+ "VIDEO",
205
+ "SFX",
206
+ "SPEECH",
207
+ "MUSIC",
184
208
  ];
185
209
  const GAME_CREATION_TASK_KINDS = new Set(["NEW_GAME", "STATIC_GAME", "REMIX_GAME", "GAME_UPDATE"]);
186
210
  const AGENT_MANAGED_UPLOAD_TASK_KINDS = new Set(["NEW_GAME", "REMIX_GAME", "GAME_UPDATE"]);
@@ -222,6 +246,46 @@ function buildCreatorRecoveryPrompt(input) {
222
246
  `Original request: ${prompt}`,
223
247
  ].join("\n\n");
224
248
  }
249
+ function buildMissingAgentOutcomeRecoveryPrompt(kind) {
250
+ if (kind === "STATIC_GAME" || kind === "RELEASE_SMOKE") {
251
+ throw new Error(`agent_task_outcome_recovery_unsupported:${kind}`);
252
+ }
253
+ return [
254
+ "You stopped without reporting task success or failure.",
255
+ "Please read the task-worker skill, resume your work in this same task and conversation, and report failure or success before you exit.",
256
+ ].join(" ");
257
+ }
258
+ function shouldRecoverMissingAgentOutcome(input) {
259
+ return (input.kind !== "STATIC_GAME" &&
260
+ input.kind !== "RELEASE_SMOKE" &&
261
+ input.taskStatus === "RUNNING" &&
262
+ !input.completionReported &&
263
+ input.result.exitCode === 0 &&
264
+ input.result.signal === null &&
265
+ !input.result.timedOut);
266
+ }
267
+ function missingAgentOutcomeTerminalReason() {
268
+ const code = "agent_stopped_without_outcome";
269
+ return {
270
+ kind: "failure",
271
+ category: "agent_failure",
272
+ code,
273
+ message: "The agent stopped without being able to upload the game. Please retry.",
274
+ retryable: true,
275
+ source: "worker",
276
+ };
277
+ }
278
+ function creationTimeoutTerminalReason() {
279
+ const code = "agent_task_creation_timeout";
280
+ return {
281
+ kind: "failure",
282
+ category: "provider_timeout",
283
+ code,
284
+ message: "The task took too long and couldn't finish on time. Retry it, or clear it and ask for a simpler change.",
285
+ retryable: true,
286
+ source: "worker",
287
+ };
288
+ }
225
289
  function buildGameEvalRetryPrompt(prompt) {
226
290
  return [
227
291
  prompt,
@@ -263,12 +327,36 @@ const WORKER_CONTEXT_ALLOWED_COMMANDS = [
263
327
  ["documentation", "read"],
264
328
  ["help"],
265
329
  ["ai", "create"],
266
- ["ai", "jobs", "browse"],
267
- ["ai", "jobs", "detail"],
330
+ ["ai", "tasks", "browse"],
331
+ ["chat", "channels"],
332
+ ["chat", "inbox"],
333
+ ["chat", "conversation"],
334
+ ["chat", "read"],
335
+ ["chat", "thread"],
336
+ ["chat", "send"],
337
+ ["chat", "reply"],
338
+ ["chat", "dm"],
339
+ ["chat", "react"],
340
+ ["chat", "unreact"],
341
+ ["ai", "tasks", "detail"],
268
342
  ["ai", "generations"],
269
343
  ];
270
344
  exports.WORKER_CONTEXT_COMMAND_NOT_ALLOWED_MESSAGE = "worker_context_command_not_allowed: inside a PlayDrop worker task only task progress, task asset-runtime, task claim-slug, task upload/done/fail, assigned app scaffolding, project build/dev/check/capture, read-only help/catalogue/documentation lookup, and PlayDrop AI generation are permitted.";
271
345
  exports.WORKER_CONTEXT_PROJECT_VALIDATE_NOT_ALLOWED_MESSAGE = 'worker_context_project_validate_not_allowed: "playdrop project validate" is not available inside a PlayDrop worker task. Run "playdrop task upload --preflight-only" as the sole pre-upload validation gate.';
346
+ const TASK_UPLOAD_FAILURE_CATEGORIES = new Set([
347
+ "provider_capacity",
348
+ "provider_connection",
349
+ "provider_timeout",
350
+ "instrument_error",
351
+ "upload_validation",
352
+ "agent_failure",
353
+ ]);
354
+ const TASK_UPLOAD_FAILURE_SOURCES = new Set([
355
+ "worker",
356
+ "agent",
357
+ "provider",
358
+ "instrument",
359
+ ]);
272
360
  function resolveInitialCreatorProgress(kind, creationRecovery = false) {
273
361
  if (creationRecovery) {
274
362
  return null;
@@ -395,7 +483,13 @@ function isAssignedAppScaffoldCommandAllowed(argv) {
395
483
  return normalizeRemixFlagValue(argv) === context.remixSourceRef;
396
484
  }
397
485
  function resolveWorkerExecutionTargetFromRole(role) {
398
- return role === "ADMIN" ? "FIRST_PARTY" : "PERSONAL";
486
+ if (role === "CLOUD_WORKER") {
487
+ return "FIRST_PARTY";
488
+ }
489
+ if (role === "CREATOR") {
490
+ return "PERSONAL";
491
+ }
492
+ throw new Error(`worker_role_not_supported:${String(role ?? "unknown")}`);
399
493
  }
400
494
  function normalizeAssignmentWorkspaceFolder(value) {
401
495
  const folderName = typeof value === "string" ? value.trim() : "";
@@ -439,138 +533,6 @@ function normalizeAssignmentContent(input) {
439
533
  }
440
534
  return { path: relativePath, contentBase64, sha256, sizeBytes, buffer };
441
535
  }
442
- function normalizeWorkerTaskAssignmentV2(claim) {
443
- if (claim.protocolVersion !== 2) {
444
- throw new Error("agent_task_claim_protocol_version_mismatch");
445
- }
446
- if (claim.action !== "run") {
447
- return null;
448
- }
449
- const assignment = claim.assignment;
450
- const taskId = parsePositiveInteger(assignment.task.id, "agent_task_assignment_task_id_missing");
451
- const taskToken = typeof assignment.task.token === "string" ? assignment.task.token.trim() : "";
452
- const taskType = typeof assignment.task.type === "string" ? assignment.task.type.trim() : "";
453
- const attempt = parsePositiveInteger(assignment.task.attempt, "agent_task_assignment_attempt_missing");
454
- const prompt = typeof assignment.request.prompt === "string" ? assignment.request.prompt : "";
455
- const model = typeof assignment.agent.model === "string" ? assignment.agent.model.trim() : "";
456
- if (!taskToken || !taskType || !prompt.trim() || !model) {
457
- throw new Error("agent_task_assignment_invalid");
458
- }
459
- const workspacePaths = new Set();
460
- const files = assignment.workspace.files.map((file) => {
461
- const normalized = normalizeAssignmentContent({
462
- path: file.path,
463
- contentBase64: file.contentBase64,
464
- sha256: file.sha256,
465
- sizeBytes: file.sizeBytes,
466
- code: "agent_task_assignment_workspace_file",
467
- });
468
- const contentType = typeof file.contentType === "string" ? file.contentType.trim() : "";
469
- if (!contentType) {
470
- throw new Error("agent_task_assignment_workspace_file_content_type_missing");
471
- }
472
- if (workspacePaths.has(normalized.path)) {
473
- throw new Error("agent_task_assignment_workspace_path_duplicate");
474
- }
475
- workspacePaths.add(normalized.path);
476
- return {
477
- path: normalized.path,
478
- sha256: normalized.sha256,
479
- sizeBytes: normalized.sizeBytes,
480
- contentType,
481
- contentBase64: normalized.contentBase64,
482
- };
483
- });
484
- const archives = assignment.workspace.archives.map((archive) => {
485
- if (archive.format !== "zip") {
486
- throw new Error("agent_task_assignment_workspace_archive_format_invalid");
487
- }
488
- const normalized = normalizeAssignmentContent({
489
- path: archive.path,
490
- contentBase64: archive.contentBase64,
491
- sha256: archive.sha256,
492
- sizeBytes: archive.sizeBytes,
493
- code: "agent_task_assignment_workspace_archive",
494
- });
495
- if (workspacePaths.has(normalized.path)) {
496
- throw new Error("agent_task_assignment_workspace_path_duplicate");
497
- }
498
- workspacePaths.add(normalized.path);
499
- return {
500
- path: normalized.path,
501
- format: "zip",
502
- sha256: normalized.sha256,
503
- sizeBytes: normalized.sizeBytes,
504
- contentBase64: normalized.contentBase64,
505
- };
506
- });
507
- const fileByPath = new Map(files.map((file) => [file.path, file]));
508
- const attachmentIds = new Set();
509
- const attachments = assignment.request.attachments.map((attachment) => {
510
- const id = typeof attachment.id === "string" ? attachment.id.trim() : "";
511
- const attachmentPath = normalizeAssignmentWorkspaceRelativePath(attachment.path);
512
- const fileName = typeof attachment.fileName === "string" ? attachment.fileName.trim() : "";
513
- const contentType = typeof attachment.contentType === "string" ? attachment.contentType.trim() : "";
514
- const sha256 = typeof attachment.sha256 === "string" ? attachment.sha256.trim() : "";
515
- const sizeBytes = Number(attachment.sizeBytes);
516
- const file = fileByPath.get(attachmentPath);
517
- if (!id || attachmentIds.has(id) || !fileName || !contentType || !file) {
518
- throw new Error("agent_task_assignment_attachment_invalid");
519
- }
520
- if (sha256 !== file.sha256 || sizeBytes !== file.sizeBytes || contentType !== file.contentType) {
521
- throw new Error("agent_task_assignment_attachment_metadata_mismatch");
522
- }
523
- attachmentIds.add(id);
524
- return {
525
- id,
526
- path: attachmentPath,
527
- fileName,
528
- contentType,
529
- sizeBytes,
530
- sha256,
531
- description: typeof attachment.description === "string" ? attachment.description : null,
532
- };
533
- });
534
- for (const attachment of attachments) {
535
- if (!fileByPath.has(attachment.path)) {
536
- throw new Error("agent_task_assignment_attachment_file_missing");
537
- }
538
- }
539
- const browser = assignment.agent.browser;
540
- if (browser !== "NONE" && browser !== "CHROME" && browser !== "PLAYWRIGHT") {
541
- throw new Error("agent_task_assignment_browser_invalid");
542
- }
543
- const reasoningEffort = assignment.agent.reasoningEffort;
544
- if (reasoningEffort !== null && typeof reasoningEffort !== "string") {
545
- throw new Error("agent_task_assignment_reasoning_effort_invalid");
546
- }
547
- return {
548
- task: {
549
- id: taskId,
550
- token: taskToken,
551
- type: taskType,
552
- executionTarget: normalizeAssignmentTarget(assignment.task.executionTarget),
553
- attempt,
554
- metadata: assignment.task.metadata,
555
- },
556
- workspace: {
557
- folderName: normalizeAssignmentWorkspaceFolder(assignment.workspace.folderName),
558
- archives,
559
- files,
560
- },
561
- plugin: assignment.plugin,
562
- request: {
563
- prompt,
564
- attachments,
565
- },
566
- agent: {
567
- runtime: normalizeAssignmentAgent(assignment.agent.runtime),
568
- model,
569
- reasoningEffort,
570
- browser,
571
- },
572
- };
573
- }
574
536
  function requireClaimRecord(value, code = "agent_task_claim_response_malformed") {
575
537
  if (!value || typeof value !== "object" || Array.isArray(value)) {
576
538
  throw new Error(code);
@@ -616,33 +578,168 @@ function normalizeWorkspaceArchiveDownloadUrl(value) {
616
578
  }
617
579
  return parsed.toString();
618
580
  }
619
- function normalizeWorkerTaskAssignmentV3(claim) {
620
- const response = requireClaimRecord(claim);
621
- if (response.protocolVersion !== 3) {
622
- throw new Error("agent_task_claim_protocol_version_mismatch");
623
- }
624
- if (response.action === "idle" || response.action === "quiesce_for_update") {
625
- if (response.assignment !== null || response.lease !== null) {
626
- throw new Error("agent_task_claim_response_malformed");
581
+ function normalizeGameSourceAssignment(value) {
582
+ if (value === undefined)
583
+ return undefined;
584
+ const source = requireClaimRecord(value, "agent_task_assignment_game_source_invalid");
585
+ if (source.version !== 2 || !["NEW", "STATIC", "REMIX", "UPDATE"].includes(String(source.mode))) {
586
+ throw new Error("agent_task_assignment_game_source_invalid");
587
+ }
588
+ const normalizeVersions = (entries) => {
589
+ if (!Array.isArray(entries))
590
+ throw new Error("agent_task_assignment_game_source_invalid");
591
+ return entries.map((entry) => {
592
+ const item = requireClaimRecord(entry, "agent_task_assignment_game_source_invalid");
593
+ const versionId = parsePositiveInteger(item.versionId, "agent_task_assignment_game_source_invalid");
594
+ const major = Number(item.major);
595
+ const minor = Number(item.minor);
596
+ const patch = Number(item.patch);
597
+ const commitSha = String(item.commitSha ?? "").trim().toLowerCase();
598
+ if ([major, minor, patch].some((part) => !Number.isSafeInteger(part) || part < 0) || !/^[0-9a-f]{40}$/.test(commitSha)) {
599
+ throw new Error("agent_task_assignment_game_source_invalid");
600
+ }
601
+ return { versionId, major, minor, patch, commitSha };
602
+ });
603
+ };
604
+ const authoritativeVersions = normalizeVersions(source.authoritativeVersions);
605
+ if (!Array.isArray(source.transcripts)) {
606
+ throw new Error("agent_task_assignment_game_source_invalid");
607
+ }
608
+ const transcripts = source.transcripts.map((entry) => {
609
+ const item = requireClaimRecord(entry, "agent_task_assignment_game_source_invalid");
610
+ const taskId = parsePositiveInteger(item.taskId, "agent_task_assignment_game_source_invalid");
611
+ const attempt = parsePositiveInteger(item.attempt, "agent_task_assignment_game_source_invalid");
612
+ const sha256 = String(item.sha256 ?? "").trim().toLowerCase();
613
+ const bytes = Number(item.bytes);
614
+ if (!/^[0-9a-f]{64}$/.test(sha256) || !Number.isSafeInteger(bytes) || bytes < 0) {
615
+ throw new Error("agent_task_assignment_game_source_invalid");
627
616
  }
628
- return null;
629
- }
630
- if (response.action !== "run") {
631
- throw new Error("agent_task_claim_response_malformed");
632
- }
633
- const assignment = requireClaimRecord(response.assignment, "agent_task_assignment_invalid");
617
+ return {
618
+ taskId,
619
+ attempt,
620
+ downloadUrl: normalizeWorkspaceArchiveDownloadUrl(item.downloadUrl),
621
+ sha256,
622
+ bytes,
623
+ };
624
+ });
625
+ const listingObject = source.listingSync === null
626
+ ? null
627
+ : requireClaimRecord(source.listingSync, "agent_task_assignment_game_source_invalid");
628
+ const listingSync = listingObject
629
+ ? (() => {
630
+ const stringOrNull = (value) => {
631
+ if (value === null)
632
+ return null;
633
+ if (typeof value !== "string")
634
+ throw new Error("agent_task_assignment_game_source_invalid");
635
+ return value;
636
+ };
637
+ const stringArray = (value) => {
638
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
639
+ throw new Error("agent_task_assignment_game_source_invalid");
640
+ }
641
+ return value;
642
+ };
643
+ if (!Array.isArray(listingObject.files) || typeof listingObject.updatedAt !== "string" || !Number.isFinite(Date.parse(listingObject.updatedAt))) {
644
+ throw new Error("agent_task_assignment_game_source_invalid");
645
+ }
646
+ const files = listingObject.files.map((entry) => {
647
+ const item = requireClaimRecord(entry, "agent_task_assignment_game_source_invalid");
648
+ const filePath = typeof item.path === "string" ? item.path.trim() : "";
649
+ const fileSha256 = String(item.sha256 ?? "").trim().toLowerCase();
650
+ const fileBytes = Number(item.bytes);
651
+ if (!filePath.startsWith("listing/") || !/^[0-9a-f]{64}$/.test(fileSha256) || !Number.isSafeInteger(fileBytes) || fileBytes < 0) {
652
+ throw new Error("agent_task_assignment_game_source_invalid");
653
+ }
654
+ return {
655
+ path: filePath,
656
+ downloadUrl: normalizeWorkspaceArchiveDownloadUrl(item.downloadUrl),
657
+ sha256: fileSha256,
658
+ bytes: fileBytes,
659
+ };
660
+ });
661
+ const version = String(listingObject.version ?? "").trim();
662
+ if (!/^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$/.test(version)) {
663
+ throw new Error("agent_task_assignment_game_source_invalid");
664
+ }
665
+ return {
666
+ versionId: parsePositiveInteger(listingObject.versionId, "agent_task_assignment_game_source_invalid"),
667
+ version,
668
+ displayName: stringOrNull(listingObject.displayName),
669
+ subtitle: stringOrNull(listingObject.subtitle),
670
+ description: stringOrNull(listingObject.description),
671
+ iconPath: stringOrNull(listingObject.iconPath),
672
+ heroPortraitPath: stringOrNull(listingObject.heroPortraitPath),
673
+ heroLandscapePath: stringOrNull(listingObject.heroLandscapePath),
674
+ socialLandscapePath: stringOrNull(listingObject.socialLandscapePath),
675
+ screenshotsPortrait: stringArray(listingObject.screenshotsPortrait),
676
+ screenshotsLandscape: stringArray(listingObject.screenshotsLandscape),
677
+ videosPortrait: stringArray(listingObject.videosPortrait),
678
+ videosLandscape: stringArray(listingObject.videosLandscape),
679
+ updatedAt: listingObject.updatedAt,
680
+ files,
681
+ };
682
+ })()
683
+ : null;
684
+ const selectedBaseVersionId = source.selectedBaseVersionId === null
685
+ ? null
686
+ : parsePositiveInteger(source.selectedBaseVersionId, "agent_task_assignment_game_source_invalid");
687
+ const baseObject = source.baseRepository === null
688
+ ? null
689
+ : requireClaimRecord(source.baseRepository, "agent_task_assignment_game_source_invalid");
690
+ const baseRepository = baseObject
691
+ ? {
692
+ appId: parsePositiveInteger(baseObject.appId, "agent_task_assignment_game_source_invalid"),
693
+ repoGeneration: String(baseObject.repoGeneration ?? "").trim(),
694
+ bundleDownloadUrl: normalizeWorkspaceArchiveDownloadUrl(baseObject.bundleDownloadUrl),
695
+ expectedHeadSha: String(baseObject.expectedHeadSha ?? "").trim().toLowerCase(),
696
+ selectedCommitSha: String(baseObject.selectedCommitSha ?? "").trim().toLowerCase(),
697
+ }
698
+ : null;
699
+ const targetObject = requireClaimRecord(source.targetRepository, "agent_task_assignment_game_source_invalid");
700
+ const targetRepository = {
701
+ appId: targetObject.appId === null
702
+ ? null
703
+ : parsePositiveInteger(targetObject.appId, "agent_task_assignment_game_source_invalid"),
704
+ repoGeneration: targetObject.repoGeneration === null ? null : String(targetObject.repoGeneration).trim(),
705
+ expectedHeadSha: targetObject.expectedHeadSha === null ? null : String(targetObject.expectedHeadSha).trim().toLowerCase(),
706
+ };
707
+ if ((baseRepository && (!/^[1-9][0-9]*$/.test(baseRepository.repoGeneration) ||
708
+ !/^[0-9a-f]{40}$/.test(baseRepository.expectedHeadSha) ||
709
+ !/^[0-9a-f]{40}$/.test(baseRepository.selectedCommitSha))) ||
710
+ ((source.mode === "UPDATE" || source.mode === "REMIX") && baseRepository === null) ||
711
+ ((source.mode === "NEW" || source.mode === "STATIC") && baseRepository !== null) ||
712
+ (targetRepository.repoGeneration !== null && !/^[1-9][0-9]*$/.test(targetRepository.repoGeneration)) ||
713
+ (targetRepository.expectedHeadSha !== null && !/^[0-9a-f]{40}$/.test(targetRepository.expectedHeadSha)) ||
714
+ ((targetRepository.repoGeneration === null) !== (targetRepository.expectedHeadSha === null)))
715
+ throw new Error("agent_task_assignment_game_source_invalid");
716
+ return {
717
+ version: 2,
718
+ mode: source.mode,
719
+ baseRepository,
720
+ targetRepository,
721
+ authoritativeVersions,
722
+ selectedBaseVersionId,
723
+ transcripts,
724
+ listingSync,
725
+ };
726
+ }
727
+ function normalizeWorkerGameTaskAssignment(value) {
728
+ const assignment = requireClaimRecord(value, "agent_task_assignment_invalid");
634
729
  const task = requireClaimRecord(assignment.task, "agent_task_assignment_invalid");
635
730
  const workspace = requireClaimRecord(assignment.workspace, "agent_task_assignment_workspace_missing");
636
731
  const request = requireClaimRecord(assignment.request, "agent_task_assignment_invalid");
637
732
  const agent = requireClaimRecord(assignment.agent, "agent_task_assignment_invalid");
638
733
  const taskId = parsePositiveInteger(task.id, "agent_task_assignment_task_id_missing");
639
734
  const taskToken = typeof task.token === "string" ? task.token.trim() : "";
735
+ const taskAccessToken = typeof task.accessToken === "string" ? task.accessToken.trim() : "";
640
736
  const taskType = typeof task.type === "string" ? task.type.trim() : "";
641
737
  const attempt = parsePositiveInteger(task.attempt, "agent_task_assignment_attempt_missing");
642
738
  const prompt = typeof request.prompt === "string" ? request.prompt : "";
643
739
  const model = typeof agent.model === "string" ? agent.model.trim() : "";
644
740
  const metadata = requireClaimRecord(task.metadata, "agent_task_assignment_metadata_invalid");
645
- if (!taskToken || !taskType || !prompt.trim() || !model) {
741
+ const executionTarget = normalizeAssignmentTarget(task.executionTarget);
742
+ if (!taskToken || !taskType || !prompt.trim() || !model || (executionTarget === "FIRST_PARTY" && !taskAccessToken)) {
646
743
  throw new Error("agent_task_assignment_invalid");
647
744
  }
648
745
  if (!Array.isArray(workspace.files) || !Array.isArray(workspace.archives) || !Array.isArray(request.attachments)) {
@@ -678,7 +775,11 @@ function normalizeWorkerTaskAssignmentV3(claim) {
678
775
  const archive = requireClaimRecord(value, "agent_task_assignment_workspace_archive_invalid");
679
776
  const archivePath = normalizeAssignmentWorkspaceRelativePath(archive.path);
680
777
  const sizeBytes = Number(archive.sizeBytes);
681
- if (archive.format !== "zip" || !Number.isInteger(sizeBytes) || sizeBytes <= 0) {
778
+ const sha256 = typeof archive.sha256 === "string" ? archive.sha256.trim().toLowerCase() : "";
779
+ if (archive.format !== "zip" ||
780
+ !Number.isInteger(sizeBytes) ||
781
+ sizeBytes <= 0 ||
782
+ !/^[0-9a-f]{64}$/.test(sha256)) {
682
783
  throw new Error("agent_task_assignment_workspace_archive_metadata_invalid");
683
784
  }
684
785
  if (workspacePaths.has(archivePath)) {
@@ -690,6 +791,7 @@ function normalizeWorkerTaskAssignmentV3(claim) {
690
791
  format: "zip",
691
792
  downloadUrl: normalizeWorkspaceArchiveDownloadUrl(archive.downloadUrl),
692
793
  sizeBytes,
794
+ sha256,
693
795
  };
694
796
  });
695
797
  const fileByPath = new Map(files.map((file) => [file.path, file]));
@@ -728,12 +830,17 @@ function normalizeWorkerTaskAssignmentV3(claim) {
728
830
  if (reasoningEffort !== null && typeof reasoningEffort !== "string") {
729
831
  throw new Error("agent_task_assignment_reasoning_effort_invalid");
730
832
  }
833
+ const gameSource = normalizeGameSourceAssignment(assignment.gameSource);
834
+ if (gameSource && executionTarget !== "FIRST_PARTY") {
835
+ throw new Error("agent_task_assignment_game_source_invalid");
836
+ }
731
837
  return {
732
838
  task: {
733
839
  id: taskId,
734
840
  token: taskToken,
841
+ ...(taskAccessToken ? { accessToken: taskAccessToken } : {}),
735
842
  type: taskType,
736
- executionTarget: normalizeAssignmentTarget(task.executionTarget),
843
+ executionTarget,
737
844
  attempt,
738
845
  metadata,
739
846
  },
@@ -750,38 +857,103 @@ function normalizeWorkerTaskAssignmentV3(claim) {
750
857
  reasoningEffort,
751
858
  browser,
752
859
  },
860
+ gameSource,
753
861
  };
754
862
  }
755
- function normalizeWorkerClaimResponseV3(value) {
863
+ function normalizeWorkerClaimResponseV5(value) {
756
864
  const response = requireClaimRecord(value);
757
- const assignment = normalizeWorkerTaskAssignmentV3(response);
865
+ if (response.protocolVersion !== 5)
866
+ throw new Error("agent_task_claim_protocol_version_mismatch");
758
867
  const updatePolicy = response.updatePolicy;
759
868
  if (updatePolicy !== null && (typeof updatePolicy !== "object" || Array.isArray(updatePolicy))) {
760
869
  throw new Error("agent_task_claim_response_malformed");
761
870
  }
762
871
  if (response.action === "idle" || response.action === "quiesce_for_update") {
872
+ if (response.assignment !== null || response.lease !== null)
873
+ throw new Error("agent_task_claim_response_malformed");
763
874
  return {
764
- protocolVersion: 3,
875
+ protocolVersion: 5,
765
876
  action: response.action,
766
877
  assignment: null,
767
878
  lease: null,
768
879
  updatePolicy: updatePolicy,
769
880
  };
770
881
  }
771
- if (!assignment) {
772
- throw new Error("agent_task_claim_missing_assignment");
882
+ if (response.action !== "run")
883
+ throw new Error("agent_task_claim_response_malformed");
884
+ const rawAssignment = requireClaimRecord(response.assignment, "agent_task_assignment_invalid");
885
+ let assignment;
886
+ if (rawAssignment.operation === "GAME") {
887
+ const { operation: _operation, ...gameAssignment } = rawAssignment;
888
+ const game = normalizeWorkerGameTaskAssignment(gameAssignment);
889
+ assignment = { operation: "GAME", ...game };
890
+ }
891
+ else if (rawAssignment.operation === "SCREENING") {
892
+ const task = requireClaimRecord(rawAssignment.task, "agent_task_assignment_invalid");
893
+ const request = requireClaimRecord(rawAssignment.request, "agent_task_assignment_invalid");
894
+ const execution = requireClaimRecord(rawAssignment.execution, "agent_task_assignment_invalid");
895
+ const taskId = parsePositiveInteger(task.id, "agent_task_assignment_task_id_missing");
896
+ const token = typeof task.token === "string" ? task.token.trim() : "";
897
+ const accessToken = typeof task.accessToken === "string" ? task.accessToken.trim() : "";
898
+ const taskType = typeof task.type === "string" ? task.type.trim().toUpperCase() : "";
899
+ const prompt = typeof request.prompt === "string" ? request.prompt : "";
900
+ if (!token ||
901
+ !accessToken ||
902
+ task.attempt !== 0 ||
903
+ !["NEW_GAME", "STATIC_GAME", "REMIX_GAME", "GAME_UPDATE"].includes(taskType) ||
904
+ !prompt.trim() ||
905
+ !Array.isArray(request.attachments) ||
906
+ !request.outputSchema ||
907
+ typeof request.outputSchema !== "object" ||
908
+ !Array.isArray(rawAssignment.inputs) ||
909
+ execution.provider !== "CODEX" ||
910
+ execution.model !== "gpt-5.6-luna" ||
911
+ execution.reasoningEffort !== "high" ||
912
+ execution.sandbox !== "read-only") {
913
+ throw new Error("agent_task_assignment_invalid");
914
+ }
915
+ assignment = rawAssignment;
916
+ assignment.task.id = taskId;
917
+ }
918
+ else if (rawAssignment.operation === "GENERATION") {
919
+ const task = requireClaimRecord(rawAssignment.task, "agent_task_assignment_invalid");
920
+ const request = requireClaimRecord(rawAssignment.request, "agent_task_assignment_invalid");
921
+ const execution = requireClaimRecord(rawAssignment.execution, "agent_task_assignment_invalid");
922
+ const taskId = parsePositiveInteger(task.id, "agent_task_assignment_task_id_missing");
923
+ const attempt = parsePositiveInteger(task.attempt, "agent_task_assignment_attempt_missing");
924
+ const token = typeof task.token === "string" ? task.token.trim() : "";
925
+ const accessToken = typeof task.accessToken === "string" ? task.accessToken.trim() : "";
926
+ const taskType = typeof task.type === "string" ? task.type.trim().toUpperCase() : "";
927
+ const requestKind = typeof request.kind === "string" ? request.kind.trim().toUpperCase() : "";
928
+ const provider = execution.provider;
929
+ const model = typeof execution.model === "string" ? execution.model.trim() : "";
930
+ if (!token ||
931
+ !accessToken ||
932
+ !["TEXT", "JSON", "IMAGE", "MODEL_3D", "VIDEO", "SFX", "SPEECH", "MUSIC"].includes(taskType) ||
933
+ requestKind !== taskType ||
934
+ (provider !== "CODEX" && provider !== "FAL") ||
935
+ !model ||
936
+ !Array.isArray(rawAssignment.inputs)) {
937
+ throw new Error("agent_task_assignment_invalid");
938
+ }
939
+ assignment = rawAssignment;
940
+ assignment.task.id = taskId;
941
+ assignment.task.attempt = attempt;
942
+ }
943
+ else {
944
+ throw new Error("agent_task_assignment_operation_invalid");
773
945
  }
774
946
  const lease = requireClaimRecord(response.lease, "agent_task_claim_missing_lease_token");
775
- const token = typeof lease.token === "string" ? lease.token.trim() : "";
947
+ const leaseToken = typeof lease.token === "string" ? lease.token.trim() : "";
776
948
  const expiresAt = typeof lease.expiresAt === "string" ? lease.expiresAt.trim() : "";
777
- if (!token || !expiresAt || Number.isNaN(Date.parse(expiresAt))) {
949
+ if (!leaseToken || !expiresAt || Number.isNaN(Date.parse(expiresAt))) {
778
950
  throw new Error("agent_task_claim_missing_lease_token");
779
951
  }
780
952
  return {
781
- protocolVersion: 3,
953
+ protocolVersion: 5,
782
954
  action: "run",
783
955
  assignment,
784
- lease: { token, expiresAt },
956
+ lease: { token: leaseToken, expiresAt },
785
957
  updatePolicy: updatePolicy,
786
958
  };
787
959
  }
@@ -837,6 +1009,10 @@ function resolveWorkerHomeDir() {
837
1009
  }
838
1010
  return node_path_1.default.join(node_os_1.default.homedir(), "PlayDropWorker");
839
1011
  }
1012
+ async function configureWorkerFal() {
1013
+ await (0, generation_1.configureFalCredential)(resolveWorkerHomeDir());
1014
+ (0, output_1.printSuccess)("Fal is configured for this PlayDrop worker.");
1015
+ }
840
1016
  function workerStateFilePath() {
841
1017
  return node_path_1.default.join(resolveWorkerStateDir(), "current-task.json");
842
1018
  }
@@ -853,13 +1029,24 @@ async function pruneWorkerTaskWorkspaces(incomingWorkspaceDir, retention = expor
853
1029
  return [];
854
1030
  }
855
1031
  const incoming = node_path_1.default.resolve(incomingWorkspaceDir);
1032
+ const retainedCutoff = Date.now() - 7 * 24 * 60 * 60 * 1000;
856
1033
  const entries = (0, node_fs_1.readdirSync)(tasksRoot, { withFileTypes: true })
857
1034
  .filter((entry) => entry.isDirectory())
858
1035
  .map((entry) => node_path_1.default.join(tasksRoot, entry.name))
859
1036
  .filter((dir) => dir !== incoming)
860
- .map((dir) => ({ dir, mtimeMs: (0, node_fs_1.statSync)(dir).mtimeMs }))
1037
+ .map((dir) => {
1038
+ const marker = node_path_1.default.join(dir, ".playdrop", "game-source-retained.json");
1039
+ const markerMtime = (0, node_fs_1.existsSync)(marker) ? (0, node_fs_1.statSync)(marker).mtimeMs : null;
1040
+ return {
1041
+ dir,
1042
+ mtimeMs: (0, node_fs_1.statSync)(dir).mtimeMs,
1043
+ retainedGameSource: markerMtime !== null && markerMtime >= retainedCutoff,
1044
+ };
1045
+ })
861
1046
  .sort((a, b) => b.mtimeMs - a.mtimeMs);
862
- const stale = entries.slice(Math.max(0, retention - 1));
1047
+ const stale = entries
1048
+ .filter((entry) => !entry.retainedGameSource)
1049
+ .slice(Math.max(0, retention - 1));
863
1050
  const removed = [];
864
1051
  for (const entry of stale) {
865
1052
  await (0, promises_1.rm)(entry.dir, { recursive: true, force: true });
@@ -889,11 +1076,15 @@ async function prepareClaimedTaskWorkspace(input) {
889
1076
  (0, promises_1.rm)(node_path_1.default.join(input.workspaceDir, ".playdrop", "task-events"), { recursive: true, force: true }),
890
1077
  (0, promises_1.rm)(node_path_1.default.join(input.workspaceDir, ".playdrop", "task-upload.lock"), { force: true }),
891
1078
  (0, promises_1.rm)(node_path_1.default.join(input.workspaceDir, ".playdrop", "task-upload-result.json"), { force: true }),
1079
+ (0, promises_1.rm)(node_path_1.default.join(input.workspaceDir, ".playdrop", "task-upload-failure.json"), { force: true }),
892
1080
  ...retainedProjectBindings.map((file) => (0, promises_1.rm)(file, { force: true })),
893
1081
  ]);
894
1082
  return { resumed: true };
895
1083
  }
896
1084
  function buildWorkerTaskContext(input) {
1085
+ if (input.target === "FIRST_PARTY" && !input.taskAccessToken?.trim()) {
1086
+ throw new Error("agent_task_access_token_missing");
1087
+ }
897
1088
  const playdrop = input.metadata.playdrop;
898
1089
  if (!playdrop || typeof playdrop !== "object" || Array.isArray(playdrop)) {
899
1090
  throw new Error("agent_task_context_playdrop_metadata_missing");
@@ -937,6 +1128,7 @@ function buildWorkerTaskContext(input) {
937
1128
  taskId: input.taskId,
938
1129
  attempt: input.attempt,
939
1130
  taskToken: input.taskToken,
1131
+ taskAccessToken: input.taskAccessToken?.trim() || null,
940
1132
  taskType: input.taskType,
941
1133
  metadata: input.metadata,
942
1134
  kind,
@@ -951,6 +1143,7 @@ function buildWorkerTaskContext(input) {
951
1143
  remixSourceRef,
952
1144
  allowedTemplateKeys,
953
1145
  reviewAppVersionId,
1146
+ gameSourceGit: input.gameSourceGit === true,
954
1147
  };
955
1148
  }
956
1149
  function buildWorkerTaskContextV2(assignment) {
@@ -958,10 +1151,12 @@ function buildWorkerTaskContextV2(assignment) {
958
1151
  taskId: assignment.task.id,
959
1152
  attempt: assignment.task.attempt,
960
1153
  taskToken: assignment.task.token,
1154
+ taskAccessToken: assignment.task.accessToken ?? null,
961
1155
  taskType: assignment.task.type,
962
1156
  metadata: assignment.task.metadata,
963
1157
  prompt: assignment.request.prompt,
964
1158
  target: assignment.task.executionTarget,
1159
+ gameSourceGit: Boolean("gameSource" in assignment && assignment.gameSource),
965
1160
  });
966
1161
  }
967
1162
  function normalizeWorkerInstrumentSurface(value) {
@@ -1040,9 +1235,30 @@ function workerTaskContextPath(workspaceDir) {
1040
1235
  function workerTaskUploadResultPath(workspaceDir) {
1041
1236
  return node_path_1.default.join(workspaceDir, ".playdrop", "task-upload-result.json");
1042
1237
  }
1238
+ function workerTaskUploadFailurePath(workspaceDir) {
1239
+ return node_path_1.default.join(workspaceDir, ".playdrop", "task-upload-failure.json");
1240
+ }
1043
1241
  function workerTaskMaterialsPath(workspaceDir) {
1044
1242
  return node_path_1.default.join(workspaceDir, ".playdrop", "task-materials.json");
1045
1243
  }
1244
+ function workerGameSourceReadinessPath(workspaceDir) {
1245
+ return node_path_1.default.join(workspaceDir, ".playdrop", "game-source-ready.json");
1246
+ }
1247
+ function workerGameSourceCompletionPath(workspaceDir) {
1248
+ return node_path_1.default.join(workspaceDir, ".playdrop", "game-source-completion.json");
1249
+ }
1250
+ function readWorkerGameSourceCompletion(workspaceDir) {
1251
+ const file = workerGameSourceCompletionPath(workspaceDir);
1252
+ if (!(0, node_fs_1.existsSync)(file))
1253
+ return null;
1254
+ const parsed = JSON.parse((0, node_fs_1.readFileSync)(file, "utf8"));
1255
+ if (!Number.isSafeInteger(parsed.taskId) || Number(parsed.taskId) <= 0 ||
1256
+ !Number.isSafeInteger(parsed.attempt) || Number(parsed.attempt) <= 0 ||
1257
+ typeof parsed.summary !== "string" || !parsed.summary.trim()) {
1258
+ throw new Error("game_source_completion_intent_invalid");
1259
+ }
1260
+ return parsed;
1261
+ }
1046
1262
  function serializeWorkerTaskContext(taskContext) {
1047
1263
  const playdrop = taskContext.metadata.playdrop;
1048
1264
  if (!playdrop || typeof playdrop !== "object" || Array.isArray(playdrop)) {
@@ -1072,6 +1288,7 @@ function serializeWorkerTaskContext(taskContext) {
1072
1288
  id: taskContext.taskId,
1073
1289
  attempt: taskContext.attempt,
1074
1290
  token: taskContext.taskToken,
1291
+ ...(taskContext.taskAccessToken ? { accessToken: taskContext.taskAccessToken } : {}),
1075
1292
  type: taskContext.taskType,
1076
1293
  executionTarget: taskContext.target,
1077
1294
  metadata,
@@ -1082,6 +1299,7 @@ function serializeWorkerTaskContext(taskContext) {
1082
1299
  worker: {
1083
1300
  devPort: taskContext.devPort,
1084
1301
  env: taskContext.env,
1302
+ gameSourceGit: taskContext.gameSourceGit,
1085
1303
  },
1086
1304
  };
1087
1305
  }
@@ -1098,7 +1316,7 @@ async function stageWorkerTaskContext(input) {
1098
1316
  async function writeWorkerTaskContextFile(workspaceDir, taskContext) {
1099
1317
  await (0, promises_1.writeFile)(workerTaskContextPath(workspaceDir), JSON.stringify(serializeWorkerTaskContext(taskContext), null, 2), "utf8");
1100
1318
  }
1101
- function findWorkspaceTaskFile(startDir, basename) {
1319
+ function findOptionalWorkspaceTaskFile(startDir, basename) {
1102
1320
  let current = node_path_1.default.resolve(startDir);
1103
1321
  while (true) {
1104
1322
  const candidate = node_path_1.default.join(current, ".playdrop", basename);
@@ -1107,11 +1325,18 @@ function findWorkspaceTaskFile(startDir, basename) {
1107
1325
  }
1108
1326
  const parent = node_path_1.default.dirname(current);
1109
1327
  if (parent === current) {
1110
- throw new Error(`task_context_missing:${basename}`);
1328
+ return null;
1111
1329
  }
1112
1330
  current = parent;
1113
1331
  }
1114
1332
  }
1333
+ function findWorkspaceTaskFile(startDir, basename) {
1334
+ const file = findOptionalWorkspaceTaskFile(startDir, basename);
1335
+ if (!file) {
1336
+ throw new Error(`task_context_missing:${basename}`);
1337
+ }
1338
+ return file;
1339
+ }
1115
1340
  function readTaskContextFile(startDir = node_process_1.default.cwd()) {
1116
1341
  const file = findWorkspaceTaskFile(startDir, "task.json");
1117
1342
  const parsed = JSON.parse((0, node_fs_1.readFileSync)(file, "utf8"));
@@ -1121,6 +1346,7 @@ function readTaskContextFile(startDir = node_process_1.default.cwd()) {
1121
1346
  const taskId = Number(task?.id);
1122
1347
  const attempt = Number(task?.attempt);
1123
1348
  const taskToken = typeof task?.token === "string" ? task.token.trim() : "";
1349
+ const taskAccessToken = typeof task?.accessToken === "string" ? task.accessToken.trim() : "";
1124
1350
  const taskType = typeof task?.type === "string" ? task.type.trim() : "";
1125
1351
  const metadata = task?.metadata && typeof task.metadata === "object" && !Array.isArray(task.metadata) ? task.metadata : null;
1126
1352
  const prompt = typeof request?.prompt === "string" ? request.prompt : "";
@@ -1132,6 +1358,7 @@ function readTaskContextFile(startDir = node_process_1.default.cwd()) {
1132
1358
  !Number.isInteger(attempt) ||
1133
1359
  attempt <= 0 ||
1134
1360
  !taskToken ||
1361
+ (task?.executionTarget === "FIRST_PARTY" && !taskAccessToken) ||
1135
1362
  !taskType ||
1136
1363
  !metadata ||
1137
1364
  (task?.executionTarget !== "FIRST_PARTY" && task?.executionTarget !== "PERSONAL") ||
@@ -1148,10 +1375,12 @@ function readTaskContextFile(startDir = node_process_1.default.cwd()) {
1148
1375
  taskId,
1149
1376
  attempt,
1150
1377
  taskToken,
1378
+ taskAccessToken,
1151
1379
  taskType,
1152
1380
  metadata,
1153
1381
  prompt,
1154
1382
  target: task.executionTarget,
1383
+ gameSourceGit: worker?.gameSourceGit === true,
1155
1384
  }),
1156
1385
  devPort,
1157
1386
  env,
@@ -1165,24 +1394,189 @@ function hasAgentTaskUploadedArtifact(task) {
1165
1394
  return ((Number.isInteger(task.appId) && Number(task.appId) > 0) ||
1166
1395
  (Number.isInteger(task.appVersionId) && Number(task.appVersionId) > 0));
1167
1396
  }
1168
- async function appendWorkerTranscriptChunks(input) {
1169
- const batchSize = input.batchSize ?? WORKER_TRANSCRIPT_CHUNK_BATCH_SIZE;
1170
- if (!Number.isInteger(batchSize) || batchSize <= 0 || batchSize > WORKER_TRANSCRIPT_CHUNK_BATCH_SIZE) {
1171
- throw new Error(`invalid_worker_transcript_chunk_batch_size:${batchSize}`);
1172
- }
1173
- for (let index = 0; index < input.chunks.length; index += batchSize) {
1174
- await input.client.workerAppendAgentTaskTranscriptChunks(input.taskId, {
1397
+ function buildWorkerTranscriptUploadHeaders(input) {
1398
+ return {
1399
+ ...input.headers,
1400
+ "content-type": input.contentType,
1401
+ "content-md5": input.md5Base64,
1402
+ };
1403
+ }
1404
+ async function archiveWorkerTranscriptBestEffort(input) {
1405
+ if (!input.transcriptPath || !(0, node_fs_1.existsSync)(input.transcriptPath))
1406
+ return;
1407
+ try {
1408
+ const content = (0, node_fs_1.readFileSync)(input.transcriptPath);
1409
+ const contentSha256 = (0, node_crypto_1.createHash)("sha256").update(content).digest("hex");
1410
+ const contentMd5Base64 = (0, node_crypto_1.createHash)("md5").update(content).digest("base64");
1411
+ const request = {
1175
1412
  workerKey: input.workerKey,
1176
1413
  leaseToken: input.leaseToken,
1177
- chunks: input.chunks.slice(index, index + batchSize),
1178
- });
1414
+ attempt: input.attempt,
1415
+ sha256: contentSha256,
1416
+ md5Base64: contentMd5Base64,
1417
+ bytes: content.length,
1418
+ };
1419
+ const prepared = await input.client.workerPrepareAgentTaskTranscript(input.taskId, request);
1420
+ if (prepared.upload) {
1421
+ const response = await fetch(prepared.upload.url, {
1422
+ method: "PUT",
1423
+ headers: buildWorkerTranscriptUploadHeaders({
1424
+ headers: prepared.upload.headers,
1425
+ contentType: prepared.upload.contentType,
1426
+ md5Base64: contentMd5Base64,
1427
+ }),
1428
+ body: Uint8Array.from(content),
1429
+ });
1430
+ if (!response.ok)
1431
+ throw new Error(`agent_task_transcript_upload_failed:${response.status}`);
1432
+ }
1433
+ await input.client.workerCompleteAgentTaskTranscript(input.taskId, request);
1434
+ }
1435
+ catch (error) {
1436
+ const archiveDir = node_path_1.default.join(resolveWorkerHomeDir(), "transcripts", "unarchived");
1437
+ try {
1438
+ (0, node_fs_1.mkdirSync)(archiveDir, { recursive: true });
1439
+ const retainedPath = node_path_1.default.join(archiveDir, `task-${input.taskId}-attempt-${input.attempt}.ndjson`);
1440
+ await (0, promises_1.copyFile)(input.transcriptPath, retainedPath);
1441
+ const cutoff = Date.now() - 7 * 24 * 60 * 60 * 1000;
1442
+ for (const entry of (0, node_fs_1.readdirSync)(archiveDir)) {
1443
+ const candidate = node_path_1.default.join(archiveDir, entry);
1444
+ if ((0, node_fs_1.statSync)(candidate).isFile() && (0, node_fs_1.statSync)(candidate).mtimeMs < cutoff) {
1445
+ (0, node_fs_1.rmSync)(candidate, { force: true });
1446
+ }
1447
+ }
1448
+ }
1449
+ catch (retentionError) {
1450
+ console.error(`Transcript spool retention failed: ${retentionError instanceof Error ? retentionError.message : String(retentionError)}`);
1451
+ }
1452
+ console.error(`Transcript archival did not complete; the local spool was retained for seven days: ${error instanceof Error ? error.message : String(error)}`);
1179
1453
  }
1180
1454
  }
1181
1455
  function resolveTaskWorkspaceDir(startDir = node_process_1.default.cwd()) {
1182
1456
  return node_path_1.default.dirname(node_path_1.default.dirname(findWorkspaceTaskFile(startDir, "task.json")));
1183
1457
  }
1458
+ function describeTaskUploadFailure(error) {
1459
+ const fullRawError = (error instanceof Error ? error.message : String(error)).trim() || "task_upload_failed";
1460
+ const errorText = fullRawError.slice(0, 40000);
1461
+ const explicitCode = (/^([a-z0-9_.-]+):/.exec(errorText)?.[1] ||
1462
+ (error instanceof types_1.ApiError && typeof error.code === "string" ? error.code.trim() : "") ||
1463
+ "").slice(0, 160);
1464
+ if (explicitCode === "agent_task_playtest_smoke_check_not_interactive") {
1465
+ return {
1466
+ kind: "failure",
1467
+ category: "upload_validation",
1468
+ code: explicitCode,
1469
+ message: "We built the game, but PlayDrop could not verify that its recorded controls changed the game, so it was not uploaded. Retry to repair the controls or playtest recording.",
1470
+ retryable: true,
1471
+ source: "agent",
1472
+ };
1473
+ }
1474
+ let terminalReason = {
1475
+ ...(0, types_1.classifyAgentTaskTerminalReason)(errorText, "worker"),
1476
+ ...(explicitCode ? { code: explicitCode } : {}),
1477
+ };
1478
+ if (error instanceof types_1.ApiError && error.status === 429) {
1479
+ terminalReason = {
1480
+ ...terminalReason,
1481
+ category: "provider_capacity",
1482
+ code: explicitCode || "task_upload_rate_limited",
1483
+ retryable: true,
1484
+ };
1485
+ }
1486
+ else if (error instanceof types_1.ApiError && error.status >= 500) {
1487
+ terminalReason = {
1488
+ ...terminalReason,
1489
+ category: "agent_failure",
1490
+ code: explicitCode || "task_upload_platform_error",
1491
+ retryable: true,
1492
+ };
1493
+ }
1494
+ let creatorMessage = null;
1495
+ if (error instanceof types_1.ApiError && error.status >= 500) {
1496
+ creatorMessage = "PlayDrop could not complete the game upload.";
1497
+ }
1498
+ else if (terminalReason.category === "provider_connection") {
1499
+ creatorMessage = "PlayDrop could not be reached while uploading the game.";
1500
+ }
1501
+ else if (terminalReason.category === "provider_timeout") {
1502
+ creatorMessage = "The game upload did not finish before the timeout.";
1503
+ }
1504
+ else if (terminalReason.category === "provider_capacity") {
1505
+ creatorMessage = "PlayDrop could not accept the game upload yet.";
1506
+ }
1507
+ if (creatorMessage) {
1508
+ return {
1509
+ ...terminalReason,
1510
+ message: creatorMessage,
1511
+ source: "worker",
1512
+ };
1513
+ }
1514
+ return terminalReason;
1515
+ }
1516
+ async function recordTaskUploadFailure(input) {
1517
+ const failure = {
1518
+ schemaVersion: 2,
1519
+ taskId: input.taskContext.taskId,
1520
+ attempt: input.taskContext.attempt,
1521
+ terminalReason: describeTaskUploadFailure(input.error),
1522
+ failedAt: new Date().toISOString(),
1523
+ };
1524
+ await (0, promises_1.mkdir)(node_path_1.default.dirname(workerTaskUploadFailurePath(input.workspaceDir)), { recursive: true });
1525
+ await (0, promises_1.writeFile)(workerTaskUploadFailurePath(input.workspaceDir), JSON.stringify(failure, null, 2), "utf8");
1526
+ return failure;
1527
+ }
1528
+ function readTaskUploadFailureFile(startDir = node_process_1.default.cwd()) {
1529
+ const file = findOptionalWorkspaceTaskFile(startDir, "task-upload-failure.json");
1530
+ if (!file)
1531
+ return null;
1532
+ const parsed = JSON.parse((0, node_fs_1.readFileSync)(file, "utf8"));
1533
+ const taskId = Number(parsed.taskId);
1534
+ const attempt = Number(parsed.attempt);
1535
+ const terminalReasonValue = parsed.terminalReason;
1536
+ const code = typeof terminalReasonValue?.code === "string" ? terminalReasonValue.code.trim() : "";
1537
+ const message = typeof terminalReasonValue?.message === "string" ? terminalReasonValue.message.trim() : "";
1538
+ const failedAt = typeof parsed.failedAt === "string" ? parsed.failedAt.trim() : "";
1539
+ if (parsed.schemaVersion !== 2 ||
1540
+ !Number.isInteger(taskId) ||
1541
+ taskId <= 0 ||
1542
+ !Number.isInteger(attempt) ||
1543
+ attempt <= 0 ||
1544
+ terminalReasonValue?.kind !== "failure" ||
1545
+ !TASK_UPLOAD_FAILURE_CATEGORIES.has(terminalReasonValue.category) ||
1546
+ !code ||
1547
+ code.length > 160 ||
1548
+ !message ||
1549
+ message.length > 600 ||
1550
+ typeof terminalReasonValue.retryable !== "boolean" ||
1551
+ !TASK_UPLOAD_FAILURE_SOURCES.has(terminalReasonValue.source) ||
1552
+ !failedAt ||
1553
+ !Number.isFinite(Date.parse(failedAt))) {
1554
+ throw new Error(`task_upload_failure_invalid:${file}`);
1555
+ }
1556
+ return {
1557
+ schemaVersion: 2,
1558
+ taskId,
1559
+ attempt,
1560
+ terminalReason: {
1561
+ kind: "failure",
1562
+ category: terminalReasonValue.category,
1563
+ code,
1564
+ message,
1565
+ retryable: terminalReasonValue.retryable,
1566
+ source: terminalReasonValue.source,
1567
+ },
1568
+ failedAt,
1569
+ };
1570
+ }
1184
1571
  function readTaskUploadResultFile(startDir = node_process_1.default.cwd()) {
1185
- const file = findWorkspaceTaskFile(startDir, "task-upload-result.json");
1572
+ const file = findOptionalWorkspaceTaskFile(startDir, "task-upload-result.json");
1573
+ if (!file) {
1574
+ const failure = readTaskUploadFailureFile(startDir);
1575
+ if (failure) {
1576
+ throw new Error(`task_done_blocked_by_upload_failure:${failure.terminalReason.code}: ${failure.terminalReason.message}`);
1577
+ }
1578
+ throw new Error("task_context_missing:task-upload-result.json");
1579
+ }
1186
1580
  const parsed = JSON.parse((0, node_fs_1.readFileSync)(file, "utf8"));
1187
1581
  const taskId = Number(parsed.taskId);
1188
1582
  const appId = Number(parsed.appId);
@@ -1380,34 +1774,7 @@ function resolveWorkspaceFileDestination(workspaceDir, relativePath) {
1380
1774
  }
1381
1775
  return destination;
1382
1776
  }
1383
- async function stageAssignmentWorkspaceV2(workspaceDir, workspace) {
1384
- for (const file of workspace.files) {
1385
- const normalized = normalizeAssignmentContent({
1386
- path: file.path,
1387
- contentBase64: file.contentBase64,
1388
- sha256: file.sha256,
1389
- sizeBytes: file.sizeBytes,
1390
- code: "agent_task_assignment_workspace_file",
1391
- });
1392
- const destination = resolveWorkspaceFileDestination(workspaceDir, normalized.path);
1393
- await (0, promises_1.mkdir)(node_path_1.default.dirname(destination), { recursive: true });
1394
- await (0, promises_1.writeFile)(destination, normalized.buffer);
1395
- }
1396
- for (const archive of workspace.archives) {
1397
- const normalized = normalizeAssignmentContent({
1398
- path: archive.path,
1399
- contentBase64: archive.contentBase64,
1400
- sha256: archive.sha256,
1401
- sizeBytes: archive.sizeBytes,
1402
- code: "agent_task_assignment_workspace_archive",
1403
- });
1404
- const destination = resolveWorkspaceFileDestination(workspaceDir, normalized.path);
1405
- await (0, promises_1.mkdir)(destination, { recursive: true });
1406
- (0, archive_staging_1.extractZipArchive)(new Uint8Array(normalized.buffer), destination);
1407
- await removeStaleWorkerControlFiles(destination);
1408
- }
1409
- }
1410
- async function stageAssignmentWorkspaceV3(workspaceDir, workspace, options = {}) {
1777
+ async function stageAssignmentWorkspace(workspaceDir, workspace, options = {}) {
1411
1778
  for (const file of workspace.files) {
1412
1779
  const normalized = normalizeAssignmentContent({
1413
1780
  path: file.path,
@@ -1425,6 +1792,7 @@ async function stageAssignmentWorkspaceV3(workspaceDir, workspace, options = {})
1425
1792
  await (0, archive_staging_1.stageWorkspaceArchiveInChild)({
1426
1793
  downloadUrl: normalizeWorkspaceArchiveDownloadUrl(archive.downloadUrl),
1427
1794
  expectedSize: Number(archive.sizeBytes),
1795
+ expectedSha256: archive.sha256,
1428
1796
  targetDir: destination,
1429
1797
  timeoutMs: options.timeoutMs ?? exports.WORKSPACE_ARCHIVE_DOWNLOAD_TIMEOUT_MS,
1430
1798
  });
@@ -1975,13 +2343,15 @@ function buildWorkerCapabilities(input) {
1975
2343
  : null;
1976
2344
  const maxParallelTasks = input.maxParallelTasks ?? DEFAULT_WORKER_MAX_PARALLEL_TASKS;
1977
2345
  const runningTaskCount = input.runningTaskCount ?? 0;
2346
+ const requireGenerationRuntime = input.requireGenerationRuntime === true;
1978
2347
  const degradedReasons = [];
2348
+ const gameSourceInventory = (0, game_source_git_1.readGameSourceWorkerInventory)(resolveWorkerHomeDir());
1979
2349
  const agents = [];
1980
2350
  if (codexVersion) {
1981
- if (!codexAuthenticated) {
2351
+ if (requireGenerationRuntime && !codexAuthenticated) {
1982
2352
  degradedReasons.push("codex_not_authenticated");
1983
2353
  }
1984
- if (codexModels.length === 0) {
2354
+ if (requireGenerationRuntime && codexModels.length === 0) {
1985
2355
  degradedReasons.push("codex_models_unavailable");
1986
2356
  }
1987
2357
  agents.push({
@@ -1994,12 +2364,6 @@ function buildWorkerCapabilities(input) {
1994
2364
  });
1995
2365
  }
1996
2366
  if (claudeVersion) {
1997
- if (!claudeAuthenticated) {
1998
- degradedReasons.push("claude_not_authenticated");
1999
- }
2000
- if (claudeModels.length === 0) {
2001
- degradedReasons.push("claude_models_unavailable");
2002
- }
2003
2367
  agents.push({
2004
2368
  agent: "CLAUDE_CODE",
2005
2369
  cliVersion: claudeVersion,
@@ -2010,12 +2374,6 @@ function buildWorkerCapabilities(input) {
2010
2374
  });
2011
2375
  }
2012
2376
  if (grokVersion) {
2013
- if (!grokAuthenticated) {
2014
- degradedReasons.push("grok_not_authenticated");
2015
- }
2016
- if (grokModels.length === 0) {
2017
- degradedReasons.push("grok_models_unavailable");
2018
- }
2019
2377
  agents.push({
2020
2378
  agent: "GROK_BUILD",
2021
2379
  cliVersion: grokVersion,
@@ -2026,10 +2384,6 @@ function buildWorkerCapabilities(input) {
2026
2384
  });
2027
2385
  }
2028
2386
  if (cursorVersion) {
2029
- degradedReasons.push("cursor_runner_not_configured");
2030
- if (!cursorAuthenticated) {
2031
- degradedReasons.push("cursor_not_authenticated");
2032
- }
2033
2387
  agents.push({
2034
2388
  agent: "CURSOR_COMPOSER",
2035
2389
  cliVersion: cursorVersion,
@@ -2041,17 +2395,120 @@ function buildWorkerCapabilities(input) {
2041
2395
  if (agents.length === 0) {
2042
2396
  throw new Error("agent_cli_not_found: install and authenticate Codex, Claude Code, Grok Build, or Cursor before starting a PlayDrop worker.");
2043
2397
  }
2398
+ const codexReady = agents.some((agent) => agent.agent === "CODEX" && agent.ready === true);
2399
+ const codexImageGenerationAvailable = input.codexImageGenerationAvailable === true;
2400
+ const falConfigured = input.falConfigured ?? (0, generation_1.hasFalCredential)(resolveWorkerHomeDir());
2401
+ const falAuthenticated = input.falAuthenticated ?? falConfigured;
2402
+ const ffmpegReady = input.ffmpegReady ?? (workerCommandExists("ffmpeg") && workerCommandExists("ffprobe"));
2403
+ const generationProviders = [];
2404
+ if (codexReady) {
2405
+ generationProviders.push({
2406
+ provider: "CODEX",
2407
+ kind: "TEXT",
2408
+ model: "gpt-5.6-luna",
2409
+ sourceModes: ["TEXT"],
2410
+ authenticated: true,
2411
+ ready: true,
2412
+ features: ["SANDBOXED_EXEC"],
2413
+ }, {
2414
+ provider: "CODEX",
2415
+ kind: "JSON",
2416
+ model: "gpt-5.6-luna",
2417
+ sourceModes: ["TEXT"],
2418
+ authenticated: true,
2419
+ ready: true,
2420
+ features: ["SANDBOXED_EXEC", "JSON_SCHEMA"],
2421
+ });
2422
+ }
2423
+ if (codexReady && codexImageGenerationAvailable) {
2424
+ generationProviders.push({
2425
+ provider: "CODEX",
2426
+ kind: "IMAGE",
2427
+ model: "gpt-5.6-luna",
2428
+ sourceModes: ["TEXT", "IMAGE", "MULTI_IMAGE"],
2429
+ authenticated: true,
2430
+ ready: true,
2431
+ features: ["SANDBOXED_EXEC", "IMAGE_GENERATION"],
2432
+ });
2433
+ }
2434
+ if (requireGenerationRuntime && !codexVersion)
2435
+ degradedReasons.push("codex_not_installed");
2436
+ if (requireGenerationRuntime && codexReady && !codexImageGenerationAvailable) {
2437
+ degradedReasons.push("codex_image_generation_unavailable");
2438
+ }
2439
+ if (falConfigured && falAuthenticated && codexReady && ffmpegReady) {
2440
+ generationProviders.push({
2441
+ provider: "FAL",
2442
+ kind: "SPEECH",
2443
+ model: "fal-ai/elevenlabs/tts/turbo-v2.5",
2444
+ sourceModes: ["TEXT"],
2445
+ authenticated: true,
2446
+ ready: true,
2447
+ features: ["CODEX_METADATA", "FFMPEG", "WAVEFORM"],
2448
+ }, ...[
2449
+ ["fal-ai/veo3.1/fast", "TEXT"],
2450
+ ["fal-ai/veo3.1/fast/image-to-video", "IMAGE"],
2451
+ ["fal-ai/veo3.1/fast/first-last-frame-to-video", "FIRST_LAST_FRAME"],
2452
+ ].map(([model, sourceMode]) => ({
2453
+ provider: "FAL",
2454
+ kind: "VIDEO",
2455
+ model,
2456
+ sourceModes: [sourceMode],
2457
+ authenticated: true,
2458
+ ready: true,
2459
+ features: ["CODEX_METADATA", "FFMPEG", "THUMBNAIL"],
2460
+ })), ...[
2461
+ ["fal-ai/meshy/v6/text-to-3d", "TEXT"],
2462
+ ["fal-ai/meshy/v6/image-to-3d", "IMAGE"],
2463
+ ["fal-ai/meshy/v6/multi-image-to-3d", "MULTI_IMAGE"],
2464
+ ].map(([model, sourceMode]) => ({
2465
+ provider: "FAL",
2466
+ kind: "MODEL_3D",
2467
+ model,
2468
+ sourceModes: [sourceMode],
2469
+ authenticated: true,
2470
+ ready: true,
2471
+ features: ["CODEX_METADATA", "GLB_VALIDATION", "THUMBNAIL"],
2472
+ })));
2473
+ if (codexImageGenerationAvailable) {
2474
+ generationProviders.push({
2475
+ provider: "FAL",
2476
+ kind: "MUSIC",
2477
+ model: "fal-ai/elevenlabs/music",
2478
+ sourceModes: ["TEXT"],
2479
+ authenticated: true,
2480
+ ready: true,
2481
+ features: ["CODEX_METADATA", "CODEX_IMAGE", "FFMPEG"],
2482
+ }, {
2483
+ provider: "FAL",
2484
+ kind: "SFX",
2485
+ model: "fal-ai/elevenlabs/sound-effects/v2",
2486
+ sourceModes: ["TEXT"],
2487
+ authenticated: true,
2488
+ ready: true,
2489
+ features: ["CODEX_METADATA", "CODEX_IMAGE", "FFMPEG"],
2490
+ });
2491
+ }
2492
+ }
2493
+ const supportedGenerationKinds = new Set(generationProviders.map((provider) => provider.kind));
2494
+ const supportedKinds = WORKER_SUPPORTED_KINDS.filter((kind) => !["TEXT", "JSON", "IMAGE", "MODEL_3D", "VIDEO", "SFX", "SPEECH", "MUSIC"].includes(kind) || supportedGenerationKinds.has(kind));
2495
+ const hasReadyAgent = agents.some((agent) => agent.ready === true);
2496
+ if (!hasReadyAgent && degradedReasons.length === 0)
2497
+ degradedReasons.push("agent_cli_not_authenticated");
2044
2498
  return {
2045
2499
  agent: agents[0].agent,
2046
- supportedKinds: [...WORKER_SUPPORTED_KINDS],
2500
+ supportedKinds,
2047
2501
  maxParallelTasks,
2048
2502
  runningTaskCount,
2049
2503
  cliVersion: (0, clientInfo_1.getCliVersion)(),
2050
2504
  nodeVersion: node_process_1.default.versions.node,
2051
2505
  agents,
2052
- ready: agents.some((agent) => agent.ready === true),
2506
+ generationProviders,
2507
+ ready: hasReadyAgent && degradedReasons.length === 0,
2053
2508
  degradedReasons,
2054
2509
  wrapperContractVersion: (0, runtime_1.readPositiveEnvInt)("PLAYDROP_WORKER_WRAPPER_CONTRACT_VERSION", WORKER_WRAPPER_CONTRACT_VERSION),
2510
+ gameSourceGit: 1,
2511
+ gameSource: gameSourceInventory,
2055
2512
  ...(npmVersion ? { npmVersion } : {}),
2056
2513
  ...(playwright ? { playwright } : {}),
2057
2514
  os: node_process_1.default.platform,
@@ -2060,7 +2517,7 @@ function buildWorkerCapabilities(input) {
2060
2517
  }
2061
2518
  function buildWorkerClaimBody(input) {
2062
2519
  return {
2063
- protocolVersion: 3,
2520
+ protocolVersion: 5,
2064
2521
  workerKey: input.workerKey,
2065
2522
  capabilities: {
2066
2523
  ...input.capabilities,
@@ -2317,6 +2774,7 @@ async function prepareCodexRunHomes(input) {
2317
2774
  workerUsername: input.workerUsername,
2318
2775
  envName: input.envName,
2319
2776
  playdropConfig: input.playdropConfig,
2777
+ accessToken: input.playdropAccessToken,
2320
2778
  });
2321
2779
  }
2322
2780
  return { homeDir, codexHomeDir };
@@ -2326,7 +2784,8 @@ async function stageWorkerPlaydropSession(input) {
2326
2784
  const envName = input.envName.trim();
2327
2785
  const config = input.playdropConfig ?? (0, config_1.loadConfig)();
2328
2786
  const session = config.accounts?.[workerUsername]?.[envName];
2329
- if (!workerUsername || !envName || !session?.token?.trim()) {
2787
+ const accessToken = input.accessToken?.trim() || session?.token?.trim() || "";
2788
+ if (!workerUsername || !envName || !accessToken) {
2330
2789
  throw new Error(`worker_playdrop_session_missing:${workerUsername || "unknown"}:${envName || "unknown"}`);
2331
2790
  }
2332
2791
  const playdropConfigDir = node_path_1.default.join(input.homeDir, ".playdrop");
@@ -2334,13 +2793,13 @@ async function stageWorkerPlaydropSession(input) {
2334
2793
  await (0, promises_1.mkdir)(playdropConfigDir, { recursive: true, mode: 0o700 });
2335
2794
  await (0, promises_1.chmod)(playdropConfigDir, 0o700);
2336
2795
  await (0, promises_1.writeFile)(playdropConfigPath, JSON.stringify({
2337
- version: 2,
2796
+ version: 3,
2338
2797
  currentAccount: { username: workerUsername, env: envName },
2339
2798
  accounts: {
2340
2799
  [workerUsername]: {
2341
2800
  [envName]: {
2342
- token: session.token.trim(),
2343
- updatedAt: session.updatedAt,
2801
+ token: accessToken,
2802
+ updatedAt: session?.updatedAt ?? new Date().toISOString(),
2344
2803
  },
2345
2804
  },
2346
2805
  },
@@ -2370,6 +2829,7 @@ async function prepareClaudeRunConfig(input) {
2370
2829
  workerUsername: input.workerUsername,
2371
2830
  envName: input.envName,
2372
2831
  playdropConfig: input.playdropConfig,
2832
+ accessToken: input.playdropAccessToken,
2373
2833
  }),
2374
2834
  };
2375
2835
  }
@@ -2393,6 +2853,7 @@ async function prepareGrokRunConfig(input) {
2393
2853
  workerUsername: input.workerUsername,
2394
2854
  envName: input.envName,
2395
2855
  playdropConfig: input.playdropConfig,
2856
+ accessToken: input.playdropAccessToken,
2396
2857
  });
2397
2858
  return {
2398
2859
  homeDir,
@@ -2428,6 +2889,7 @@ async function runCodex(input) {
2428
2889
  workspaceDir: input.stateDir ?? input.workspaceDir,
2429
2890
  workerUsername: input.workerUsername,
2430
2891
  envName: input.envName,
2892
+ playdropAccessToken: input.taskAccessToken,
2431
2893
  stagePlaydropSession: !input.cleanRoom,
2432
2894
  preserveExisting: Boolean(input.resumeSessionId),
2433
2895
  });
@@ -2468,8 +2930,6 @@ async function runCodex(input) {
2468
2930
  workspaceDir: input.workspaceDir,
2469
2931
  model: input.codexModel.model,
2470
2932
  reasoningEffort: input.codexModel.reasoningEffort,
2471
- networkAccess: input.cleanRoom || (0, runtime_1.readEnvBoolean)("PLAYDROP_WORKER_AGENT_NETWORK_ACCESS", false),
2472
- sandboxMode: (0, runtime_1.readCodexSandboxMode)(),
2473
2933
  imagePaths: input.imagePaths,
2474
2934
  resumeSessionId: input.resumeSessionId,
2475
2935
  }),
@@ -2492,6 +2952,7 @@ async function runCodex(input) {
2492
2952
  homeDir,
2493
2953
  codexHomeDir,
2494
2954
  playwrightBrowsersPath: resolveWorkerPlaywrightBrowsersPath(),
2955
+ gameServerDev: input.gameServerDev ?? undefined,
2495
2956
  }),
2496
2957
  stdin: input.prompt,
2497
2958
  timeoutMs: input.timeoutMs ?? (0, runtime_1.readPositiveEnvInt)("PLAYDROP_WORKER_CODEX_TIMEOUT_MS", runtime_1.DEFAULT_CODEX_TIMEOUT_MS),
@@ -2538,6 +2999,142 @@ async function runCodex(input) {
2538
2999
  tokensUsed: rolloutUsage.totalTokens,
2539
3000
  };
2540
3001
  }
3002
+ async function runPlaydropE2EGameFixture(input) {
3003
+ if (!(0, e2e_fixtures_1.isPlaydropE2EGameKind)(input.taskContext.kind)) {
3004
+ throw new Error(`playdrop_e2e_game_kind_invalid:${input.taskContext.kind}`);
3005
+ }
3006
+ const runRoot = node_path_1.default.join(input.workspaceDir, ".playdrop", "e2e-run");
3007
+ const homeDir = node_path_1.default.join(runRoot, "home");
3008
+ await (0, promises_1.rm)(runRoot, { recursive: true, force: true });
3009
+ await (0, promises_1.mkdir)(homeDir, { recursive: true, mode: 0o700 });
3010
+ await (0, promises_1.chmod)(homeDir, 0o700);
3011
+ const playdropConfigPath = await stageWorkerPlaydropSession({
3012
+ homeDir,
3013
+ workerUsername: input.workerUsername,
3014
+ envName: input.envName,
3015
+ accessToken: input.taskContext.taskAccessToken,
3016
+ });
3017
+ const childEnv = (0, runtime_1.buildWorkerChildEnv)({
3018
+ binDir: input.binDir,
3019
+ workerHomeDir: resolveWorkerHomeDir(),
3020
+ taskId: input.taskContext.taskId,
3021
+ taskToken: input.taskContext.taskToken,
3022
+ attempt: input.taskContext.attempt,
3023
+ envName: input.envName,
3024
+ eventDir: input.eventDir,
3025
+ devPort: input.devPort,
3026
+ homeDir,
3027
+ playdropConfigPath,
3028
+ gameServerDev: input.gameServerDev ?? undefined,
3029
+ });
3030
+ const results = [];
3031
+ const run = async (args) => {
3032
+ const result = await (0, runtime_1.runLoggedProcess)({
3033
+ command: "playdrop",
3034
+ args,
3035
+ cwd: input.projectDir,
3036
+ env: childEnv,
3037
+ timeoutMs: 10 * 60 * 1000,
3038
+ maxOutputChars: runtime_1.DEFAULT_CODEX_LOG_TAIL_CHARS,
3039
+ transcriptFlushIntervalMs: runtime_1.DEFAULT_TRANSCRIPT_FLUSH_INTERVAL_MS,
3040
+ onTranscriptChunks: input.onTranscriptChunks,
3041
+ onChild: input.onChild,
3042
+ });
3043
+ results.push(result);
3044
+ return !result.timedOut && result.exitCode === 0 && result.signal === null;
3045
+ };
3046
+ try {
3047
+ const claimsSlug = input.taskContext.kind === "NEW_GAME" || input.taskContext.kind === "REMIX_GAME";
3048
+ const appName = claimsSlug
3049
+ ? `playdrop-e2e-${input.taskContext.taskId}`
3050
+ : input.taskContext.outputAppName?.trim() ?? "";
3051
+ if (!appName)
3052
+ throw new Error("playdrop_e2e_game_output_app_name_missing");
3053
+ const updateSource = input.taskContext.kind === "GAME_UPDATE"
3054
+ ? (0, taskSelection_1.selectTasks)(input.projectDir).tasks.find((task) => task.kind === "app" && task.name === appName)
3055
+ : null;
3056
+ if (input.taskContext.kind === "GAME_UPDATE" && !updateSource) {
3057
+ throw new Error(`playdrop_e2e_update_source_app_missing:${appName}`);
3058
+ }
3059
+ const displayName = claimsSlug
3060
+ ? `PlayDrop E2E ${input.taskContext.taskId}`
3061
+ : input.taskContext.outputDisplayName?.trim() ||
3062
+ updateSource?.displayName?.trim() ||
3063
+ `PlayDrop E2E ${input.taskContext.taskId}`;
3064
+ const subtitle = claimsSlug
3065
+ ? "Deterministic worker fixture"
3066
+ : input.taskContext.outputSubtitle?.trim() || updateSource?.subtitle?.trim() || "Deterministic worker fixture";
3067
+ const primarySurface = claimsSlug
3068
+ ? "DESKTOP"
3069
+ : input.taskContext.outputPrimarySurface ?? updateSource?.primarySurface ?? "DESKTOP";
3070
+ if (input.taskContext.kind === "REMIX_GAME" && !input.taskContext.remixSourceRef?.trim()) {
3071
+ throw new Error("playdrop_e2e_remix_source_missing");
3072
+ }
3073
+ if (claimsSlug &&
3074
+ !(await run([
3075
+ "task",
3076
+ "claim-slug",
3077
+ "--app-name",
3078
+ appName,
3079
+ "--display-name",
3080
+ displayName,
3081
+ "--subtitle",
3082
+ subtitle,
3083
+ "--primary-surface",
3084
+ primarySurface,
3085
+ ]))) {
3086
+ return mergePlaydropE2EProcessResults(results);
3087
+ }
3088
+ await (0, e2e_fixtures_1.replaceWithPlaydropE2EGameFixture)({
3089
+ projectDir: input.projectDir,
3090
+ taskId: input.taskContext.taskId,
3091
+ kind: input.taskContext.kind,
3092
+ appName,
3093
+ displayName,
3094
+ subtitle,
3095
+ primarySurface,
3096
+ outputVersion: input.taskContext.outputVersion,
3097
+ remixSourceRef: input.taskContext.remixSourceRef?.trim() || null,
3098
+ });
3099
+ if (!(await run(["task", "upload"])))
3100
+ return mergePlaydropE2EProcessResults(results);
3101
+ await run([
3102
+ "task",
3103
+ "done",
3104
+ "--summary",
3105
+ `Completed the deterministic ${input.taskContext.kind} PlayDrop E2E fixture.`,
3106
+ ]);
3107
+ return mergePlaydropE2EProcessResults(results);
3108
+ }
3109
+ finally {
3110
+ await (0, promises_1.rm)(runRoot, { recursive: true, force: true });
3111
+ }
3112
+ }
3113
+ function mergePlaydropE2EProcessResults(results) {
3114
+ const stdout = results.map((result) => result.stdout).filter(Boolean).join("\n");
3115
+ const stderr = results.map((result) => result.stderr).filter(Boolean).join("\n");
3116
+ const outputTail = results.map((result) => result.outputTail).filter(Boolean).join("\n").slice(-runtime_1.DEFAULT_CODEX_LOG_TAIL_CHARS);
3117
+ const failed = results.find((result) => result.timedOut || result.exitCode !== 0 || result.signal !== null);
3118
+ return {
3119
+ exitCode: failed?.exitCode ?? results.at(-1)?.exitCode ?? 0,
3120
+ signal: failed?.signal ?? null,
3121
+ stdout,
3122
+ stderr,
3123
+ outputTail,
3124
+ timedOut: failed?.timedOut ?? false,
3125
+ tokenUsage: {
3126
+ inputTokens: 0,
3127
+ outputTokens: 0,
3128
+ cacheCreationInputTokens: 0,
3129
+ cacheReadInputTokens: 0,
3130
+ totalTokens: 0,
3131
+ rawProviderUsage: { fixture: "playdrop-e2e-v1" },
3132
+ usageParseError: null,
3133
+ },
3134
+ tokensUsed: 0,
3135
+ providerSessionId: null,
3136
+ };
3137
+ }
2541
3138
  async function runClaude(input) {
2542
3139
  const playdropConfigPath = input.cleanRoom
2543
3140
  ? null
@@ -2545,6 +3142,7 @@ async function runClaude(input) {
2545
3142
  workspaceDir: input.stateDir ?? input.workspaceDir,
2546
3143
  workerUsername: input.workerUsername,
2547
3144
  envName: input.envName,
3145
+ playdropAccessToken: input.taskAccessToken,
2548
3146
  })).playdropConfigPath;
2549
3147
  const denyReadRoots = resolveClaudeDenyReadRoots(input.workspaceDir);
2550
3148
  const screenshotCollector = input.enableChrome || input.enablePlaywrightMcp
@@ -2566,6 +3164,7 @@ async function runClaude(input) {
2566
3164
  denyReadRoots,
2567
3165
  pluginDir: input.taskPluginRoot,
2568
3166
  playdropCliEnabled: !input.cleanRoom,
3167
+ allowGit: input.allowGit,
2569
3168
  chrome: input.enableChrome,
2570
3169
  playwrightMcp: input.enablePlaywrightMcp,
2571
3170
  playwrightMcpConfigPath,
@@ -2587,6 +3186,7 @@ async function runClaude(input) {
2587
3186
  devPort: input.devPort,
2588
3187
  playdropConfigPath: playdropConfigPath,
2589
3188
  playwrightBrowsersPath: resolveWorkerPlaywrightBrowsersPath(),
3189
+ gameServerDev: input.gameServerDev ?? undefined,
2590
3190
  }),
2591
3191
  stdin: `${JSON.stringify({
2592
3192
  type: "user",
@@ -2669,6 +3269,7 @@ async function runGrok(input) {
2669
3269
  workspaceDir: input.stateDir ?? input.workspaceDir,
2670
3270
  workerUsername: input.workerUsername,
2671
3271
  envName: input.envName,
3272
+ playdropAccessToken: input.taskAccessToken,
2672
3273
  preserveExisting: Boolean(input.resumeSessionId),
2673
3274
  stagePlaydropSession: !input.cleanRoom,
2674
3275
  });
@@ -2715,6 +3316,7 @@ async function runGrok(input) {
2715
3316
  homeDir,
2716
3317
  playdropConfigPath: playdropConfigPath,
2717
3318
  playwrightBrowsersPath: resolveWorkerPlaywrightBrowsersPath(),
3319
+ gameServerDev: input.gameServerDev ?? undefined,
2718
3320
  });
2719
3321
  childEnv.HOME = homeDir;
2720
3322
  childEnv.GROK_HOME = grokHomeDir;
@@ -2779,9 +3381,9 @@ function resolveClaudeDenyReadRoots(workspaceDir) {
2779
3381
  }
2780
3382
  return roots;
2781
3383
  }
2782
- // Raw stdout/stderr tails stay out of task results on purpose: transcripts are
2783
- // streamed separately and results can surface to creators (spec section 11).
2784
- function mergeCreatorRecoveryResults(runtime, initial, recovery) {
3384
+ // Raw stdout/stderr tails stay out of task results on purpose. The local spool
3385
+ // is archived directly to private storage after the agent exits.
3386
+ function mergeAgentContinuationResults(runtime, initial, recovery, timeoutMarkerCode) {
2785
3387
  const sumNullable = (left, right) => left === null && right === null ? null : (left ?? 0) + (right ?? 0);
2786
3388
  const tokenUsage = runtime === "CODEX" && recovery.tokenUsage.totalTokens !== null
2787
3389
  ? recovery.tokenUsage
@@ -2799,7 +3401,7 @@ function mergeCreatorRecoveryResults(runtime, initial, recovery) {
2799
3401
  ? "creator_recovery_usage_incomplete"
2800
3402
  : null,
2801
3403
  };
2802
- const recoveryMarker = recovery.timedOut ? "\nagent_task_creation_recovery_timeout" : "";
3404
+ const recoveryMarker = recovery.timedOut ? `\n${timeoutMarkerCode}` : "";
2803
3405
  return {
2804
3406
  ...recovery,
2805
3407
  stdout: `${initial.stdout}\n${recovery.stdout}`.slice(-runtime_1.DEFAULT_CODEX_LOG_TAIL_CHARS),
@@ -2811,6 +3413,12 @@ function mergeCreatorRecoveryResults(runtime, initial, recovery) {
2811
3413
  providerResolvedModel: recovery.providerResolvedModel ?? initial.providerResolvedModel ?? null,
2812
3414
  };
2813
3415
  }
3416
+ function mergeCreatorRecoveryResults(runtime, initial, recovery) {
3417
+ return mergeAgentContinuationResults(runtime, initial, recovery, "agent_task_creation_recovery_timeout");
3418
+ }
3419
+ function mergeMissingAgentOutcomeRecoveryResults(runtime, initial, recovery) {
3420
+ return mergeAgentContinuationResults(runtime, initial, recovery, "agent_task_outcome_recovery_timeout");
3421
+ }
2814
3422
  function buildAgentRunResult(result) {
2815
3423
  return {
2816
3424
  exitCode: result.exitCode,
@@ -2915,7 +3523,7 @@ async function recordAgentRunTelemetry(input) {
2915
3523
  completedAt: input.completedAt.toISOString(),
2916
3524
  ...(input.status === "FAILED"
2917
3525
  ? {
2918
- terminalReason: (0, types_1.classifyAgentTaskTerminalReason)(agentFailureRawError(input.result), "agent"),
3526
+ terminalReason: input.terminalReason ?? (0, types_1.classifyAgentTaskTerminalReason)(agentFailureRawError(input.result), "agent"),
2919
3527
  }
2920
3528
  : {}),
2921
3529
  });
@@ -2970,9 +3578,7 @@ function buildAgentFailureCode(agent, result, request) {
2970
3578
  return `agent_provider_overloaded:${provider}`;
2971
3579
  }
2972
3580
  if (providerStatus === 429 ||
2973
- providerStatus === 502 ||
2974
- providerStatus === 503 ||
2975
- providerStatus === 504 ||
3581
+ (providerStatus !== null && providerStatus >= 500 && providerStatus <= 599) ||
2976
3582
  (agent === "CLAUDE_CODE" &&
2977
3583
  /\bResponse stalled mid-stream\.?(?:\s+The response above may be incomplete\.?)?/i.test(combinedOutput)) ||
2978
3584
  /\b(?:service|provider|api)\s+(?:is\s+)?(?:temporarily\s+)?unavailable\b/i.test(combinedOutput) ||
@@ -2982,6 +3588,9 @@ function buildAgentFailureCode(agent, result, request) {
2982
3588
  if (result.timedOut) {
2983
3589
  return `agent_provider_timeout:${provider}`;
2984
3590
  }
3591
+ if (/\b(?:econnreset|econnrefused|network error|socket hang up|connection reset|connection lost|fetch failed)\b/i.test(combinedOutput)) {
3592
+ return `agent_provider_connection:${provider}`;
3593
+ }
2985
3594
  if (result.exitCode !== null) {
2986
3595
  return `agent_unknown_exit:${provider}:${result.exitCode}`;
2987
3596
  }
@@ -2990,7 +3599,7 @@ function buildAgentFailureCode(agent, result, request) {
2990
3599
  }
2991
3600
  return `agent_unknown_exit:${provider}`;
2992
3601
  }
2993
- function classifyAgentAvailabilityFailureReason(errorCode) {
3602
+ function classifyAgentAvailabilityFailureReason(errorCode, retryTransientProviderFailure = false) {
2994
3603
  if (errorCode.startsWith("agent_provider_not_authenticated:")) {
2995
3604
  return "agent_not_authenticated";
2996
3605
  }
@@ -3001,6 +3610,10 @@ function classifyAgentAvailabilityFailureReason(errorCode) {
3001
3610
  errorCode.startsWith("agent_provider_temporarily_unavailable:")) {
3002
3611
  return "agent_temporarily_unavailable";
3003
3612
  }
3613
+ if (retryTransientProviderFailure &&
3614
+ (errorCode.startsWith("agent_provider_connection:") || errorCode.startsWith("agent_provider_timeout:"))) {
3615
+ return "agent_temporarily_unavailable";
3616
+ }
3004
3617
  if (errorCode.startsWith("requested_model_unavailable:")) {
3005
3618
  return "agent_model_unsupported";
3006
3619
  }
@@ -3031,6 +3644,9 @@ function describeAgentFailureForEvent(errorCode) {
3031
3644
  if (code === "agent_provider_timeout") {
3032
3645
  return `${providerLabel} timed out before completing the task. Retry this task when the provider is healthy.`;
3033
3646
  }
3647
+ if (code === "agent_provider_connection") {
3648
+ return `${providerLabel} lost its connection before completing the task. Retry this task when the provider is healthy.`;
3649
+ }
3034
3650
  if (code === "agent_task_resume_session_missing") {
3035
3651
  return `${providerLabel} timed out, but the worker could not recover the same conversation. The task was stopped without starting over.`;
3036
3652
  }
@@ -3076,6 +3692,7 @@ async function createLocalPlaydropShim(input) {
3076
3692
  `set "PLAYDROP_WORKER_TASK_ATTEMPT=${input.attempt}"`,
3077
3693
  `set "PLAYDROP_WORKER_TASK_DEV_PORT=${input.devPort}"`,
3078
3694
  `set "PLAYDROP_DEV_ROUTER_PORT=${input.devPort}"`,
3695
+ ...(input.gameSourceGit ? ['set "PLAYDROP_GAME_SOURCE_GIT_ROOT=1"'] : []),
3079
3696
  `"${node_process_1.default.execPath}" "${cliEntrypoint}" %*`,
3080
3697
  "exit /b %ERRORLEVEL%",
3081
3698
  "",
@@ -3087,12 +3704,25 @@ async function createLocalPlaydropShim(input) {
3087
3704
  `export PLAYDROP_WORKER_TASK_ATTEMPT=${JSON.stringify(String(input.attempt))}`,
3088
3705
  `export PLAYDROP_WORKER_TASK_DEV_PORT=${JSON.stringify(String(input.devPort))}`,
3089
3706
  `export PLAYDROP_DEV_ROUTER_PORT=${JSON.stringify(String(input.devPort))}`,
3707
+ ...(input.gameSourceGit ? ["export PLAYDROP_GAME_SOURCE_GIT_ROOT=1"] : []),
3090
3708
  `exec ${JSON.stringify(node_process_1.default.execPath)} ${JSON.stringify(cliEntrypoint)} "$@"`,
3091
3709
  "",
3092
3710
  ];
3093
3711
  await (0, promises_1.writeFile)(shimPath, shim.join(isWindows ? "\r\n" : "\n"), "utf8");
3094
3712
  if (!isWindows) {
3095
3713
  await (0, promises_1.chmod)(shimPath, 0o755);
3714
+ const npxShimPath = node_path_1.default.join(binDir, "npx");
3715
+ const npxShim = [
3716
+ "#!/bin/sh",
3717
+ 'if [ "$1" = "playdrop" ]; then',
3718
+ " shift",
3719
+ ` exec ${JSON.stringify(shimPath)} "$@"`,
3720
+ "fi",
3721
+ 'PATH="${PATH#*:}" exec npx "$@"',
3722
+ "",
3723
+ ];
3724
+ await (0, promises_1.writeFile)(npxShimPath, npxShim.join("\n"), "utf8");
3725
+ await (0, promises_1.chmod)(npxShimPath, 0o755);
3096
3726
  }
3097
3727
  return binDir;
3098
3728
  }
@@ -3189,6 +3819,19 @@ function probeCodexModels() {
3189
3819
  }
3190
3820
  return parseCodexModelCatalog(result.output);
3191
3821
  }
3822
+ function codexFeatureEnabled(output, feature) {
3823
+ const normalizedFeature = feature.trim();
3824
+ if (!normalizedFeature)
3825
+ return false;
3826
+ return output.split(/\r?\n/).some((line) => {
3827
+ const columns = line.trim().split(/\s+/);
3828
+ return columns[0] === normalizedFeature && columns[columns.length - 1] === "true";
3829
+ });
3830
+ }
3831
+ function probeCodexImageGeneration() {
3832
+ const result = (0, shellProbe_1.runShell)("codex features list");
3833
+ return result.exitCode === 0 && codexFeatureEnabled(result.output, "image_generation");
3834
+ }
3192
3835
  function probeClaudeInstallation() {
3193
3836
  const probe = (0, shellProbe_1.runShell)((0, shellProbe_1.buildCommandPathProbe)("claude"));
3194
3837
  if (probe.exitCode !== 0 || !probe.output.trim()) {
@@ -3320,12 +3963,21 @@ function probeReadyWorkerCapabilities(input) {
3320
3963
  const grokVersion = probeWorkerProviderIndependently(() => probeGrokInstallation()?.grokVersion ?? null, null);
3321
3964
  const cursorVersion = probeWorkerProviderIndependently(() => probeCursorInstallation()?.cursorVersion ?? null, null);
3322
3965
  const codexModels = codexVersion ? probeWorkerProviderIndependently(probeCodexModels, []) : [];
3966
+ const codexImageGenerationAvailable = codexVersion
3967
+ ? probeWorkerProviderIndependently(probeCodexImageGeneration, false)
3968
+ : false;
3323
3969
  const claudeModels = claudeVersion ? probeWorkerProviderIndependently(probeClaudeModels, []) : [];
3324
3970
  const grokModels = grokVersion ? probeWorkerProviderIndependently(probeGrokModels, []) : [];
3971
+ const workerHomeDir = resolveWorkerHomeDir();
3972
+ const falConfigured = (0, generation_1.hasFalCredential)(workerHomeDir);
3973
+ const falProbe = probeFalAuthentication(workerHomeDir);
3325
3974
  const capabilities = buildWorkerCapabilities({
3326
3975
  codexVersion,
3327
3976
  codexAuthenticated: codexVersion ? readCodexAuthenticated() : false,
3328
3977
  codexModels,
3978
+ codexImageGenerationAvailable,
3979
+ falConfigured,
3980
+ falAuthenticated: falProbe.authenticated,
3329
3981
  claudeVersion,
3330
3982
  claudeAuthenticated: claudeVersion ? readClaudeAuthenticated() : false,
3331
3983
  claudeModels,
@@ -3338,8 +3990,9 @@ function probeReadyWorkerCapabilities(input) {
3338
3990
  playwright: input.playwright,
3339
3991
  maxParallelTasks: input.maxParallelTasks,
3340
3992
  runningTaskCount: 0,
3993
+ requireGenerationRuntime: input.requireGenerationRuntime,
3341
3994
  });
3342
- if (!capabilities.ready) {
3995
+ if (!capabilities.ready || (capabilities.degradedReasons?.length ?? 0) > 0) {
3343
3996
  const reasons = Array.isArray(capabilities.degradedReasons) && capabilities.degradedReasons.length > 0
3344
3997
  ? capabilities.degradedReasons.join(", ")
3345
3998
  : "no ready agent";
@@ -3446,11 +4099,18 @@ function describeWorkerPreflightReason(reason) {
3446
4099
  worker_preflight_recorder_hash_mismatch: "The PlayDrop macOS recorder does not match this CLI release.",
3447
4100
  worker_preflight_screen_recording_permission_missing: "Screen Recording permission is not granted to the PlayDrop recorder.",
3448
4101
  codex_not_authenticated: "Codex is installed but not authenticated.",
4102
+ codex_not_installed: "Codex is required for generation work but is not installed.",
3449
4103
  codex_models_unavailable: "Codex is authenticated but reported no usable models.",
4104
+ codex_image_generation_unavailable: "Codex built-in image generation is unavailable.",
3450
4105
  claude_not_authenticated: "Claude Code is installed but not authenticated.",
3451
4106
  claude_models_unavailable: "Claude Code is authenticated but reported no usable models.",
3452
4107
  grok_not_authenticated: "Grok Build is installed but not authenticated with a subscription login.",
3453
4108
  grok_models_unavailable: "Grok Build is authenticated but reported no usable models.",
4109
+ fal_not_configured: "The Fal credential file is missing.",
4110
+ fal_not_authenticated: "The Fal credential was rejected by the Fal queue API.",
4111
+ fal_api_unreachable: "The Fal queue API could not be reached during worker readiness checks.",
4112
+ ffmpeg_not_ready_for_generation: "ffmpeg or ffprobe is missing from PATH.",
4113
+ agent_cli_not_authenticated: "No installed agent CLI has a usable authenticated model.",
3454
4114
  agent_cli_not_found: "No supported authenticated agent CLI is installed.",
3455
4115
  };
3456
4116
  return descriptions[reason] ?? reason;
@@ -3477,10 +4137,56 @@ function readWorkerTargetUpdateFailure() {
3477
4137
  minimumCliVersion: typeof parsed.minimumCliVersion === "string" ? parsed.minimumCliVersion : null,
3478
4138
  };
3479
4139
  }
3480
- function collectWorkerLocalStatus() {
4140
+ function falAuthenticationProbeExitCode(status) {
4141
+ if (status === 404)
4142
+ return 0;
4143
+ if (status === 401 || status === 403)
4144
+ return 2;
4145
+ return 3;
4146
+ }
4147
+ function probeFalAuthentication(workerHomeDir = resolveWorkerHomeDir(), deps = { spawnSync: node_child_process_1.spawnSync }) {
4148
+ const credentialFile = (0, generation_1.falCredentialPath)(workerHomeDir);
4149
+ if (!(0, node_fs_1.existsSync)(credentialFile)) {
4150
+ return { authenticated: false, error: "fal_not_configured" };
4151
+ }
4152
+ const credential = (0, node_fs_1.readFileSync)(credentialFile, "utf8").trim();
4153
+ if (!credential) {
4154
+ return { authenticated: false, error: "fal_not_authenticated" };
4155
+ }
4156
+ const probe = deps.spawnSync(node_process_1.default.execPath, [
4157
+ "-e",
4158
+ [
4159
+ "const chunks=[];",
4160
+ "process.stdin.on('data',(chunk)=>chunks.push(chunk));",
4161
+ "process.stdin.on('end',async()=>{",
4162
+ "const key=Buffer.concat(chunks).toString('utf8').trim();",
4163
+ "const controller=new AbortController();",
4164
+ "const timeout=setTimeout(()=>controller.abort(),10000);",
4165
+ "try{",
4166
+ "const response=await fetch('https://queue.fal.run/fal-ai/elevenlabs/requests/playdrop-auth-probe/status?logs=0',{headers:{Authorization:`Key ${key}`},signal:controller.signal});",
4167
+ "process.exitCode=response.status===404?0:response.status===401||response.status===403?2:3;",
4168
+ "}catch{process.exitCode=3;}finally{clearTimeout(timeout);}",
4169
+ "});",
4170
+ ].join(""),
4171
+ ], {
4172
+ input: `${credential}\n`,
4173
+ stdio: ["pipe", "ignore", "ignore"],
4174
+ timeout: 15000,
4175
+ });
4176
+ if (probe.status === 0) {
4177
+ return { authenticated: true, error: null };
4178
+ }
4179
+ if (probe.status === 2) {
4180
+ return { authenticated: false, error: "fal_not_authenticated" };
4181
+ }
4182
+ return { authenticated: false, error: "fal_api_unreachable" };
4183
+ }
4184
+ function collectWorkerLocalStatus(input = {}) {
4185
+ const requireGenerationRuntime = input.requireGenerationRuntime ?? (0, runtime_1.readEnvBoolean)("PLAYDROP_WORKER_REQUIRE_GENERATION_RUNTIME", false);
3481
4186
  const degradedReasons = [];
3482
4187
  let codexVersion = null;
3483
4188
  let codexModels = [];
4189
+ let codexImageGenerationAvailable = false;
3484
4190
  let claudeVersion = null;
3485
4191
  let claudeModels = [];
3486
4192
  let grokVersion = null;
@@ -3489,9 +4195,13 @@ function collectWorkerLocalStatus() {
3489
4195
  const npmVersion = null;
3490
4196
  let playwright = null;
3491
4197
  let targetUpdateFailure = null;
4198
+ const workerHomeDir = resolveWorkerHomeDir();
4199
+ const falConfigured = (0, generation_1.hasFalCredential)(workerHomeDir);
4200
+ const falProbe = probeFalAuthentication(workerHomeDir);
3492
4201
  try {
3493
4202
  codexVersion = probeCodexInstallation()?.codexVersion ?? null;
3494
4203
  codexModels = codexVersion ? probeCodexModels() : [];
4204
+ codexImageGenerationAvailable = codexVersion ? probeCodexImageGeneration() : false;
3495
4205
  }
3496
4206
  catch (error) {
3497
4207
  degradedReasons.push(errorCode(error));
@@ -3500,22 +4210,16 @@ function collectWorkerLocalStatus() {
3500
4210
  claudeVersion = probeClaudeInstallation()?.claudeVersion ?? null;
3501
4211
  claudeModels = claudeVersion ? probeClaudeModels() : [];
3502
4212
  }
3503
- catch (error) {
3504
- degradedReasons.push(errorCode(error));
3505
- }
4213
+ catch { }
3506
4214
  try {
3507
4215
  grokVersion = probeGrokInstallation()?.grokVersion ?? null;
3508
4216
  grokModels = grokVersion ? probeGrokModels() : [];
3509
4217
  }
3510
- catch (error) {
3511
- degradedReasons.push(errorCode(error));
3512
- }
4218
+ catch { }
3513
4219
  try {
3514
4220
  cursorVersion = probeCursorInstallation()?.cursorVersion ?? null;
3515
4221
  }
3516
- catch (error) {
3517
- degradedReasons.push(errorCode(error));
3518
- }
4222
+ catch { }
3519
4223
  try {
3520
4224
  playwright = probePlaywrightChromium();
3521
4225
  if (!playwright.chromiumInstalled) {
@@ -3538,6 +4242,9 @@ function collectWorkerLocalStatus() {
3538
4242
  codexVersion,
3539
4243
  codexAuthenticated: codexVersion ? readCodexAuthenticated() : false,
3540
4244
  codexModels,
4245
+ codexImageGenerationAvailable,
4246
+ falConfigured,
4247
+ falAuthenticated: falProbe.authenticated,
3541
4248
  claudeVersion,
3542
4249
  claudeAuthenticated: claudeVersion ? readClaudeAuthenticated() : false,
3543
4250
  claudeModels,
@@ -3550,6 +4257,7 @@ function collectWorkerLocalStatus() {
3550
4257
  playwright,
3551
4258
  runningTaskCount: 0,
3552
4259
  maxParallelTasks: (0, runtime_1.readPositiveEnvInt)("PLAYDROP_WORKER_MAX_PARALLEL_TASKS", DEFAULT_WORKER_MAX_PARALLEL_TASKS),
4260
+ requireGenerationRuntime,
3553
4261
  });
3554
4262
  if (targetUpdateFailure) {
3555
4263
  capabilities.targetUpdateFailure = targetUpdateFailure;
@@ -3564,10 +4272,10 @@ function collectWorkerLocalStatus() {
3564
4272
  const uniqueReasons = Array.from(new Set(degradedReasons));
3565
4273
  const infrastructureFailures = degradedReasons.filter((reason) => reason.startsWith("worker_preflight_"));
3566
4274
  const infrastructureReady = Boolean(playwright?.chromiumInstalled) && infrastructureFailures.length === 0;
3567
- const ready = Boolean(capabilities?.ready) && infrastructureReady;
4275
+ const ready = Boolean(capabilities?.ready) && infrastructureReady && uniqueReasons.length === 0;
3568
4276
  return {
3569
4277
  ready,
3570
- degradedReasons: ready ? [] : uniqueReasons,
4278
+ degradedReasons: uniqueReasons,
3571
4279
  cliVersion: (0, clientInfo_1.getCliVersion)(),
3572
4280
  nodeVersion: node_process_1.default.versions.node,
3573
4281
  npmVersion,
@@ -4431,6 +5139,13 @@ function printWorkerStatus(payload) {
4431
5139
  else {
4432
5140
  console.log(" Agents: none detected");
4433
5141
  }
5142
+ const generationProviders = payload.local.capabilities?.generationProviders ?? [];
5143
+ const codexGenerationReady = generationProviders.some((entry) => entry.provider === "CODEX" && entry.ready);
5144
+ const codexImageGenerationReady = generationProviders.some((entry) => entry.provider === "CODEX" && entry.kind === "IMAGE" && entry.ready);
5145
+ const falGenerationReady = generationProviders.some((entry) => entry.provider === "FAL" && entry.ready);
5146
+ console.log(` Codex generation: ${codexGenerationReady ? "ready" : "unavailable"}`);
5147
+ console.log(` Codex image generation: ${codexImageGenerationReady ? "ready" : "unavailable"}`);
5148
+ console.log(` Fal generation: ${falGenerationReady ? "ready" : "unavailable"}`);
4434
5149
  if (payload.local.degradedReasons.length > 0) {
4435
5150
  console.log(` Reasons: ${payload.local.degradedReasons.join(", ")}`);
4436
5151
  }
@@ -4487,11 +5202,16 @@ async function setupWorker(options = {}) {
4487
5202
  return;
4488
5203
  }
4489
5204
  if (!options.env?.trim()) {
4490
- throw new Error("worker_setup_requires_env: admin worker setup must pass --env <env> so the LaunchAgent cannot be installed for the wrong environment.");
5205
+ throw new Error("worker_setup_requires_env: cloud worker setup must pass --env <env> so the LaunchAgent cannot be installed for the wrong environment.");
4491
5206
  }
4492
5207
  if (!explicitWorkerName) {
4493
- throw new Error("worker_setup_requires_name: admin worker setup must pass --name <worker name> so the LaunchAgent identity is deterministic.");
5208
+ throw new Error("worker_setup_requires_name: cloud worker setup must pass --name <worker name> so the LaunchAgent identity is deterministic.");
4494
5209
  }
5210
+ const gameServerInfrastructure = await (0, game_server_infra_1.setupManagedWorkerGameServerInfrastructure)({
5211
+ workerHomeDir: resolveWorkerHomeDir(),
5212
+ });
5213
+ node_process_1.default.env.PLAYDROP_WORKER_GAME_SERVER_REDIS_ADMIN_URL = gameServerInfrastructure.redisAdminUrl;
5214
+ node_process_1.default.env.PLAYDROP_WORKER_GAME_SERVER_MONGO_ADMIN_URL = gameServerInfrastructure.mongoAdminUrl;
4495
5215
  verifyNpmGlobalInstallPreconditions();
4496
5216
  const { wrapperPath, policyFile } = await writeManagedWorkerWrapper({ env, workerName, workerKey });
4497
5217
  const { label, plistPath, registrationChanged } = await writeManagedWorkerLaunchAgent({
@@ -4523,7 +5243,7 @@ async function setupWorker(options = {}) {
4523
5243
  `LaunchAgent: ${plistPath}`,
4524
5244
  `Policy file: ${policyFile}`,
4525
5245
  ]);
4526
- }, { env: options.env });
5246
+ }, { env: options.env, forceRefresh: true });
4527
5247
  }
4528
5248
  async function failTaskWithRetry(client, taskId, body) {
4529
5249
  const terminalReason = body.terminalReason ?? (0, types_1.classifyAgentTaskTerminalReason)(body.error, "worker");
@@ -4553,12 +5273,69 @@ async function failTaskWithRetry(client, taskId, body) {
4553
5273
  }
4554
5274
  }
4555
5275
  }
4556
- async function fetchTaskDetail(client, target, taskId) {
4557
- if (target === "FIRST_PARTY") {
4558
- const detail = await client.adminGetAgentTask(taskId);
4559
- return { task: detail.task };
5276
+ async function completeWorkerAgentTaskWithRetry(input) {
5277
+ const sleepImpl = input.sleepImpl ?? sleep;
5278
+ const maxAttempts = input.maxAttempts ?? 3;
5279
+ if (!Number.isInteger(maxAttempts) || maxAttempts <= 0) {
5280
+ throw new Error("agent_task_completion_attempts_invalid");
4560
5281
  }
4561
- return await client.getAgentTask(taskId);
5282
+ let lastError = new Error("agent_task_completion_failed");
5283
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
5284
+ try {
5285
+ return await input.client.workerCompleteAgentTask(input.taskId, input.body);
5286
+ }
5287
+ catch (error) {
5288
+ if (error instanceof types_1.ApiError && error.status < 500)
5289
+ throw error;
5290
+ lastError = error;
5291
+ if (attempt < maxAttempts)
5292
+ await sleepImpl(500 * 2 ** (attempt - 1));
5293
+ }
5294
+ }
5295
+ throw lastError;
5296
+ }
5297
+ async function fetchTaskDetail(client, target, taskId, workerKey) {
5298
+ try {
5299
+ if (target === "FIRST_PARTY") {
5300
+ return await client.workerGetAgentTask(taskId, { workerKey });
5301
+ }
5302
+ return await client.getAgentTask(taskId);
5303
+ }
5304
+ catch (error) {
5305
+ const detail = error instanceof Error ? error.message : String(error);
5306
+ throw new Error(`agent_task_worker_detail_failed:${detail}`);
5307
+ }
5308
+ }
5309
+ function classifyWorkerSupervisorFailure(message, stage = "setup") {
5310
+ if (message.startsWith("agent_task_worker_detail_failed:")) {
5311
+ return {
5312
+ kind: "failure",
5313
+ category: "agent_failure",
5314
+ code: "agent_task_worker_detail_failed",
5315
+ message: "The worker could not refresh the task after the agent run. Retry after the platform is healthy.",
5316
+ retryable: true,
5317
+ source: "worker",
5318
+ };
5319
+ }
5320
+ const classified = (0, types_1.classifyAgentTaskTerminalReason)(message, "worker");
5321
+ const stageFailureCodes = {
5322
+ post_agent_detail: "agent_task_post_agent_detail_failed",
5323
+ game_source_reconcile: "agent_task_game_source_reconcile_failed",
5324
+ game_publish: "agent_task_game_publish_failed",
5325
+ task_complete: "agent_task_completion_failed",
5326
+ };
5327
+ const stageFailureCode = stageFailureCodes[stage];
5328
+ if (stageFailureCode && classified.category === "agent_failure" && !classified.retryable) {
5329
+ return {
5330
+ kind: "failure",
5331
+ category: "agent_failure",
5332
+ code: stageFailureCode,
5333
+ message: "The worker failed after the agent finished. Retry after the platform is healthy.",
5334
+ retryable: true,
5335
+ source: "worker",
5336
+ };
5337
+ }
5338
+ return classified;
4562
5339
  }
4563
5340
  function assignmentRequiresRegisteredBundle(assignment) {
4564
5341
  const playdropMetadata = assignment.task.metadata.playdrop;
@@ -4572,7 +5349,8 @@ function throwWorkerRuntimeError(error) {
4572
5349
  }
4573
5350
  async function startWorker(options = {}) {
4574
5351
  loadWorkerEnvFile(options.env);
4575
- await (0, commandContext_1.withEnvironment)("worker start", "Starting the PlayDrop worker", async ({ client, env, envConfig, token }) => {
5352
+ await (0, commandContext_1.withEnvironment)("worker start", "Starting the PlayDrop worker", async (ctx) => {
5353
+ const { client, env, envConfig } = ctx;
4576
5354
  const devPluginWorkingTree = resolveDevPluginWorkingTree({
4577
5355
  workingTree: options.devPluginWorkingTree,
4578
5356
  env,
@@ -4596,6 +5374,16 @@ async function startWorker(options = {}) {
4596
5374
  throw new Error('worker_session_invalid: the stored session did not resolve to a user. Run "playdrop auth login" and retry.');
4597
5375
  }
4598
5376
  const target = resolveWorkerExecutionTargetFromRole(me.user.role);
5377
+ if (target === "FIRST_PARTY" &&
5378
+ node_process_1.default.env.PLAYDROP_WORKER_WRAPPER_CONTRACT_VERSION &&
5379
+ node_process_1.default.platform === "darwin") {
5380
+ const gameServerInfrastructure = (0, game_server_infra_1.writeManagedWorkerGameServerConfiguration)({
5381
+ workerHomeDir: resolveWorkerHomeDir(),
5382
+ });
5383
+ await (0, game_server_infra_1.probeManagedWorkerGameServerInfrastructure)(gameServerInfrastructure);
5384
+ node_process_1.default.env.PLAYDROP_WORKER_GAME_SERVER_REDIS_ADMIN_URL = gameServerInfrastructure.redisAdminUrl;
5385
+ node_process_1.default.env.PLAYDROP_WORKER_GAME_SERVER_MONGO_ADMIN_URL = gameServerInfrastructure.mongoAdminUrl;
5386
+ }
4599
5387
  const initialWorkerPolicy = (await client.listMyAgentWorkers()).updatePolicy;
4600
5388
  if (initialWorkerPolicy.executionTarget !== target) {
4601
5389
  throw new Error(`worker_update_policy_target_mismatch: expected ${target}, received ${initialWorkerPolicy.executionTarget}.`);
@@ -4618,8 +5406,23 @@ async function startWorker(options = {}) {
4618
5406
  const supervisorLock = acquireWorkerSupervisorLock({ supervisorType, workerName });
4619
5407
  try {
4620
5408
  await removeStaleAgentRunCredentials();
5409
+ await (0, game_source_git_1.maintainGameSourceCaches)(resolveWorkerHomeDir()).catch((error) => {
5410
+ console.error(`Game source cache maintenance failed at startup: ${error instanceof Error ? error.message : String(error)}`);
5411
+ });
5412
+ const workerRedisAdminUrl = node_process_1.default.env.PLAYDROP_WORKER_GAME_SERVER_REDIS_ADMIN_URL?.trim() ?? "";
5413
+ const workerMongoAdminUrl = node_process_1.default.env.PLAYDROP_WORKER_GAME_SERVER_MONGO_ADMIN_URL?.trim() ?? "";
5414
+ if (target === "FIRST_PARTY" && workerRedisAdminUrl && workerMongoAdminUrl) {
5415
+ const cleaned = await (0, game_server_infra_1.cleanupStaleWorkerGameServerDevResources)({
5416
+ redisAdminUrl: workerRedisAdminUrl,
5417
+ mongoAdminUrl: workerMongoAdminUrl,
5418
+ });
5419
+ if (cleaned.redisUsers > 0 || cleaned.redisKeys > 0 || cleaned.mongoDatabases > 0) {
5420
+ console.log(`Cleaned stale game-server development resources: Redis users=${cleaned.redisUsers}, ` +
5421
+ `keys=${cleaned.redisKeys}, MongoDB databases=${cleaned.mongoDatabases}.`);
5422
+ }
5423
+ }
4621
5424
  const startupFailures = [];
4622
- const local = collectWorkerLocalStatus();
5425
+ const local = collectWorkerLocalStatus({ requireGenerationRuntime: target === "FIRST_PARTY" });
4623
5426
  startupFailures.push(...local.degradedReasons);
4624
5427
  if (startupFailures.length > 0 || !local.ready || !local.capabilities || !local.playwright?.chromiumInstalled) {
4625
5428
  throw formatWorkerPreflightFailure(startupFailures);
@@ -4632,6 +5435,14 @@ async function startWorker(options = {}) {
4632
5435
  };
4633
5436
  const maxParallelTasks = (0, runtime_1.readPositiveEnvInt)("PLAYDROP_WORKER_MAX_PARALLEL_TASKS", DEFAULT_WORKER_MAX_PARALLEL_TASKS);
4634
5437
  let capabilities = local.capabilities;
5438
+ if (target === "FIRST_PARTY" && workerRedisAdminUrl && workerMongoAdminUrl) {
5439
+ capabilities.gameServerDevelopment = {
5440
+ ready: true,
5441
+ redisVersion: "7.4.5",
5442
+ mongoVersion: "8.0.26",
5443
+ maxParallelTasks,
5444
+ };
5445
+ }
4635
5446
  let lastCapabilityRefreshAt = Date.now();
4636
5447
  let lastProviderTelemetryRefreshAt = 0;
4637
5448
  let providerTelemetryRefresh = null;
@@ -4652,6 +5463,8 @@ async function startWorker(options = {}) {
4652
5463
  let shutdownSignal = null;
4653
5464
  let fatalControlError = null;
4654
5465
  let presenceTimer = null;
5466
+ let authRefreshTimer = null;
5467
+ let durableAuthInvalid = false;
4655
5468
  let currentWorkerPolicy = initialWorkerPolicy;
4656
5469
  const firstActiveTaskId = () => activeTaskIds.values().next().value ?? null;
4657
5470
  const terminateActiveTasks = () => {
@@ -4665,7 +5478,7 @@ async function startWorker(options = {}) {
4665
5478
  });
4666
5479
  const beginProviderTelemetryRefresh = () => {
4667
5480
  if (providerTelemetryRefresh)
4668
- return;
5481
+ return providerTelemetryRefresh;
4669
5482
  providerTelemetryRefresh = (0, provider_telemetry_1.probeWorkerProviderTelemetry)(capabilities)
4670
5483
  .then((telemetry) => {
4671
5484
  capabilities = (0, provider_telemetry_1.mergeWorkerProviderTelemetry)(capabilities, telemetry);
@@ -4678,6 +5491,7 @@ async function startWorker(options = {}) {
4678
5491
  lastProviderTelemetryRefreshAt = Date.now();
4679
5492
  providerTelemetryRefresh = null;
4680
5493
  });
5494
+ return providerTelemetryRefresh;
4681
5495
  };
4682
5496
  const presenceBody = () => ({
4683
5497
  workerKey,
@@ -4747,7 +5561,7 @@ async function startWorker(options = {}) {
4747
5561
  node_process_1.default.stdin.resume();
4748
5562
  }
4749
5563
  console.log(`PlayDrop Worker started as "${workerName}" (${target}).`);
4750
- beginProviderTelemetryRefresh();
5564
+ await beginProviderTelemetryRefresh();
4751
5565
  // Marks the session expired, kills any in-flight agent child, and lets the
4752
5566
  // run loop finish the task lifecycle before the worker stops non-zero.
4753
5567
  const handleSessionExpiry = () => {
@@ -4758,6 +5572,17 @@ async function startWorker(options = {}) {
4758
5572
  console.error(exports.WORKER_SESSION_EXPIRED_MESSAGE);
4759
5573
  terminateActiveTasks();
4760
5574
  };
5575
+ const quiesceForDurableAuthFailure = (error) => {
5576
+ if (durableAuthInvalid)
5577
+ return;
5578
+ durableAuthInvalid = true;
5579
+ quiescingForUpdate = true;
5580
+ console.error(error instanceof Error ? error.message : String(error));
5581
+ console.error(exports.WORKER_SESSION_EXPIRED_MESSAGE);
5582
+ };
5583
+ authRefreshTimer = setInterval(() => {
5584
+ ctx.refreshToken(false).catch(quiesceForDurableAuthFailure);
5585
+ }, 60000);
4761
5586
  try {
4762
5587
  handleWorkerAction(await client.workerPresence(presenceBody()));
4763
5588
  }
@@ -4809,7 +5634,14 @@ async function startWorker(options = {}) {
4809
5634
  let agentCompletedAt = null;
4810
5635
  let agentResult = null;
4811
5636
  let telemetryReported = false;
5637
+ let gameServerDevLease = null;
5638
+ let gameSourceWorkspace = null;
5639
+ let transcriptSpoolPath = null;
5640
+ let transcriptSpoolSequence = 0;
4812
5641
  let heartbeatTransientFailures = 0;
5642
+ let automaticContinuationKind = null;
5643
+ let automaticContinuationAttempted = false;
5644
+ let workerFailureStage = "setup";
4813
5645
  const sendTaskHeartbeat = async () => {
4814
5646
  try {
4815
5647
  const heartbeat = await client.workerHeartbeatAgentTask(task.id, { workerKey, leaseToken });
@@ -4830,7 +5662,7 @@ async function startWorker(options = {}) {
4830
5662
  clearInterval(heartbeatTimer);
4831
5663
  heartbeatTimer = null;
4832
5664
  }
4833
- const refreshed = await fetchTaskDetail(client, target, task.id).catch(() => null);
5665
+ const refreshed = await fetchTaskDetail(client, target, task.id, workerKey).catch(() => null);
4834
5666
  if (refreshed?.task.status === "DONE") {
4835
5667
  fenced = true;
4836
5668
  if (!heartbeatObservedFinalizedTask) {
@@ -4859,7 +5691,7 @@ async function startWorker(options = {}) {
4859
5691
  console.error(`Agent task heartbeat failed: ${error instanceof Error ? error.message : String(error)}`);
4860
5692
  }
4861
5693
  };
4862
- const reportTelemetry = async (status, setupError) => {
5694
+ const reportTelemetry = async (status, setupError, terminalReason) => {
4863
5695
  const completedAt = agentCompletedAt ?? new Date();
4864
5696
  await recordAgentRunTelemetry({
4865
5697
  client,
@@ -4875,11 +5707,20 @@ async function startWorker(options = {}) {
4875
5707
  observedSkillPaths: [...observedSkillPaths],
4876
5708
  startedAt: agentStartedAt ?? completedAt,
4877
5709
  completedAt,
5710
+ ...(terminalReason ? { terminalReason } : {}),
4878
5711
  });
4879
5712
  telemetryReported = true;
4880
5713
  };
4881
5714
  const failUploadedArtifactWithoutTaskDone = async () => {
4882
5715
  const failureCode = "agent_exited_after_upload_without_task_done";
5716
+ const terminalReason = {
5717
+ kind: "failure",
5718
+ category: "agent_failure",
5719
+ code: failureCode,
5720
+ message: "The game was uploaded, but the task could not be finalized. Clear this error to continue.",
5721
+ retryable: false,
5722
+ source: "worker",
5723
+ };
4883
5724
  retainWorkspace = true;
4884
5725
  await client.workerCreateAgentTaskEvent(task.id, {
4885
5726
  workerKey,
@@ -4894,21 +5735,73 @@ async function startWorker(options = {}) {
4894
5735
  workerKey,
4895
5736
  leaseToken,
4896
5737
  error: failureCode,
4897
- terminalReason: {
4898
- kind: "failure",
4899
- category: "agent_failure",
4900
- code: failureCode,
4901
- message: "The agent uploaded an artifact but did not complete the task protocol.",
4902
- retryable: false,
4903
- rawError: failureCode,
4904
- source: "agent",
4905
- },
4906
- result: {
4907
- stage: "completion",
4908
- uploadedArtifactPreserved: true,
4909
- },
5738
+ terminalReason,
5739
+ });
5740
+ await reportTelemetry("FAILED", undefined, terminalReason);
5741
+ };
5742
+ const readRecordedTaskUploadFailure = () => {
5743
+ if (!workspaceDir)
5744
+ return null;
5745
+ const failure = readTaskUploadFailureFile(workspaceDir);
5746
+ if (!failure)
5747
+ return null;
5748
+ if (failure.taskId !== task.id || failure.attempt !== taskContext.attempt) {
5749
+ throw new Error("task_upload_failure_context_mismatch");
5750
+ }
5751
+ return failure;
5752
+ };
5753
+ const hasReportedGameSourceCompletion = () => {
5754
+ if (!assignment.gameSource || task.kind === "STATIC_GAME" || !workspaceDir) {
5755
+ return false;
5756
+ }
5757
+ const intent = readWorkerGameSourceCompletion(workspaceDir);
5758
+ return intent?.taskId === task.id && intent.attempt === taskContext.attempt;
5759
+ };
5760
+ const taskStillMissingAgentOutcome = (detail) => detail.task.status === "RUNNING" &&
5761
+ task.kind !== "STATIC_GAME" &&
5762
+ task.kind !== "RELEASE_SMOKE" &&
5763
+ !hasReportedGameSourceCompletion();
5764
+ const failMissingAgentOutcome = async (runResult) => {
5765
+ const terminalReason = missingAgentOutcomeTerminalReason();
5766
+ retainWorkspace = true;
5767
+ await client.workerCreateAgentTaskEvent(task.id, {
5768
+ workerKey,
5769
+ leaseToken,
5770
+ kind: "progress",
5771
+ phase: "failed",
5772
+ message: terminalReason.message,
5773
+ pct: null,
5774
+ payload: { error: terminalReason.code },
5775
+ });
5776
+ await failTaskWithRetry(client, task.id, {
5777
+ workerKey,
5778
+ leaseToken,
5779
+ error: terminalReason.code,
5780
+ terminalReason,
5781
+ result: runResult,
5782
+ });
5783
+ await reportTelemetry("FAILED", undefined, terminalReason);
5784
+ };
5785
+ const failCreationTimeout = async (runResult) => {
5786
+ const terminalReason = creationTimeoutTerminalReason();
5787
+ retainWorkspace = true;
5788
+ await client.workerCreateAgentTaskEvent(task.id, {
5789
+ workerKey,
5790
+ leaseToken,
5791
+ kind: "progress",
5792
+ phase: "failed",
5793
+ message: terminalReason.message,
5794
+ pct: null,
5795
+ payload: { error: terminalReason.code },
5796
+ });
5797
+ await failTaskWithRetry(client, task.id, {
5798
+ workerKey,
5799
+ leaseToken,
5800
+ error: terminalReason.code,
5801
+ terminalReason,
5802
+ result: runResult,
4910
5803
  });
4911
- await reportTelemetry("FAILED");
5804
+ await reportTelemetry("FAILED", undefined, terminalReason);
4912
5805
  };
4913
5806
  try {
4914
5807
  await sendTaskHeartbeat();
@@ -4936,6 +5829,25 @@ async function startWorker(options = {}) {
4936
5829
  codexModel?.model ?? claudeModel?.model ?? grokModel?.model ?? assignment.agent.model;
4937
5830
  reasoningEffort = codexModel?.reasoningEffort ?? claudeModel?.effort ?? grokModel?.reasoningEffort ?? null;
4938
5831
  const cleanRoom = task.kind === "STATIC_GAME";
5832
+ if (task.kind === "NEW_GAME" || task.kind === "REMIX_GAME" || task.kind === "GAME_UPDATE") {
5833
+ const managedRedisAdminUrl = node_process_1.default.env.PLAYDROP_WORKER_GAME_SERVER_REDIS_ADMIN_URL?.trim() ?? "";
5834
+ const managedMongoAdminUrl = node_process_1.default.env.PLAYDROP_WORKER_GAME_SERVER_MONGO_ADMIN_URL?.trim() ?? "";
5835
+ const redisAdminUrl = managedRedisAdminUrl || node_process_1.default.env.PLAYDROP_DEV_REDIS_URL?.trim() || "";
5836
+ const mongoAdminUrl = managedMongoAdminUrl || node_process_1.default.env.PLAYDROP_DEV_MONGO_URL?.trim() || "";
5837
+ if (target === "FIRST_PARTY" && (!redisAdminUrl || !mongoAdminUrl)) {
5838
+ throw new Error("worker_game_server_dev_infrastructure_missing");
5839
+ }
5840
+ if (redisAdminUrl && mongoAdminUrl) {
5841
+ gameServerDevLease = await (0, game_server_infra_1.provisionWorkerGameServerDevLease)({
5842
+ taskId: task.id,
5843
+ attempt: taskContext.attempt,
5844
+ environment: env,
5845
+ redisAdminUrl,
5846
+ mongoAdminUrl,
5847
+ requireRedisAcl: Boolean(managedRedisAdminUrl),
5848
+ });
5849
+ }
5850
+ }
4939
5851
  workspaceDir = workerTaskWorkspaceDirFromAssignment(assignment);
4940
5852
  const preparedWorkspace = await prepareClaimedTaskWorkspace({
4941
5853
  workspaceDir,
@@ -4944,11 +5856,30 @@ async function startWorker(options = {}) {
4944
5856
  if (preparedWorkspace.resumed) {
4945
5857
  console.log(`Agent task ${task.id} retry resumed retained workspace attempt ${task.attempts}.`);
4946
5858
  }
4947
- agentWorkspaceDir = cleanRoom ? node_path_1.default.join(workspaceDir, "project") : workspaceDir;
5859
+ if (assignment.gameSource) {
5860
+ gameSourceWorkspace = await (0, game_source_git_1.setupGameSourceWorkspace)({
5861
+ assignment,
5862
+ workspaceDir,
5863
+ workerHomeDir: resolveWorkerHomeDir(),
5864
+ client,
5865
+ workerKey,
5866
+ leaseToken,
5867
+ });
5868
+ }
5869
+ agentWorkspaceDir = gameSourceWorkspace?.projectDir ?? (cleanRoom ? node_path_1.default.join(workspaceDir, "project") : workspaceDir);
4948
5870
  await (0, promises_1.mkdir)(agentWorkspaceDir, { recursive: true });
4949
5871
  const eventDir = node_path_1.default.join(workspaceDir, ".playdrop", "task-events");
4950
5872
  await (0, promises_1.mkdir)(eventDir, { recursive: true });
4951
- if (!cleanRoom) {
5873
+ transcriptSpoolPath = node_path_1.default.join(workspaceDir, ".playdrop", `transcript-attempt-${taskContext.attempt}.jsonl`);
5874
+ if (!(0, node_fs_1.existsSync)(transcriptSpoolPath)) {
5875
+ await (0, promises_1.writeFile)(transcriptSpoolPath, `${JSON.stringify({
5876
+ version: 1,
5877
+ taskId: task.id,
5878
+ attempt: taskContext.attempt,
5879
+ createdAt: new Date().toISOString(),
5880
+ })}\n`, "utf8");
5881
+ }
5882
+ if (!cleanRoom || assignment.gameSource) {
4952
5883
  await stageWorkerTaskContext({
4953
5884
  workspaceDir,
4954
5885
  env,
@@ -4963,6 +5894,7 @@ async function startWorker(options = {}) {
4963
5894
  taskId: taskContext.taskId,
4964
5895
  attempt: taskContext.attempt,
4965
5896
  devPort,
5897
+ gameSourceGit: Boolean(assignment.gameSource),
4966
5898
  });
4967
5899
  const handleEventDrainFailure = (error) => {
4968
5900
  const classification = classifyWorkerEventDrainError(error);
@@ -4991,11 +5923,20 @@ async function startWorker(options = {}) {
4991
5923
  }
4992
5924
  }
4993
5925
  };
4994
- const appendTranscriptChunks = async (chunks) => {
5926
+ const appendTranscriptSpool = async (chunks) => {
4995
5927
  appendObservedSkillPathsFromTranscript(chunks);
4996
- await appendWorkerTranscriptChunks({ client, taskId: task.id, workerKey, leaseToken, chunks });
5928
+ if (transcriptSpoolPath) {
5929
+ const lines = chunks.map((chunk) => JSON.stringify({
5930
+ sequence: ++transcriptSpoolSequence,
5931
+ stream: chunk.stream,
5932
+ content: chunk.content,
5933
+ createdAt: new Date().toISOString(),
5934
+ })).join("\n");
5935
+ if (lines)
5936
+ await (0, promises_1.appendFile)(transcriptSpoolPath, `${lines}\n`, "utf8");
5937
+ }
4997
5938
  };
4998
- await stageAssignmentWorkspaceV3(agentWorkspaceDir, assignment.workspace);
5939
+ await stageAssignmentWorkspace(agentWorkspaceDir, assignment.workspace);
4999
5940
  if (fenced) {
5000
5941
  throw new Error("agent_task_lease_invalid");
5001
5942
  }
@@ -5030,7 +5971,7 @@ async function startWorker(options = {}) {
5030
5971
  }
5031
5972
  const taskClient = (0, apiClient_1.createCliApiClient)({
5032
5973
  baseUrl: envConfig.apiBase,
5033
- token,
5974
+ tokenProvider: ctx.getToken,
5034
5975
  onBehalfCreatorUsername: taskContext.creatorUsername,
5035
5976
  agentTaskToken: assignment.task.token,
5036
5977
  agentTaskId: task.id,
@@ -5047,7 +5988,7 @@ async function startWorker(options = {}) {
5047
5988
  webBase: envConfig.webBase ?? null,
5048
5989
  cdnBase: envConfig.cdnBase,
5049
5990
  devRouterPort: devPort,
5050
- token,
5991
+ token: ctx.getToken(),
5051
5992
  user: me.user,
5052
5993
  skipReview: true,
5053
5994
  });
@@ -5105,7 +6046,7 @@ async function startWorker(options = {}) {
5105
6046
  }, 1000);
5106
6047
  const gameCreationTask = usesCreatorRecovery(task.kind);
5107
6048
  const startsInCreationRecovery = gameCreationTask && preparedWorkspace.resumed;
5108
- const prompt = startsInCreationRecovery
6049
+ const basePrompt = startsInCreationRecovery
5109
6050
  ? buildCreatorRecoveryPrompt({
5110
6051
  prompt: assignment.request.prompt,
5111
6052
  kind: task.kind,
@@ -5113,6 +6054,7 @@ async function startWorker(options = {}) {
5113
6054
  : task.kind === "GAME_EVAL" && preparedWorkspace.resumed
5114
6055
  ? buildGameEvalRetryPrompt(assignment.request.prompt)
5115
6056
  : assignment.request.prompt;
6057
+ const prompt = basePrompt;
5116
6058
  const imageAttachments = assignment.request.attachments
5117
6059
  .filter((attachment) => attachment.contentType.startsWith("image/"))
5118
6060
  .map((attachment) => ({
@@ -5144,6 +6086,28 @@ async function startWorker(options = {}) {
5144
6086
  }
5145
6087
  };
5146
6088
  const runAssignedAgent = async (input) => {
6089
+ const fixtureKind = (0, e2e_fixtures_1.detectPlaydropE2EFixture)(assignment.request.prompt);
6090
+ if (fixtureKind) {
6091
+ if (!(0, e2e_fixtures_1.isPlaydropE2EGameKind)(task.kind) || fixtureKind !== task.kind) {
6092
+ throw new Error(`playdrop_e2e_marker_kind_mismatch:expected_${task.kind}:received_${fixtureKind}`);
6093
+ }
6094
+ if (!assignment.gameSource || !gameSourceWorkspace || !workspaceDir || !agentWorkspaceDir) {
6095
+ throw new Error("playdrop_e2e_game_source_git_required");
6096
+ }
6097
+ return runPlaydropE2EGameFixture({
6098
+ projectDir: agentWorkspaceDir,
6099
+ workspaceDir,
6100
+ binDir,
6101
+ eventDir,
6102
+ envName: env,
6103
+ devPort,
6104
+ workerUsername: username,
6105
+ taskContext,
6106
+ gameServerDev: gameServerDevLease,
6107
+ onTranscriptChunks: appendTranscriptSpool,
6108
+ onChild: handleAgentChild,
6109
+ });
6110
+ }
5147
6111
  if (assignment.agent.runtime === "CODEX") {
5148
6112
  return runCodex({
5149
6113
  workspaceDir: agentWorkspaceDir,
@@ -5155,18 +6119,21 @@ async function startWorker(options = {}) {
5155
6119
  workerUsername: username,
5156
6120
  taskId: task.id,
5157
6121
  taskToken: assignment.task.token,
6122
+ taskAccessToken: assignment.task.accessToken ?? null,
5158
6123
  attempt: taskContext.attempt,
5159
6124
  devPort,
6125
+ gameServerDev: gameServerDevLease,
5160
6126
  codexModel: codexModel ?? resolveCodexModel(assignment.agent.model),
5161
6127
  taskPluginRoot: assignmentPluginRoot,
5162
6128
  enablePlaywrightMcp: assignment.agent.browser === "PLAYWRIGHT",
5163
6129
  instrumentSurface: resolveWorkerInstrumentSurface(taskContext),
5164
6130
  imagePaths: imageAttachments.map((attachment) => attachment.path),
5165
6131
  onTranscriptChunks: async (chunks) => {
5166
- await appendTranscriptChunks(chunks);
6132
+ await appendTranscriptSpool(chunks);
5167
6133
  },
5168
6134
  onChild: handleAgentChild,
5169
6135
  cleanRoom,
6136
+ allowGit: Boolean(assignment.gameSource),
5170
6137
  timeoutMs: input.timeoutMs,
5171
6138
  resumeSessionId: input.resumeSessionId,
5172
6139
  });
@@ -5185,16 +6152,19 @@ async function startWorker(options = {}) {
5185
6152
  workerUsername: username,
5186
6153
  taskId: task.id,
5187
6154
  taskToken: assignment.task.token,
6155
+ taskAccessToken: assignment.task.accessToken ?? null,
5188
6156
  attempt: taskContext.attempt,
5189
6157
  devPort,
6158
+ gameServerDev: gameServerDevLease,
5190
6159
  claudeModel: claudeModel ?? resolveClaudeModel(assignment.agent.model),
5191
6160
  taskPluginRoot: assignmentPluginRoot,
5192
6161
  imageAttachments,
5193
6162
  onTranscriptChunks: async (chunks) => {
5194
- await appendTranscriptChunks(chunks);
6163
+ await appendTranscriptSpool(chunks);
5195
6164
  },
5196
6165
  onChild: handleAgentChild,
5197
6166
  cleanRoom,
6167
+ allowGit: Boolean(assignment.gameSource),
5198
6168
  timeoutMs: input.timeoutMs,
5199
6169
  resumeSessionId: input.resumeSessionId,
5200
6170
  });
@@ -5213,15 +6183,17 @@ async function startWorker(options = {}) {
5213
6183
  workerUsername: username,
5214
6184
  taskId: task.id,
5215
6185
  taskToken: assignment.task.token,
6186
+ taskAccessToken: assignment.task.accessToken ?? null,
5216
6187
  attempt: taskContext.attempt,
5217
6188
  devPort,
6189
+ gameServerDev: gameServerDevLease,
5218
6190
  grokModel: grokModel ?? resolveGrokModel(assignment.agent.model),
5219
6191
  taskPluginRoot: assignmentPluginRoot,
5220
6192
  imageAttachments,
5221
6193
  enablePlaywrightMcp: assignment.agent.browser === "PLAYWRIGHT",
5222
6194
  instrumentSurface: resolveWorkerInstrumentSurface(taskContext),
5223
6195
  onTranscriptChunks: async (chunks) => {
5224
- await appendTranscriptChunks(chunks);
6196
+ await appendTranscriptSpool(chunks);
5225
6197
  },
5226
6198
  onChild: handleAgentChild,
5227
6199
  cleanRoom,
@@ -5251,6 +6223,7 @@ async function startWorker(options = {}) {
5251
6223
  message: "Resumed the retained game workspace with a focused creation recovery window.",
5252
6224
  });
5253
6225
  }
6226
+ workerFailureStage = "agent";
5254
6227
  agentResult = await runAssignedAgent({
5255
6228
  prompt,
5256
6229
  timeoutMs: startsInCreationRecovery
@@ -5259,6 +6232,7 @@ async function startWorker(options = {}) {
5259
6232
  ? runtime_1.DEFAULT_CREATOR_AGENT_TIMEOUT_MS
5260
6233
  : undefined,
5261
6234
  });
6235
+ workerFailureStage = "post_agent_detail";
5262
6236
  if (startsInCreationRecovery && agentResult.timedOut) {
5263
6237
  const marker = "agent_task_creation_recovery_timeout";
5264
6238
  agentResult = {
@@ -5269,9 +6243,10 @@ async function startWorker(options = {}) {
5269
6243
  }
5270
6244
  if (gameCreationTask && !startsInCreationRecovery && agentResult.timedOut && !fenced && !shuttingDown) {
5271
6245
  const timedOutResult = agentResult;
5272
- const refreshedAfterTimeout = await fetchTaskDetail(client, target, task.id);
6246
+ const refreshedAfterTimeout = await fetchTaskDetail(client, target, task.id, workerKey);
5273
6247
  if (refreshedAfterTimeout.task.status === "RUNNING" &&
5274
6248
  !hasAgentTaskUploadedArtifact(refreshedAfterTimeout.task)) {
6249
+ automaticContinuationKind = "timeout";
5275
6250
  const providerSessionId = timedOutResult.providerSessionId?.trim() ?? "";
5276
6251
  if (!providerSessionId) {
5277
6252
  const marker = "agent_task_resume_session_missing";
@@ -5282,6 +6257,7 @@ async function startWorker(options = {}) {
5282
6257
  };
5283
6258
  }
5284
6259
  else {
6260
+ automaticContinuationAttempted = true;
5285
6261
  await client.workerCreateAgentTaskEvent(task.id, {
5286
6262
  workerKey,
5287
6263
  leaseToken,
@@ -5301,15 +6277,67 @@ async function startWorker(options = {}) {
5301
6277
  }
5302
6278
  }
5303
6279
  }
6280
+ if (!automaticContinuationKind) {
6281
+ const refreshedAfterExit = await fetchTaskDetail(client, target, task.id, workerKey);
6282
+ if (shouldRecoverMissingAgentOutcome({
6283
+ kind: task.kind,
6284
+ taskStatus: refreshedAfterExit.task.status,
6285
+ completionReported: hasReportedGameSourceCompletion(),
6286
+ result: agentResult,
6287
+ })) {
6288
+ automaticContinuationKind = "missing_outcome";
6289
+ const initialResult = agentResult;
6290
+ const providerSessionId = initialResult.providerSessionId?.trim() ?? "";
6291
+ if (!providerSessionId) {
6292
+ const marker = "agent_task_outcome_recovery_session_missing";
6293
+ agentResult = {
6294
+ ...initialResult,
6295
+ stderr: `${initialResult.stderr}\n${marker}`.trim(),
6296
+ outputTail: `${initialResult.outputTail}\n${marker}`.trim(),
6297
+ };
6298
+ }
6299
+ else {
6300
+ automaticContinuationAttempted = true;
6301
+ await client.workerCreateAgentTaskEvent(task.id, {
6302
+ workerKey,
6303
+ leaseToken,
6304
+ kind: "progress",
6305
+ phase: "outcome_recovery",
6306
+ message: "The agent stopped before confirming the result. Asking it to resume and finish the task.",
6307
+ });
6308
+ const recoveryResult = await runAssignedAgent({
6309
+ prompt: buildMissingAgentOutcomeRecoveryPrompt(task.kind),
6310
+ timeoutMs: runtime_1.DEFAULT_CREATOR_UPLOAD_RECOVERY_TIMEOUT_MS,
6311
+ resumeSessionId: providerSessionId,
6312
+ });
6313
+ agentResult = mergeMissingAgentOutcomeRecoveryResults(assignment.agent.runtime, initialResult, recoveryResult);
6314
+ }
6315
+ }
6316
+ }
5304
6317
  resolvedRuntimeModel = agentResult.providerResolvedModel?.trim() || resolvedRuntimeModel;
5305
6318
  agentCompletedAt = new Date();
5306
6319
  await queueEventDrain().catch((error) => {
5307
6320
  handleEventDrainFailure(error);
5308
6321
  });
6322
+ void archiveWorkerTranscriptBestEffort({
6323
+ client,
6324
+ taskId: task.id,
6325
+ attempt: taskContext.attempt,
6326
+ workerKey,
6327
+ leaseToken,
6328
+ transcriptPath: transcriptSpoolPath,
6329
+ });
5309
6330
  const completedAgentResult = agentResult;
5310
6331
  const agentRunResult = buildAgentRunResult(completedAgentResult);
6332
+ if (assignment.gameSource &&
6333
+ gameSourceWorkspace &&
6334
+ (completedAgentResult.timedOut || completedAgentResult.exitCode !== 0 || completedAgentResult.signal)) {
6335
+ await (0, game_source_git_1.checkpointFailedGameSourceWorkspace)({ workspace: gameSourceWorkspace, taskId: task.id }).catch((error) => {
6336
+ console.error(`Failed Git workspace checkpoint did not complete: ${error instanceof Error ? error.message : String(error)}`);
6337
+ });
6338
+ }
5311
6339
  if (fenced) {
5312
- const refreshed = await fetchTaskDetail(client, target, task.id).catch(() => null);
6340
+ const refreshed = await fetchTaskDetail(client, target, task.id, workerKey).catch(() => null);
5313
6341
  if (refreshed && refreshed.task.status !== "RUNNING" && refreshed.task.status !== "QUEUED") {
5314
6342
  retainWorkspace = refreshed.task.status === "FAILED";
5315
6343
  console.error(`Agent task ${task.id} was finalized by the server as ${refreshed.task.status}.`);
@@ -5330,18 +6358,20 @@ async function startWorker(options = {}) {
5330
6358
  });
5331
6359
  }
5332
6360
  else if (shuttingDown) {
6361
+ const terminalReason = (0, types_1.classifyAgentTaskTerminalReason)("worker_shutdown", "worker");
5333
6362
  await failTaskWithRetry(client, task.id, {
5334
6363
  workerKey,
5335
6364
  leaseToken,
5336
6365
  error: "worker_shutdown",
6366
+ terminalReason,
5337
6367
  result: agentRunResult,
5338
6368
  });
5339
- await reportTelemetry("FAILED");
6369
+ await reportTelemetry("FAILED", undefined, terminalReason);
5340
6370
  }
5341
6371
  else if (completedAgentResult.timedOut ||
5342
6372
  completedAgentResult.exitCode !== 0 ||
5343
6373
  completedAgentResult.signal) {
5344
- const refreshed = await fetchTaskDetail(client, target, task.id);
6374
+ const refreshed = await fetchTaskDetail(client, target, task.id, workerKey);
5345
6375
  if (refreshed.task.status !== "RUNNING") {
5346
6376
  retainWorkspace = refreshed.task.status === "FAILED";
5347
6377
  console.error(`Agent task ${task.id} was finalized by the agent as ${refreshed.task.status}.`);
@@ -5351,19 +6381,32 @@ async function startWorker(options = {}) {
5351
6381
  console.error(`Agent task ${task.id} uploaded an artifact but exited before task completion; failing it explicitly.`);
5352
6382
  await failUploadedArtifactWithoutTaskDone();
5353
6383
  }
6384
+ else if (automaticContinuationKind === "missing_outcome" &&
6385
+ taskStillMissingAgentOutcome(refreshed)) {
6386
+ await failMissingAgentOutcome(agentRunResult);
6387
+ }
6388
+ else if (automaticContinuationKind === "timeout" &&
6389
+ taskStillMissingAgentOutcome(refreshed) &&
6390
+ !automaticContinuationAttempted) {
6391
+ await failCreationTimeout(agentRunResult);
6392
+ }
5354
6393
  else {
5355
6394
  retainWorkspace = true;
5356
- const failureCode = buildAgentFailureCode(assignment.agent.runtime, completedAgentResult, {
5357
- model: assignment.agent.model,
5358
- reasoningEffort,
5359
- });
5360
- const availabilityReason = classifyAgentAvailabilityFailureReason(failureCode);
6395
+ const uploadFailure = readRecordedTaskUploadFailure();
6396
+ const failureCode = uploadFailure?.terminalReason.code ??
6397
+ buildAgentFailureCode(assignment.agent.runtime, completedAgentResult, {
6398
+ model: assignment.agent.model,
6399
+ reasoningEffort,
6400
+ });
6401
+ const availabilityReason = uploadFailure
6402
+ ? null
6403
+ : classifyAgentAvailabilityFailureReason(failureCode, task.kind === "GAME_REVIEW");
5361
6404
  await client.workerCreateAgentTaskEvent(task.id, {
5362
6405
  workerKey,
5363
6406
  leaseToken,
5364
6407
  kind: "progress",
5365
6408
  phase: "failed",
5366
- message: describeAgentFailureForEvent(failureCode),
6409
+ message: uploadFailure?.terminalReason.message ?? describeAgentFailureForEvent(failureCode),
5367
6410
  pct: null,
5368
6411
  payload: { error: failureCode },
5369
6412
  });
@@ -5388,7 +6431,9 @@ async function startWorker(options = {}) {
5388
6431
  workerKey,
5389
6432
  leaseToken,
5390
6433
  error: failureCode,
5391
- terminalReason: (0, types_1.classifyAgentTaskTerminalReason)(failureCode, "agent"),
6434
+ terminalReason: uploadFailure
6435
+ ? uploadFailure.terminalReason
6436
+ : (0, types_1.classifyAgentTaskTerminalReason)(failureCode, "agent"),
5392
6437
  result: agentRunResult,
5393
6438
  });
5394
6439
  await reportTelemetry("FAILED");
@@ -5396,18 +6441,237 @@ async function startWorker(options = {}) {
5396
6441
  }
5397
6442
  }
5398
6443
  else {
5399
- const refreshed = await fetchTaskDetail(client, target, task.id);
6444
+ const refreshed = await fetchTaskDetail(client, target, task.id, workerKey);
5400
6445
  if (refreshed.task.status !== "RUNNING") {
5401
6446
  retainWorkspace = refreshed.task.status === "FAILED";
5402
6447
  console.error(`Agent task ${task.id} was finalized by the agent as ${refreshed.task.status}.`);
5403
6448
  await reportTelemetry(refreshed.task.status);
5404
6449
  }
5405
- else if (task.kind === "STATIC_GAME") {
5406
- const tokenCap = (0, runtime_1.readPositiveEnvInt)("PLAYDROP_WORKER_TOKEN_CAP", runtime_1.DEFAULT_WORKER_TOKEN_CAP);
5407
- (0, runtime_1.assertWorkerTokenUsageWithinCap)({
5408
- tokensUsed: agentResult.tokensUsed,
5409
- tokenCap,
6450
+ else if (taskStillMissingAgentOutcome(refreshed) &&
6451
+ hasAgentTaskUploadedArtifact(refreshed.task)) {
6452
+ console.error(`Agent task ${task.id} uploaded an artifact but exited before task completion; failing it explicitly.`);
6453
+ await failUploadedArtifactWithoutTaskDone();
6454
+ }
6455
+ else if (automaticContinuationKind === "timeout" &&
6456
+ taskStillMissingAgentOutcome(refreshed)) {
6457
+ await failCreationTimeout(agentRunResult);
6458
+ }
6459
+ else if (automaticContinuationKind === "missing_outcome" &&
6460
+ taskStillMissingAgentOutcome(refreshed)) {
6461
+ await failMissingAgentOutcome(agentRunResult);
6462
+ }
6463
+ else if (assignment.gameSource) {
6464
+ if (!gameSourceWorkspace || !workspaceDir || !agentWorkspaceDir) {
6465
+ throw new Error("game_source_workspace_missing");
6466
+ }
6467
+ const intent = task.kind === "STATIC_GAME"
6468
+ ? {
6469
+ taskId: task.id,
6470
+ attempt: taskContext.attempt,
6471
+ summary: "Created and privately hosted a standalone static web game.",
6472
+ }
6473
+ : readWorkerGameSourceCompletion(workspaceDir);
6474
+ if (!intent || intent.taskId !== task.id || intent.attempt !== taskContext.attempt) {
6475
+ throw new Error("agent_exited_without_task_done");
6476
+ }
6477
+ if (task.kind !== "STATIC_GAME" && !(0, node_fs_1.existsSync)(workerGameSourceReadinessPath(workspaceDir))) {
6478
+ throw new Error("agent_exited_without_task_upload");
6479
+ }
6480
+ if (task.kind === "STATIC_GAME" && !(0, node_fs_1.existsSync)(node_path_1.default.join(agentWorkspaceDir, "catalogue.json"))) {
6481
+ const primarySurface = resolveWorkerInstrumentSurface(taskContext);
6482
+ if (!primarySurface)
6483
+ throw new Error("static_game_artifact_primary_surface_missing");
6484
+ if (!envConfig.cdnBase)
6485
+ throw new Error("static_game_publish_cdn_base_missing");
6486
+ const preparedStatic = await (0, staticHtml_1.prepareStaticHtmlProject)({
6487
+ projectDir: agentWorkspaceDir,
6488
+ taskId: task.id,
6489
+ appName: refreshed.task.claimedAppName ?? undefined,
6490
+ primarySurface,
6491
+ cdnBase: envConfig.cdnBase,
6492
+ });
6493
+ try {
6494
+ const sourceTask = preparedStatic.sourceTask;
6495
+ await (0, promises_1.writeFile)(node_path_1.default.join(agentWorkspaceDir, "catalogue.json"), `${JSON.stringify({
6496
+ apps: [{
6497
+ name: sourceTask.name,
6498
+ version: sourceTask.version,
6499
+ displayName: sourceTask.displayName,
6500
+ subtitle: sourceTask.subtitle,
6501
+ description: sourceTask.description,
6502
+ type: "GAME",
6503
+ authMode: "NONE",
6504
+ controllerMode: "UNSUPPORTED",
6505
+ previewable: true,
6506
+ file: "index.html",
6507
+ primarySurface,
6508
+ surfaceTargets: {
6509
+ desktop: sourceTask.surfaceTargets.includes("DESKTOP"),
6510
+ mobileLandscape: sourceTask.surfaceTargets.includes("MOBILE_LANDSCAPE"),
6511
+ mobilePortrait: sourceTask.surfaceTargets.includes("MOBILE_PORTRAIT"),
6512
+ },
6513
+ releaseNotes: sourceTask.releaseNotes,
6514
+ hostingMode: "HOSTED",
6515
+ license: "CLOSED",
6516
+ }],
6517
+ }, null, 2)}\n`, "utf8");
6518
+ }
6519
+ finally {
6520
+ await preparedStatic.cleanup();
6521
+ }
6522
+ }
6523
+ const outputProjectDir = discoverWorkerProjectRoot(agentWorkspaceDir);
6524
+ if (node_path_1.default.resolve(outputProjectDir) !== node_path_1.default.resolve(gameSourceWorkspace.projectDir)) {
6525
+ throw new Error("game_source_output_must_be_repository_root");
6526
+ }
6527
+ workerFailureStage = "game_source_reconcile";
6528
+ const reconciled = await (0, game_source_git_1.reconcileGameSourceWorkspace)({
6529
+ workspace: gameSourceWorkspace,
6530
+ taskId: task.id,
6531
+ summary: intent.summary,
6532
+ });
6533
+ const taskClient = (0, apiClient_1.createCliApiClient)({
6534
+ baseUrl: envConfig.apiBase,
6535
+ tokenProvider: ctx.getToken,
6536
+ onBehalfCreatorUsername: taskContext.creatorUsername,
6537
+ agentTaskToken: assignment.task.token,
6538
+ agentTaskId: task.id,
6539
+ agentTaskAttempt: taskContext.attempt,
6540
+ });
6541
+ let published;
6542
+ let finalizedGameSource = null;
6543
+ let preparedGameSource = null;
6544
+ const prepareGitTransition = async (input) => {
6545
+ const versionParts = input.version.split(".").map((part) => Number(part));
6546
+ if (versionParts.length !== 3 ||
6547
+ versionParts.some((part) => !Number.isSafeInteger(part) || part < 0)) {
6548
+ throw new Error("game_source_version_invalid");
6549
+ }
6550
+ finalizedGameSource = await (0, game_source_git_1.finalizeGameSourceRepository)({
6551
+ reconciled,
6552
+ appVersionId: 0,
6553
+ version: { major: versionParts[0], minor: versionParts[1], patch: versionParts[2] },
6554
+ });
6555
+ preparedGameSource = await (0, game_source_git_1.uploadFinalizedGameSource)({
6556
+ client,
6557
+ taskId: task.id,
6558
+ attempt: taskContext.attempt,
6559
+ workerKey,
6560
+ leaseToken,
6561
+ appId: input.appId,
6562
+ appUploadSessionId: input.sessionId,
6563
+ source: finalizedGameSource,
6564
+ });
6565
+ return {
6566
+ commitSha: preparedGameSource.commitSha,
6567
+ headSha: preparedGameSource.headSha,
6568
+ baseGeneration: preparedGameSource.baseGeneration,
6569
+ bundleStorageKey: preparedGameSource.bundleStorageKey,
6570
+ bundleSha256: preparedGameSource.bundleSha256,
6571
+ bundleSizeBytes: preparedGameSource.bundleBytes,
6572
+ };
6573
+ };
6574
+ workerFailureStage = "game_publish";
6575
+ if (task.kind === "STATIC_GAME") {
6576
+ const primarySurface = resolveWorkerInstrumentSurface(taskContext);
6577
+ if (!primarySurface)
6578
+ throw new Error("static_game_artifact_primary_surface_missing");
6579
+ if (!envConfig.cdnBase)
6580
+ throw new Error("static_game_publish_cdn_base_missing");
6581
+ const result = await (0, upload_1.publishStaticHtmlProject)({
6582
+ client: taskClient,
6583
+ taskId: task.id,
6584
+ taskToken: assignment.task.token,
6585
+ projectDir: agentWorkspaceDir,
6586
+ creatorUsername: taskContext.creatorUsername,
6587
+ primarySurface,
6588
+ apiBase: envConfig.apiBase,
6589
+ webBase: envConfig.webBase ?? null,
6590
+ cdnBase: envConfig.cdnBase,
6591
+ claimedAppName: refreshed.task.claimedAppName,
6592
+ devRouterPort: devPort,
6593
+ token: ctx.getToken(),
6594
+ user: me.user,
6595
+ prepareGitTransition,
6596
+ });
6597
+ published = result;
6598
+ }
6599
+ else {
6600
+ const fixtureKind = (0, e2e_fixtures_1.detectPlaydropE2EFixture)(assignment.request.prompt);
6601
+ const result = await (0, upload_1.publishWorkerAppProject)({
6602
+ client: taskClient,
6603
+ taskId: task.id,
6604
+ taskToken: assignment.task.token,
6605
+ kind: task.kind,
6606
+ executionTarget: task.executionTarget,
6607
+ expectedAppName: taskContext.outputAppName ?? undefined,
6608
+ expectedDisplayName: taskContext.outputDisplayName,
6609
+ expectedSubtitle: taskContext.outputSubtitle,
6610
+ expectedPrimarySurface: taskContext.outputPrimarySurface,
6611
+ remixSourceRef: taskContext.remixSourceRef ?? null,
6612
+ projectDir: agentWorkspaceDir,
6613
+ creatorUsername: taskContext.creatorUsername,
6614
+ apiBase: envConfig.apiBase,
6615
+ webBase: envConfig.webBase ?? null,
6616
+ token: ctx.getToken(),
6617
+ user: me.user,
6618
+ deterministicE2EFixture: fixtureKind === task.kind,
6619
+ prepareGitTransition,
6620
+ });
6621
+ published = result;
6622
+ }
6623
+ if (!finalizedGameSource || !preparedGameSource) {
6624
+ throw new Error("game_source_transition_not_prepared");
6625
+ }
6626
+ const finalized = finalizedGameSource;
6627
+ const gameSource = preparedGameSource;
6628
+ workerFailureStage = "task_complete";
6629
+ const completion = await completeWorkerAgentTaskWithRetry({
6630
+ client,
6631
+ taskId: task.id,
6632
+ body: {
6633
+ workerKey,
6634
+ leaseToken,
6635
+ appId: published.appId,
6636
+ appVersionId: published.appVersionId,
6637
+ result: {
6638
+ appId: published.appId,
6639
+ appVersionId: published.appVersionId,
6640
+ appName: published.appName,
6641
+ version: published.version,
6642
+ versionNodeId: published.versionNodeId,
6643
+ completedBy: "worker_game_source_git",
6644
+ },
6645
+ summary: intent.summary,
6646
+ ...(intent.nextSteps ? { nextSteps: intent.nextSteps } : {}),
6647
+ ...(intent.platformFeedback ? { platformFeedback: intent.platformFeedback } : {}),
6648
+ tokensUsed: agentResult.tokensUsed,
6649
+ gameSource,
6650
+ },
6651
+ });
6652
+ if (!completion.gameSource)
6653
+ throw new Error("game_source_completion_metadata_missing");
6654
+ (0, game_source_git_1.emitGameSourceWorkerEvent)("playdrop.game_source.completed", {
6655
+ taskId: task.id,
6656
+ appId: published.appId,
6657
+ repoGeneration: completion.gameSource.repoGeneration,
6658
+ bundleBytes: finalized.bundle.length,
6659
+ materialCount: finalized.materials.length,
6660
+ outcome: "DONE",
5410
6661
  });
6662
+ await reportTelemetry("DONE");
6663
+ await (0, game_source_git_1.cleanupCompletedGameSourceWorkspace)({
6664
+ workspace: gameSourceWorkspace,
6665
+ workerHomeDir: resolveWorkerHomeDir(),
6666
+ appId: published.appId,
6667
+ repoGeneration: completion.gameSource.repoGeneration,
6668
+ headSha: finalized.commitSha,
6669
+ versions: finalized.versions,
6670
+ });
6671
+ retainWorkspace = false;
6672
+ console.log(`Agent task ${task.id} reconciled, published, and completed with canonical Git source.`);
6673
+ }
6674
+ else if (task.kind === "STATIC_GAME") {
5411
6675
  await reportTelemetry("DONE");
5412
6676
  try {
5413
6677
  let appId = refreshed.task.appId;
@@ -5452,7 +6716,7 @@ async function startWorker(options = {}) {
5452
6716
  });
5453
6717
  const taskClient = (0, apiClient_1.createCliApiClient)({
5454
6718
  baseUrl: envConfig.apiBase,
5455
- token,
6719
+ tokenProvider: ctx.getToken,
5456
6720
  onBehalfCreatorUsername: taskContext.creatorUsername,
5457
6721
  agentTaskToken: assignment.task.token,
5458
6722
  agentTaskId: task.id,
@@ -5470,7 +6734,7 @@ async function startWorker(options = {}) {
5470
6734
  cdnBase: envConfig.cdnBase,
5471
6735
  claimedAppName: refreshed.task.claimedAppName,
5472
6736
  devRouterPort: devPort,
5473
- token,
6737
+ token: ctx.getToken(),
5474
6738
  user: me.user,
5475
6739
  });
5476
6740
  appId = published.appId;
@@ -5521,9 +6785,9 @@ async function startWorker(options = {}) {
5521
6785
  kind: "system",
5522
6786
  phase: artifactFailure ? "static_game_artifact_failed" : "static_game_publish_failed",
5523
6787
  message: artifactFailure
5524
- ? `Static game output was invalid: ${rawMessage}`
5525
- : `Static game publishing failed: ${rawMessage}`,
5526
- payload: { error: failureCode, detail: rawMessage },
6788
+ ? "Static game output did not pass validation."
6789
+ : "Static game publishing failed.",
6790
+ payload: { error: failureCode },
5527
6791
  });
5528
6792
  retainWorkspace = true;
5529
6793
  const terminalReason = artifactFailure
@@ -5533,7 +6797,6 @@ async function startWorker(options = {}) {
5533
6797
  code: failureCode,
5534
6798
  message: "The static game output did not pass validation.",
5535
6799
  retryable: false,
5536
- rawError: rawMessage,
5537
6800
  source: "worker",
5538
6801
  }
5539
6802
  : {
@@ -5542,7 +6805,6 @@ async function startWorker(options = {}) {
5542
6805
  code: failureCode,
5543
6806
  message: "The platform failed to publish the static game.",
5544
6807
  retryable: true,
5545
- rawError: rawMessage,
5546
6808
  source: "worker",
5547
6809
  };
5548
6810
  await failTaskWithRetry(client, task.id, {
@@ -5550,11 +6812,6 @@ async function startWorker(options = {}) {
5550
6812
  leaseToken,
5551
6813
  error: failureCode,
5552
6814
  terminalReason,
5553
- result: {
5554
- stage: artifactFailure ? "artifact" : "publish",
5555
- error: rawMessage,
5556
- agentRunSucceeded: true,
5557
- },
5558
6815
  });
5559
6816
  }
5560
6817
  }
@@ -5563,44 +6820,20 @@ async function startWorker(options = {}) {
5563
6820
  await failUploadedArtifactWithoutTaskDone();
5564
6821
  }
5565
6822
  else {
5566
- const tokenCap = (0, runtime_1.readPositiveEnvInt)("PLAYDROP_WORKER_TOKEN_CAP", runtime_1.DEFAULT_WORKER_TOKEN_CAP);
5567
- let tokenUsageKnown = true;
5568
- try {
5569
- tokenUsageKnown = (0, runtime_1.assertWorkerTokenUsageWithinCap)({
5570
- tokensUsed: agentResult.tokensUsed,
5571
- tokenCap,
5572
- });
5573
- }
5574
- catch (error) {
5575
- await client.workerCreateAgentTaskEvent(task.id, {
5576
- workerKey,
5577
- leaseToken,
5578
- kind: "progress",
5579
- message: `Token usage ${completedAgentResult.tokensUsed} exceeded the worker token cap of ${tokenCap}.`,
5580
- });
5581
- await reportTelemetry("FAILED");
5582
- throw error;
5583
- }
5584
- if (!tokenUsageKnown) {
5585
- await client.workerCreateAgentTaskEvent(task.id, {
5586
- workerKey,
5587
- leaseToken,
5588
- kind: "progress",
5589
- message: "Token usage parse failed: the agent output did not contain a recognizable total token count.",
5590
- });
5591
- }
6823
+ const uploadFailure = readRecordedTaskUploadFailure();
5592
6824
  await client.workerCreateAgentTaskEvent(task.id, {
5593
6825
  workerKey,
5594
6826
  leaseToken,
5595
6827
  kind: "progress",
5596
6828
  phase: "failed",
5597
- message: "Agent exited before running playdrop task done",
6829
+ message: uploadFailure?.terminalReason.message ?? "Agent exited before running playdrop task done",
5598
6830
  });
5599
6831
  retainWorkspace = true;
5600
6832
  await failTaskWithRetry(client, task.id, {
5601
6833
  workerKey,
5602
6834
  leaseToken,
5603
- error: "agent_exited_without_task_done",
6835
+ error: uploadFailure?.terminalReason.code ?? "agent_exited_without_task_done",
6836
+ ...(uploadFailure ? { terminalReason: uploadFailure.terminalReason } : {}),
5604
6837
  result: agentRunResult,
5605
6838
  });
5606
6839
  await reportTelemetry("FAILED");
@@ -5609,23 +6842,45 @@ async function startWorker(options = {}) {
5609
6842
  }
5610
6843
  catch (error) {
5611
6844
  const message = error instanceof Error ? error.message : String(error);
6845
+ if (gameSourceWorkspace) {
6846
+ await (0, game_source_git_1.checkpointFailedGameSourceWorkspace)({ workspace: gameSourceWorkspace, taskId: task.id }).catch((checkpointError) => {
6847
+ console.error(`Failed Git workspace checkpoint did not complete: ${checkpointError instanceof Error ? checkpointError.message : String(checkpointError)}`);
6848
+ });
6849
+ }
5612
6850
  if (isAgentTaskNotRunningError(error)) {
5613
- const refreshed = await fetchTaskDetail(client, target, task.id).catch(() => null);
6851
+ const refreshed = await fetchTaskDetail(client, target, task.id, workerKey).catch(() => null);
5614
6852
  retainWorkspace = refreshed?.task.status === "FAILED";
5615
6853
  console.error(`Agent task ${task.id} was already finalized${refreshed ? ` as ${refreshed.task.status}` : ""}.`);
5616
6854
  }
5617
6855
  else {
5618
6856
  retainWorkspace = true;
6857
+ const failureCode = normalizeWorkerFailureErrorCode(message);
6858
+ const terminalReason = classifyWorkerSupervisorFailure(message, workerFailureStage);
5619
6859
  console.error(`Agent task ${task.id} failed in the worker: ${message}`);
6860
+ await client.workerCreateAgentTaskEvent(task.id, {
6861
+ workerKey,
6862
+ leaseToken,
6863
+ kind: "system",
6864
+ phase: "worker_failed",
6865
+ message: terminalReason.message,
6866
+ pct: null,
6867
+ payload: {
6868
+ stage: workerFailureStage,
6869
+ error: failureCode.slice(0, 160),
6870
+ },
6871
+ }).catch((eventError) => {
6872
+ console.error(`Agent task ${task.id} worker failure event did not record: ${eventError instanceof Error ? eventError.message : String(eventError)}`);
6873
+ });
5620
6874
  }
5621
6875
  if (!fenced && !isAgentTaskNotRunningError(error)) {
5622
6876
  try {
6877
+ const failureCode = normalizeWorkerFailureErrorCode(message);
6878
+ const terminalReason = classifyWorkerSupervisorFailure(message, workerFailureStage);
5623
6879
  await failTaskWithRetry(client, task.id, {
5624
6880
  workerKey,
5625
6881
  leaseToken,
5626
- error: normalizeWorkerFailureErrorCode(message),
5627
- terminalReason: (0, types_1.classifyAgentTaskTerminalReason)(message, "worker"),
5628
- result: { workerError: message },
6882
+ error: failureCode,
6883
+ terminalReason,
5629
6884
  });
5630
6885
  }
5631
6886
  catch (reportError) {
@@ -5634,7 +6889,7 @@ async function startWorker(options = {}) {
5634
6889
  finally {
5635
6890
  if (!telemetryReported) {
5636
6891
  try {
5637
- await reportTelemetry("FAILED", message);
6892
+ await reportTelemetry("FAILED", undefined, classifyWorkerSupervisorFailure(message, workerFailureStage));
5638
6893
  }
5639
6894
  catch (telemetryError) {
5640
6895
  console.error(`Agent task telemetry failed: ${telemetryError instanceof Error ? telemetryError.message : String(telemetryError)}`);
@@ -5651,6 +6906,7 @@ async function startWorker(options = {}) {
5651
6906
  clearInterval(eventDrainTimer);
5652
6907
  }
5653
6908
  activeTerminators.delete(task.id);
6909
+ await gameServerDevLease?.cleanup();
5654
6910
  if (workspaceDir) {
5655
6911
  await removeAgentRunCredentials(workspaceDir);
5656
6912
  }
@@ -5660,6 +6916,9 @@ async function startWorker(options = {}) {
5660
6916
  else if (workspaceDir) {
5661
6917
  await (0, promises_1.rm)(workspaceDir, { recursive: true, force: true });
5662
6918
  }
6919
+ await (0, game_source_git_1.maintainGameSourceCaches)(resolveWorkerHomeDir()).catch((error) => {
6920
+ console.error(`Game source cache maintenance failed after task ${task.id}: ${error instanceof Error ? error.message : String(error)}`);
6921
+ });
5663
6922
  }
5664
6923
  if (sessionExpired) {
5665
6924
  throw new Error(exports.WORKER_SESSION_EXPIRED_MESSAGE);
@@ -5684,6 +6943,7 @@ async function startWorker(options = {}) {
5684
6943
  npmVersion,
5685
6944
  playwright,
5686
6945
  maxParallelTasks,
6946
+ requireGenerationRuntime: target === "FIRST_PARTY",
5687
6947
  }), capabilities);
5688
6948
  lastCapabilityRefreshAt = Date.now();
5689
6949
  console.log("Worker refreshed installed agent CLI versions and model catalogs.");
@@ -5699,7 +6959,7 @@ async function startWorker(options = {}) {
5699
6959
  });
5700
6960
  let claim;
5701
6961
  try {
5702
- claim = normalizeWorkerClaimResponseV3(await client.workerClaimAgentTask(buildWorkerClaimBody({
6962
+ claim = normalizeWorkerClaimResponseV5(await client.workerClaimAgentTask(buildWorkerClaimBody({
5703
6963
  workerKey,
5704
6964
  capabilities,
5705
6965
  runningTaskCount: activeTaskRuns.size,
@@ -5744,10 +7004,29 @@ async function startWorker(options = {}) {
5744
7004
  continue;
5745
7005
  }
5746
7006
  activeTaskIds.add(taskId);
5747
- activeDevPorts.add(devPort);
7007
+ if (claim.assignment.operation === "GAME")
7008
+ activeDevPorts.add(devPort);
7009
+ const run = claim.assignment.operation === "GENERATION"
7010
+ ? (0, generation_1.runWorkerGenerationTask)({
7011
+ client,
7012
+ assignment: claim.assignment,
7013
+ workerKey,
7014
+ leaseToken: acknowledgedLease.token,
7015
+ workerHomeDir: resolveWorkerHomeDir(),
7016
+ })
7017
+ : claim.assignment.operation === "SCREENING"
7018
+ ? (0, generation_1.runWorkerScreeningTask)({
7019
+ client,
7020
+ assignment: claim.assignment,
7021
+ workerKey,
7022
+ leaseToken: acknowledgedLease.token,
7023
+ })
7024
+ : claim.assignment.operation === "GAME"
7025
+ ? runClaimedTask(claim.assignment, acknowledgedLease.token, devPort)
7026
+ : Promise.reject(new Error("worker_assignment_operation_invalid"));
5748
7027
  const taskRun = containWorkerTaskRun({
5749
7028
  taskId,
5750
- run: runClaimedTask(claim.assignment, acknowledgedLease.token, devPort),
7029
+ run,
5751
7030
  })
5752
7031
  .finally(() => {
5753
7032
  activeTaskRuns.delete(taskId);
@@ -5772,6 +7051,9 @@ async function startWorker(options = {}) {
5772
7051
  if (sessionExpired) {
5773
7052
  throw new Error(exports.WORKER_SESSION_EXPIRED_MESSAGE);
5774
7053
  }
7054
+ if (durableAuthInvalid) {
7055
+ throw new Error(exports.WORKER_SESSION_EXPIRED_MESSAGE);
7056
+ }
5775
7057
  if (quiescingForUpdate) {
5776
7058
  console.log("Worker quiesced for managed update. Exiting for supervisor restart.");
5777
7059
  return;
@@ -5788,6 +7070,9 @@ async function startWorker(options = {}) {
5788
7070
  if (presenceTimer) {
5789
7071
  clearInterval(presenceTimer);
5790
7072
  }
7073
+ if (authRefreshTimer) {
7074
+ clearInterval(authRefreshTimer);
7075
+ }
5791
7076
  node_process_1.default.off("SIGINT", signalHandler);
5792
7077
  node_process_1.default.off("SIGTERM", signalHandler);
5793
7078
  if (options.superviseStdin) {
@@ -5800,7 +7085,7 @@ async function startWorker(options = {}) {
5800
7085
  finally {
5801
7086
  releaseWorkerSupervisorLock(supervisorLock);
5802
7087
  }
5803
- }, { env: options.env });
7088
+ }, { env: options.env, forceRefresh: true });
5804
7089
  }
5805
7090
  async function reportTask(options) {
5806
7091
  const message = options.message?.trim() || null;
@@ -5852,11 +7137,11 @@ async function prepareAssetRuntimeTask() {
5852
7137
  }
5853
7138
  const workspaceDir = resolveTaskWorkspaceDir();
5854
7139
  const relativeSetupPath = node_path_1.default.join("skills", "make-2d-asset-pack", "scripts", "setup_runtime.py");
5855
- const setupScript = [
5856
- node_path_1.default.join(workspaceDir, ".playdrop", "task-plugin", relativeSetupPath),
5857
- node_path_1.default.join(workspaceDir, ".playdrop", "dev-task-plugin", relativeSetupPath),
5858
- ].find((candidate) => isFile(candidate));
5859
- if (!setupScript) {
7140
+ const assignmentSetupScript = node_path_1.default.join(workspaceDir, ".playdrop", "task-plugin", relativeSetupPath);
7141
+ const setupScript = isFile(assignmentSetupScript)
7142
+ ? assignmentSetupScript
7143
+ : node_path_1.default.join(discoverWorkerProjectRoot(node_process_1.default.cwd()), ".playdrop", "dev-task-plugin", relativeSetupPath);
7144
+ if (!isFile(setupScript)) {
5860
7145
  throw new Error("task_asset_runtime_setup_script_missing");
5861
7146
  }
5862
7147
  const bootstrapPython = node_process_1.default.env.PLAYDROP_ASSET_PACK_BOOTSTRAP_PYTHON?.trim() || "python3";
@@ -6049,8 +7334,11 @@ async function resolveTaskCommandContext(command, optionsEnv, taskContext) {
6049
7334
  if (!ctx) {
6050
7335
  return null;
6051
7336
  }
7337
+ const accessToken = taskContext.target === "FIRST_PARTY" ? taskContext.taskAccessToken?.trim() ?? "" : ctx.token;
7338
+ if (!accessToken)
7339
+ throw new Error("agent_task_access_token_missing");
6052
7340
  const taskClientOptions = {
6053
- token: ctx.token,
7341
+ token: accessToken,
6054
7342
  onBehalfCreatorUsername: taskContext.creatorUsername,
6055
7343
  agentTaskToken: taskContext.taskToken,
6056
7344
  agentTaskId: taskContext.taskId,
@@ -6060,21 +7348,42 @@ async function resolveTaskCommandContext(command, optionsEnv, taskContext) {
6060
7348
  ...taskClientOptions,
6061
7349
  baseUrl: ctx.envConfig.apiBase,
6062
7350
  });
6063
- const aiClient = (0, apiClient_1.createCliAiClient)({
6064
- ...taskClientOptions,
6065
- baseUrl: ctx.envConfig.aiBase,
6066
- });
6067
- const me = await client.me();
6068
- if (!me.user) {
7351
+ const user = taskContext.target === "FIRST_PARTY"
7352
+ ? readAgentTaskAccessIdentity(accessToken, taskContext)
7353
+ : (await client.me()).user;
7354
+ if (!user)
6069
7355
  throw new Error("task_context_user_missing");
7356
+ return { ...ctx, token: accessToken, client, user };
7357
+ }
7358
+ function readAgentTaskAccessIdentity(token, taskContext) {
7359
+ try {
7360
+ const payloadSegment = token.split(".")[1];
7361
+ if (!payloadSegment)
7362
+ throw new Error("missing_payload");
7363
+ const payload = JSON.parse(Buffer.from(payloadSegment, "base64url").toString("utf8"));
7364
+ const id = Number(payload.sub);
7365
+ const username = typeof payload.username === "string" ? payload.username.trim() : "";
7366
+ const expiresAtSeconds = Number(payload.exp);
7367
+ if (payload.tokenKind !== "agent_task_access" ||
7368
+ payload.role !== "CLOUD_WORKER" ||
7369
+ payload.kind !== "AGENT" ||
7370
+ !Number.isInteger(id) ||
7371
+ id <= 0 ||
7372
+ !username ||
7373
+ Number(payload.taskId) !== taskContext.taskId ||
7374
+ Number(payload.taskAttempt) !== taskContext.attempt ||
7375
+ !Number.isFinite(expiresAtSeconds) ||
7376
+ expiresAtSeconds <= Date.now() / 1000) {
7377
+ throw new Error("invalid_claims");
7378
+ }
7379
+ return { id, username, role: "CLOUD_WORKER", kind: "AGENT" };
7380
+ }
7381
+ catch {
7382
+ throw new Error("agent_task_access_token_invalid");
6070
7383
  }
6071
- return { ...ctx, client, aiClient, user: me.user };
6072
7384
  }
6073
7385
  async function uploadTask(options = {}) {
6074
7386
  const taskContext = readTaskContextFile();
6075
- if (taskContext.kind === "STATIC_GAME") {
6076
- throw new Error("task_upload_not_allowed_for_static_game");
6077
- }
6078
7387
  if (taskContext.kind === "GAME_REVIEW" || taskContext.kind === "GAME_EVAL") {
6079
7388
  throw new Error("task_upload_not_allowed_for_game_review");
6080
7389
  }
@@ -6113,22 +7422,12 @@ async function uploadTask(options = {}) {
6113
7422
  (0, output_1.printSuccess)(`Task upload preflight passed for ${result.creatorUsername}/${result.appName}. Upload can start.`);
6114
7423
  return;
6115
7424
  }
6116
- if ((0, node_fs_1.existsSync)(workerTaskUploadResultPath(workspaceDir))) {
6117
- const existingResult = readTaskUploadResultFile(workspaceDir);
6118
- assertTaskUploadResultMatchesContext({
6119
- taskContext,
6120
- uploadResult: existingResult,
6121
- });
6122
- (0, output_1.printSuccess)(`Task upload already completed for ${existingResult.creatorUsername}/${existingResult.appName} version ${existingResult.version}. Run "playdrop task done" to close the task.`);
6123
- return;
6124
- }
6125
- await withTaskUploadLock(workspaceDir, async () => {
7425
+ if (taskContext.gameSourceGit) {
6126
7426
  const ctx = await resolveTaskCommandContext("task upload", options.env, taskContext);
6127
- if (!ctx) {
7427
+ if (!ctx)
6128
7428
  return;
6129
- }
6130
7429
  const projectDir = discoverWorkerProjectRoot(node_process_1.default.cwd());
6131
- const published = await (0, upload_1.publishWorkerAppProject)({
7430
+ const result = await (0, upload_1.preflightWorkerAppProject)({
6132
7431
  client: ctx.client,
6133
7432
  taskId: taskContext.taskId,
6134
7433
  taskToken: taskContext.taskToken,
@@ -6146,6 +7445,63 @@ async function uploadTask(options = {}) {
6146
7445
  token: ctx.token,
6147
7446
  user: ctx.user,
6148
7447
  });
7448
+ await (0, promises_1.writeFile)(workerGameSourceReadinessPath(workspaceDir), JSON.stringify({
7449
+ taskId: taskContext.taskId,
7450
+ attempt: taskContext.attempt,
7451
+ projectDir,
7452
+ readyAt: new Date().toISOString(),
7453
+ }, null, 2));
7454
+ for (const warning of result.warnings)
7455
+ console.error(`Preflight warning: ${warning}`);
7456
+ (0, output_1.printSuccess)("Task source is ready. Run \"playdrop task done\" when the work is complete.");
7457
+ return;
7458
+ }
7459
+ if (taskContext.kind === "STATIC_GAME") {
7460
+ throw new Error("task_upload_not_allowed_for_static_game");
7461
+ }
7462
+ if ((0, node_fs_1.existsSync)(workerTaskUploadResultPath(workspaceDir))) {
7463
+ const existingResult = readTaskUploadResultFile(workspaceDir);
7464
+ assertTaskUploadResultMatchesContext({
7465
+ taskContext,
7466
+ uploadResult: existingResult,
7467
+ });
7468
+ (0, output_1.printSuccess)(`Task upload already completed for ${existingResult.creatorUsername}/${existingResult.appName} version ${existingResult.version}. Run "playdrop task done" to close the task.`);
7469
+ return;
7470
+ }
7471
+ await withTaskUploadLock(workspaceDir, async () => {
7472
+ const ctx = await resolveTaskCommandContext("task upload", options.env, taskContext);
7473
+ if (!ctx) {
7474
+ return;
7475
+ }
7476
+ const projectDir = discoverWorkerProjectRoot(node_process_1.default.cwd());
7477
+ await (0, promises_1.rm)(workerTaskUploadFailurePath(workspaceDir), { force: true });
7478
+ let published;
7479
+ try {
7480
+ published = await (0, upload_1.publishWorkerAppProject)({
7481
+ client: ctx.client,
7482
+ taskId: taskContext.taskId,
7483
+ taskToken: taskContext.taskToken,
7484
+ kind: uploadKind,
7485
+ executionTarget: taskContext.target,
7486
+ expectedAppName: taskContext.outputAppName ?? undefined,
7487
+ expectedDisplayName: taskContext.outputDisplayName,
7488
+ expectedSubtitle: taskContext.outputSubtitle,
7489
+ expectedPrimarySurface: taskContext.outputPrimarySurface,
7490
+ remixSourceRef: taskContext.remixSourceRef ?? null,
7491
+ projectDir,
7492
+ creatorUsername: taskContext.creatorUsername,
7493
+ apiBase: ctx.envConfig.apiBase,
7494
+ webBase: ctx.envConfig.webBase ?? null,
7495
+ token: ctx.token,
7496
+ user: ctx.user,
7497
+ });
7498
+ }
7499
+ catch (error) {
7500
+ await recordTaskUploadFailure({ workspaceDir, taskContext, error }).catch((recordError) => {
7501
+ console.error(`Could not record task upload failure: ${recordError instanceof Error ? recordError.message : String(recordError)}`);
7502
+ });
7503
+ throw error;
7504
+ }
6149
7505
  const result = {
6150
7506
  taskId: taskContext.taskId,
6151
7507
  appId: published.appId,
@@ -6216,6 +7572,7 @@ async function claimSlugTask(options) {
6216
7572
  async function completeTask(options) {
6217
7573
  const taskContext = readTaskContextFile();
6218
7574
  const workspaceDir = resolveTaskWorkspaceDir();
7575
+ const commandInputDir = taskContext.gameSourceGit ? node_process_1.default.cwd() : workspaceDir;
6219
7576
  if (taskContext.kind === "GAME_REVIEW" || taskContext.kind === "GAME_EVAL") {
6220
7577
  throw new Error("task_done_not_allowed_for_game_review");
6221
7578
  }
@@ -6226,11 +7583,38 @@ async function completeTask(options) {
6226
7583
  if (summary.length > 200) {
6227
7584
  throw new Error("task_done_summary_too_long:max_200_characters");
6228
7585
  }
7586
+ if (taskContext.gameSourceGit) {
7587
+ if (!(0, node_fs_1.existsSync)(workerGameSourceReadinessPath(workspaceDir)))
7588
+ throw new Error("task_done_source_not_ready");
7589
+ const ctx = await resolveTaskCommandContext("task done", options.env, taskContext);
7590
+ if (!ctx)
7591
+ return;
7592
+ const nextSteps = await readTaskNextStepsFileForCommand({
7593
+ filePath: options.nextSteps,
7594
+ workspaceDir: commandInputDir,
7595
+ command: "task done",
7596
+ ctx,
7597
+ taskContext,
7598
+ });
7599
+ if (nextSteps === NEXT_STEPS_READ_FAILED)
7600
+ return;
7601
+ const platformFeedback = readPlatformFeedbackFile(options.platformFeedbackFile, commandInputDir);
7602
+ const intent = {
7603
+ taskId: taskContext.taskId,
7604
+ attempt: taskContext.attempt,
7605
+ summary,
7606
+ ...(nextSteps !== undefined ? { nextSteps } : {}),
7607
+ ...(platformFeedback ? { platformFeedback } : {}),
7608
+ };
7609
+ await (0, promises_1.writeFile)(workerGameSourceCompletionPath(workspaceDir), `${JSON.stringify(intent, null, 2)}\n`, "utf8");
7610
+ (0, output_1.printSuccess)("Task completion recorded. The worker will reconcile and publish the source after the agent exits.");
7611
+ return;
7612
+ }
7613
+ const uploadResult = readTaskUploadResultFile();
6229
7614
  const ctx = await resolveTaskCommandContext("task done", options.env, taskContext);
6230
7615
  if (!ctx) {
6231
7616
  return;
6232
7617
  }
6233
- const uploadResult = readTaskUploadResultFile();
6234
7618
  const appId = uploadResult.appId;
6235
7619
  const appVersionId = uploadResult.appVersionId;
6236
7620
  if (!appId || !appVersionId) {
@@ -6275,12 +7659,6 @@ async function completeTask(options) {
6275
7659
  taskToken: taskContext.taskToken,
6276
7660
  error: "remix_relation_missing",
6277
7661
  terminalReason: (0, types_1.classifyAgentTaskTerminalReason)("upload_validation:remix_relation_missing", "agent"),
6278
- result: {
6279
- appId: uploadResult.appId,
6280
- appVersionId: uploadResult.appVersionId,
6281
- appName: uploadResult.appName,
6282
- version: uploadResult.version,
6283
- },
6284
7662
  ...(platformFeedback ? { platformFeedback } : {}),
6285
7663
  });
6286
7664
  }
@@ -6543,11 +7921,6 @@ async function instrumentErrorTask(options) {
6543
7921
  taskToken: taskContext.taskToken,
6544
7922
  error: `instrument_error:${reason}`,
6545
7923
  terminalReason: (0, types_1.classifyAgentTaskTerminalReason)(`instrument_error:${reason}`, "instrument"),
6546
- result: {
6547
- outcome: "instrument_error",
6548
- reason,
6549
- reportedBy: "agent",
6550
- },
6551
7924
  });
6552
7925
  (0, output_1.printSuccess)(`Instrument error submitted (${reason}). This was the final operation; stop immediately.`);
6553
7926
  }
@@ -6570,22 +7943,26 @@ async function failTask(options) {
6570
7943
  }
6571
7944
  const taskContext = readTaskContextFile();
6572
7945
  const workspaceDir = resolveTaskWorkspaceDir();
7946
+ const uploadFailure = readTaskUploadFailureFile(workspaceDir);
7947
+ if (uploadFailure && (uploadFailure.taskId !== taskContext.taskId || uploadFailure.attempt !== taskContext.attempt)) {
7948
+ throw new Error("task_upload_failure_context_mismatch");
7949
+ }
6573
7950
  const ctx = await resolveTaskCommandContext("task fail", options.env, taskContext);
6574
7951
  if (!ctx) {
6575
7952
  return;
6576
7953
  }
6577
7954
  const platformFeedback = readPlatformFeedbackFile(options.platformFeedbackFile, workspaceDir);
6578
- const terminalReason = (0, types_1.classifyAgentTaskTerminalReason)(message, "agent");
7955
+ const classifiedMessage = (0, types_1.classifyAgentTaskTerminalReason)(message, "agent");
7956
+ const terminalReason = uploadFailure?.terminalReason ?? {
7957
+ ...classifiedMessage,
7958
+ message: "The agent reported that it could not complete the task.",
7959
+ retryable: options.retryableWithSameInput === true,
7960
+ source: "agent",
7961
+ };
6579
7962
  await ctx.client.workerFailAgentTask(taskContext.taskId, {
6580
7963
  taskToken: taskContext.taskToken,
6581
- error: message,
6582
- terminalReason: {
6583
- ...terminalReason,
6584
- message,
6585
- retryable: options.retryableWithSameInput === true,
6586
- source: "agent",
6587
- },
6588
- result: { failedBy: "agent" },
7964
+ error: uploadFailure?.terminalReason.code ?? terminalReason.code,
7965
+ terminalReason,
6589
7966
  ...(platformFeedback ? { platformFeedback } : {}),
6590
7967
  });
6591
7968
  (0, output_1.printSuccess)("Task marked failed. This was the final operation; send one short final status sentence and stop immediately.");