@playcraft/cli 0.1.22 → 0.1.24

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 (278) hide show
  1. package/README.md +3 -38
  2. package/dist/agent/local-backend.js +1 -1
  3. package/dist/cli-root-help.js +0 -1
  4. package/dist/commands/build.js +4 -9
  5. package/dist/commands/create.js +33 -26
  6. package/dist/commands/image.js +6 -2
  7. package/dist/commands/login.js +42 -84
  8. package/dist/commands/skills.js +0 -2
  9. package/dist/commands/tools-generation.js +4 -2
  10. package/dist/commands/tools.js +0 -2
  11. package/dist/config.js +4 -59
  12. package/dist/index.js +1 -6
  13. package/dist/remix/clone.js +0 -2
  14. package/dist/remix/init-template.js +0 -2
  15. package/dist/remix/pull.js +0 -2
  16. package/dist/remix/push.js +1 -21
  17. package/dist/utils/agent-api-client.js +29 -148
  18. package/dist/utils/playcraft-http-client.js +4 -6
  19. package/package.json +4 -10
  20. package/project-template/.claude/skills/playcraft-image-generation/SKILL.md +61 -23
  21. package/project-templates/phaser/.claude/skills/playcraft-image-generation/SKILL.md +61 -23
  22. package/project-templates/threejs/.claude/skills/playcraft-image-generation/SKILL.md +61 -23
  23. package/dist/auth/account-activation.js +0 -157
  24. package/dist/auth/messages.js +0 -30
  25. package/dist/commands/platform-skills.generated.js +0 -19
  26. package/dist/commands/tools-batch.js +0 -59
  27. package/dist/commands/workspace-runtime.js +0 -54
  28. package/dist/project-skills/commands.js +0 -72
  29. package/dist/project-skills/lifecycle.js +0 -28
  30. package/dist/project-skills/local-run.js +0 -112
  31. package/dist/project-skills/local-store.js +0 -149
  32. package/dist/project-skills/messages.js +0 -72
  33. package/dist/project-skills/reconcile.js +0 -62
  34. package/dist/project-skills/remote-cache.js +0 -51
  35. package/dist/project-skills/validation.js +0 -19
  36. package/dist/utils/environment-endpoints.js +0 -17
  37. package/dist/utils/tool-operation-journal.js +0 -124
  38. package/dist/workspace-runtime/codex/app-server.js +0 -571
  39. package/dist/workspace-runtime/codex/config-toml.js +0 -108
  40. package/dist/workspace-runtime/codex/jsonrpc-stdio.js +0 -123
  41. package/dist/workspace-runtime/codex/launcher-client.js +0 -163
  42. package/dist/workspace-runtime/codex/loopback.js +0 -143
  43. package/dist/workspace-runtime/codex/model-broker.js +0 -239
  44. package/dist/workspace-runtime/codex/native-adapter.js +0 -9
  45. package/dist/workspace-runtime/main.js +0 -10
  46. package/dist/workspace-runtime/persistence/journal.js +0 -355
  47. package/dist/workspace-runtime/persistence/sqlite-client.js +0 -61
  48. package/dist/workspace-runtime/persistence/sqlite-worker.js +0 -1015
  49. package/dist/workspace-runtime/previews/publish.js +0 -259
  50. package/dist/workspace-runtime/previews/single-file.js +0 -43
  51. package/dist/workspace-runtime/processes/managed-writes.js +0 -69
  52. package/dist/workspace-runtime/production/contracts.js +0 -17
  53. package/dist/workspace-runtime/production/controller.js +0 -207
  54. package/dist/workspace-runtime/production/dependencies.js +0 -60
  55. package/dist/workspace-runtime/production/native.js +0 -70
  56. package/dist/workspace-runtime/production/processes.js +0 -86
  57. package/dist/workspace-runtime/production/save-command.js +0 -20
  58. package/dist/workspace-runtime/production/stages.js +0 -175
  59. package/dist/workspace-runtime/production/storage.js +0 -32
  60. package/dist/workspace-runtime/serve.js +0 -199
  61. package/dist/workspace-runtime/server/auth.js +0 -21
  62. package/dist/workspace-runtime/server/connectivity.js +0 -201
  63. package/dist/workspace-runtime/server/desktop-host.js +0 -429
  64. package/dist/workspace-runtime/server/desktop-preview-config.js +0 -81
  65. package/dist/workspace-runtime/server/desktop-preview-service.js +0 -157
  66. package/dist/workspace-runtime/server/desktop-preview-static.js +0 -53
  67. package/dist/workspace-runtime/server/desktop-preview.js +0 -189
  68. package/dist/workspace-runtime/server/dispatch.js +0 -845
  69. package/dist/workspace-runtime/server/endpoints.js +0 -19
  70. package/dist/workspace-runtime/server/execution-group.js +0 -85
  71. package/dist/workspace-runtime/server/http.js +0 -919
  72. package/dist/workspace-runtime/server/lock.js +0 -53
  73. package/dist/workspace-runtime/server/rfb-watch-gate.js +0 -168
  74. package/dist/workspace-runtime/server/terminal-host.js +0 -294
  75. package/dist/workspace-runtime/server/types.js +0 -1
  76. package/dist/workspace-runtime/workspaces/attachments.js +0 -109
  77. package/dist/workspace-runtime/workspaces/batch-commit.js +0 -142
  78. package/dist/workspace-runtime/workspaces/cli-credentials.js +0 -111
  79. package/dist/workspace-runtime/workspaces/context-error.js +0 -2
  80. package/dist/workspace-runtime/workspaces/dependencies.js +0 -264
  81. package/dist/workspace-runtime/workspaces/developer-command.js +0 -64
  82. package/dist/workspace-runtime/workspaces/directories.js +0 -106
  83. package/dist/workspace-runtime/workspaces/directory-access.js +0 -20
  84. package/dist/workspace-runtime/workspaces/directory-maintenance.js +0 -111
  85. package/dist/workspace-runtime/workspaces/directory-stats.js +0 -49
  86. package/dist/workspace-runtime/workspaces/empty-template.js +0 -32
  87. package/dist/workspace-runtime/workspaces/files.js +0 -113
  88. package/dist/workspace-runtime/workspaces/git-credential-broker.js +0 -312
  89. package/dist/workspace-runtime/workspaces/git-credential-helper.js +0 -68
  90. package/dist/workspace-runtime/workspaces/git-direct-store.js +0 -155
  91. package/dist/workspace-runtime/workspaces/git.js +0 -471
  92. package/dist/workspace-runtime/workspaces/json5-edit.js +0 -170
  93. package/dist/workspace-runtime/workspaces/parameters.js +0 -272
  94. package/dist/workspace-runtime/workspaces/prepare.js +0 -263
  95. package/dist/workspace-runtime/workspaces/registry.js +0 -192
  96. package/dist/workspace-runtime/workspaces/revisions.js +0 -31
  97. package/dist/workspace-runtime/workspaces/skill-artifact-loader.js +0 -46
  98. package/dist/workspace-runtime/workspaces/skills.js +0 -179
  99. package/project-template-v2/.claude/agents/artist.md +0 -82
  100. package/project-template-v2/.claude/agents/developer.md +0 -153
  101. package/project-template-v2/.claude/agents/game-designer.md +0 -264
  102. package/project-template-v2/.claude/agents/refs/artist-art-style-catalog.md +0 -533
  103. package/project-template-v2/.claude/agents/refs/artist-color-audio-recipes.md +0 -153
  104. package/project-template-v2/.claude/agents/refs/artist-dimension-axis.md +0 -27
  105. package/project-template-v2/.claude/agents/refs/artist-master-composite-recipes.md +0 -208
  106. package/project-template-v2/.claude/agents/refs/atom-skill-library.md +0 -81
  107. package/project-template-v2/.claude/agents/refs/developer-impl-cookbook.md +0 -432
  108. package/project-template-v2/.claude/agents/refs/framework-5-component-filter.md +0 -252
  109. package/project-template-v2/.claude/agents/refs/framework-game-feel-juice.md +0 -266
  110. package/project-template-v2/.claude/agents/refs/framework-mda.md +0 -147
  111. package/project-template-v2/.claude/agents/refs/game-designer-gameplay-sufficiency.md +0 -123
  112. package/project-template-v2/.claude/agents/refs/ta-3d-flip-recipe.md +0 -88
  113. package/project-template-v2/.claude/agents/refs/ta-atlas-deliverable-standard.md +0 -67
  114. package/project-template-v2/.claude/agents/refs/ta-batch-pipeline-recipes.md +0 -120
  115. package/project-template-v2/.claude/agents/refs/ta-image-generation-detail.md +0 -300
  116. package/project-template-v2/.claude/agents/refs/ta-image-ops-reference.md +0 -495
  117. package/project-template-v2/.claude/agents/refs/ta-pipeline-cookbook.md +0 -1141
  118. package/project-template-v2/.claude/agents/refs/ta-tools-reference.md +0 -111
  119. package/project-template-v2/.claude/agents/refs/ta-vfx-preset-catalog.md +0 -365
  120. package/project-template-v2/.claude/agents/refs/threejs-cannon-pitfalls.md +0 -412
  121. package/project-template-v2/.claude/agents/reviewer.md +0 -75
  122. package/project-template-v2/.claude/agents/technical-artist.md +0 -86
  123. package/project-template-v2/.claude/hooks/snapshot-milestone.mjs +0 -243
  124. package/project-template-v2/.claude/hooks/user-prompt.mjs +0 -133
  125. package/project-template-v2/.claude/settings.json +0 -33
  126. package/project-template-v2/.claude/skills/brainstorming/SKILL.md +0 -161
  127. package/project-template-v2/.claude/skills/brainstorming/scripts/frame-template.html +0 -270
  128. package/project-template-v2/.claude/skills/brainstorming/scripts/helper.js +0 -177
  129. package/project-template-v2/.claude/skills/brainstorming/scripts/server.cjs +0 -354
  130. package/project-template-v2/.claude/skills/brainstorming/scripts/start-server.sh +0 -148
  131. package/project-template-v2/.claude/skills/brainstorming/scripts/stop-server.sh +0 -56
  132. package/project-template-v2/.claude/skills/brainstorming/scripts/wait-for-selection.sh +0 -62
  133. package/project-template-v2/.claude/skills/brainstorming/spec-document-reviewer-prompt.md +0 -49
  134. package/project-template-v2/.claude/skills/brainstorming/visual-companion.md +0 -309
  135. package/project-template-v2/.claude/skills/playcraft-ad-psychology/SKILL.md +0 -182
  136. package/project-template-v2/.claude/skills/playcraft-art-style-guide/SKILL.md +0 -123
  137. package/project-template-v2/.claude/skills/playcraft-asset-state-sheet/SKILL.md +0 -205
  138. package/project-template-v2/.claude/skills/playcraft-audio-generation/SKILL.md +0 -280
  139. package/project-template-v2/.claude/skills/playcraft-batch-pipeline/SKILL.md +0 -184
  140. package/project-template-v2/.claude/skills/playcraft-build-optimizer/SKILL.md +0 -306
  141. package/project-template-v2/.claude/skills/playcraft-image-generation/SKILL.md +0 -298
  142. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/build-sprite-sheet.template.mjs +0 -123
  143. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/compare-style.template.mjs +0 -254
  144. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +0 -324
  145. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +0 -97
  146. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-edit-variants.template.mjs +0 -118
  147. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +0 -137
  148. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +0 -397
  149. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/validate-sprite-sheet.template.mjs +0 -296
  150. package/project-template-v2/.claude/skills/playcraft-image-ops/SKILL.md +0 -122
  151. package/project-template-v2/.claude/skills/playcraft-image-processing/SKILL.md +0 -219
  152. package/project-template-v2/.claude/skills/playcraft-masking/SKILL.md +0 -373
  153. package/project-template-v2/.claude/skills/playcraft-playable-optimization/SKILL.md +0 -161
  154. package/project-template-v2/.claude/skills/playcraft-research/SKILL.md +0 -215
  155. package/project-template-v2/.claude/skills/playcraft-skill-recommender/SKILL.md +0 -382
  156. package/project-template-v2/.claude/skills/playcraft-sprite-generation/SKILL.md +0 -423
  157. package/project-template-v2/.claude/skills/playcraft-sprite-remix/SKILL.md +0 -158
  158. package/project-template-v2/.claude/skills/playcraft-sprite-sheet/SKILL.md +0 -100
  159. package/project-template-v2/.claude/skills/playcraft-storyboard/SKILL.md +0 -167
  160. package/project-template-v2/.claude/skills/playcraft-style-qa/SKILL.md +0 -270
  161. package/project-template-v2/.claude/skills/playcraft-text-rendering/SKILL.md +0 -236
  162. package/project-template-v2/.claude/skills/playcraft-vfx-animation/SKILL.md +0 -130
  163. package/project-template-v2/.claude/skills/playwright-cli/SKILL.md +0 -390
  164. package/project-template-v2/.claude/skills/playwright-cli/references/element-attributes.md +0 -23
  165. package/project-template-v2/.claude/skills/playwright-cli/references/playwright-tests.md +0 -39
  166. package/project-template-v2/.claude/skills/playwright-cli/references/request-mocking.md +0 -87
  167. package/project-template-v2/.claude/skills/playwright-cli/references/running-code.md +0 -240
  168. package/project-template-v2/.claude/skills/playwright-cli/references/session-management.md +0 -226
  169. package/project-template-v2/.claude/skills/playwright-cli/references/spec-driven-testing.md +0 -308
  170. package/project-template-v2/.claude/skills/playwright-cli/references/storage-state.md +0 -275
  171. package/project-template-v2/.claude/skills/playwright-cli/references/test-generation.md +0 -134
  172. package/project-template-v2/.claude/skills/playwright-cli/references/tracing.md +0 -142
  173. package/project-template-v2/.claude/skills/playwright-cli/references/video-recording.md +0 -153
  174. package/project-template-v2/.claude/skills/session-analyzer/SKILL.md +0 -386
  175. package/project-template-v2/.claude/skills/session-analyzer/scripts/execution-breakdown.mjs +0 -182
  176. package/project-template-v2/.claude/skills/session-analyzer/scripts/find-turns.mjs +0 -72
  177. package/project-template-v2/.claude/skills/session-analyzer/scripts/heavy-output.mjs +0 -121
  178. package/project-template-v2/.claude/skills/session-analyzer/scripts/resolve-session.mjs +0 -102
  179. package/project-template-v2/.claude/skills/session-analyzer/scripts/subagent-stats.mjs +0 -127
  180. package/project-template-v2/.claude/skills/session-analyzer/scripts/subagent-tool-timeline.mjs +0 -106
  181. package/project-template-v2/.claude/skills/session-analyzer/scripts/time-gaps.mjs +0 -128
  182. package/project-template-v2/.claude/skills/session-analyzer/scripts/turn-timeline.mjs +0 -67
  183. package/project-template-v2/.claude/snapshot.mjs +0 -263
  184. package/project-template-v2/.playcraft/skills.lock.json +0 -152
  185. package/project-template-v2/CLAUDE.md +0 -146
  186. package/project-template-v2/assets/audio/bgm/.gitkeep +0 -0
  187. package/project-template-v2/assets/audio/sfx/.gitkeep +0 -0
  188. package/project-template-v2/assets/bundles/.gitkeep +0 -0
  189. package/project-template-v2/assets/images/bg/.gitkeep +0 -0
  190. package/project-template-v2/assets/images/reference/.gitkeep +0 -0
  191. package/project-template-v2/assets/images/storyboard/.gitkeep +0 -0
  192. package/project-template-v2/assets/images/tiles/.gitkeep +0 -0
  193. package/project-template-v2/assets/images/ui/.gitkeep +0 -0
  194. package/project-template-v2/assets/images/vfx/.gitkeep +0 -0
  195. package/project-template-v2/assets/models/.gitkeep +0 -0
  196. package/project-template-v2/docs/harness/iteration-1/context-flow.md +0 -254
  197. package/project-template-v2/docs/harness/iteration-1/generate-flow.md +0 -91
  198. package/project-template-v2/docs/harness/iteration-1/ideate-flow.md +0 -214
  199. package/project-template-v2/docs/harness/iteration-1/optimize-flow.md +0 -75
  200. package/project-template-v2/docs/harness/iteration-1/wrapup-flow.md +0 -63
  201. package/project-template-v2/docs/harness/iteration-2/context-flow.md +0 -223
  202. package/project-template-v2/docs/harness/iteration-2/generate-flow.md +0 -129
  203. package/project-template-v2/docs/harness/iteration-2/ideate-flow.md +0 -267
  204. package/project-template-v2/docs/harness/iteration-2/optimize-flow.md +0 -164
  205. package/project-template-v2/docs/harness/iteration-2/wrapup-flow.md +0 -115
  206. package/project-template-v2/docs/harness/orchestrator-flow.md +0 -364
  207. package/project-template-v2/docs/project-state.json +0 -60
  208. package/project-template-v2/docs/project-state.md +0 -72
  209. package/project-template-v2/docs/standards/README.md +0 -225
  210. package/project-template-v2/docs/standards/agent-behavior-standards.md +0 -174
  211. package/project-template-v2/docs/standards/artifacts/design-brief.md +0 -19
  212. package/project-template-v2/docs/standards/artifacts/design.md +0 -22
  213. package/project-template-v2/docs/standards/artifacts/game-code.md +0 -41
  214. package/project-template-v2/docs/standards/artifacts/todo-list.md +0 -41
  215. package/project-template-v2/docs/standards/iter1-agent-behavior-standards.md +0 -343
  216. package/project-template-v2/game/index.ts +0 -18
  217. package/project-template-v2/globals.d.ts +0 -51
  218. package/project-template-v2/index.css +0 -34
  219. package/project-template-v2/index.html +0 -18
  220. package/project-template-v2/main.ts +0 -9
  221. package/project-template-v2/package.json +0 -46
  222. package/project-template-v2/skills/_shared/scripts/dispatch-clear.mjs +0 -31
  223. package/project-template-v2/skills/_shared/scripts/dispatch-set.mjs +0 -86
  224. package/project-template-v2/skills/_shared/scripts/dod-check.mjs +0 -153
  225. package/project-template-v2/skills/_shared/scripts/handoff-append.mjs +0 -70
  226. package/project-template-v2/skills/_shared/scripts/lib/validator-artifacts.mjs +0 -91
  227. package/project-template-v2/skills/_shared/scripts/pipeline/dod-config.mjs +0 -131
  228. package/project-template-v2/skills/_shared/scripts/pipeline/index.mjs +0 -80
  229. package/project-template-v2/skills/_shared/scripts/pipeline/iteration-1-core.mjs +0 -90
  230. package/project-template-v2/skills/_shared/scripts/pipeline/iteration-2-wrap.mjs +0 -93
  231. package/project-template-v2/skills/_shared/scripts/pipeline/iteration-3-visual.mjs +0 -23
  232. package/project-template-v2/skills/_shared/scripts/render-project-state.mjs +0 -230
  233. package/project-template-v2/skills/_shared/scripts/state-advance-stage.mjs +0 -58
  234. package/project-template-v2/skills/_shared/scripts/state-get.mjs +0 -339
  235. package/project-template-v2/skills/_shared/scripts/state-handoff.mjs +0 -39
  236. package/project-template-v2/skills/_shared/scripts/state-set.mjs +0 -94
  237. package/project-template-v2/skills/_shared/scripts/state-store.mjs +0 -783
  238. package/project-template-v2/skills/_shared/scripts/todo-add.mjs +0 -88
  239. package/project-template-v2/skills/_shared/scripts/todo-get.mjs +0 -130
  240. package/project-template-v2/skills/_shared/scripts/todo-remove.mjs +0 -33
  241. package/project-template-v2/skills/_shared/scripts/todo-set.mjs +0 -47
  242. package/project-template-v2/skills/_shared/scripts/verify-asset-code-sync.mjs +0 -268
  243. package/project-template-v2/skills/_shared/scripts/verify-env.mjs +0 -285
  244. package/project-template-v2/skills/_shared/scripts/verify-placeholders.mjs +0 -161
  245. package/project-template-v2/skills/playable-autoplay/SKILL.md +0 -176
  246. package/project-template-v2/skills/playable-autoplay/agents/openai.yaml +0 -4
  247. package/project-template-v2/skills/playable-debug/SKILL.md +0 -116
  248. package/project-template-v2/skills/playable-debug/agents/openai.yaml +0 -4
  249. package/project-template-v2/skills/playable-debug/references/debug-config.md +0 -40
  250. package/project-template-v2/skills/playable-record/SKILL.md +0 -140
  251. package/project-template-v2/skills/playable-record/scripts/lib/dev-server.mjs +0 -104
  252. package/project-template-v2/skills/playable-record/scripts/lib/record-audio-bridge.js +0 -141
  253. package/project-template-v2/skills/playable-record/scripts/record-playable.mjs +0 -425
  254. package/project-template-v2/skills/playable-record/scripts/verify-contract.mjs +0 -261
  255. package/project-template-v2/skills/playable-record/scripts/verify-firstwin.mjs +0 -398
  256. package/project-template-v2/skills/playable-record/scripts/verify-fusion.mjs +0 -322
  257. package/project-template-v2/skills/playable-record/scripts/verify-lifecycle.mjs +0 -105
  258. package/project-template-v2/skills/playable-record/scripts/verify-vlm-video.mjs +0 -230
  259. package/project-template-v2/skills/playable-validate/SKILL.md +0 -61
  260. package/project-template-v2/skills/playable-validate/validation-rules.md +0 -14
  261. package/project-template-v2/skills/playable-verify-ui/SKILL.md +0 -63
  262. package/project-template-v2/skills/playable-verify-ui/scripts/lib/__init__.py +0 -1
  263. package/project-template-v2/skills/playable-verify-ui/scripts/lib/config_expr.js +0 -218
  264. package/project-template-v2/skills/playable-verify-ui/scripts/lib/node_utils.js +0 -51
  265. package/project-template-v2/skills/playable-verify-ui/scripts/lib/profile_loader.py +0 -59
  266. package/project-template-v2/skills/playable-verify-ui/scripts/lib/report.py +0 -106
  267. package/project-template-v2/skills/playable-verify-ui/scripts/lib/runtime_contract.js +0 -116
  268. package/project-template-v2/skills/playable-verify-ui/scripts/verify_fetch_antipatterns.py +0 -59
  269. package/project-template-v2/skills/playable-verify-ui/scripts/verify_hardcoded_layout.js +0 -64
  270. package/project-template-v2/skills/playable-verify-ui/scripts/verify_runtime_contract.js +0 -58
  271. package/project-template-v2/skills/playable-verify-ui/scripts/verify_text_overlap.py +0 -333
  272. package/project-template-v2/ta-workspace/scripts/.gitkeep +0 -0
  273. package/project-template-v2/tsconfig.json +0 -20
  274. package/project-template-v2/vite.config.ts +0 -27
  275. package/project-templates/batch-locks/phaser.yaml +0 -922
  276. package/project-templates/batch-locks/threejs.yaml +0 -978
  277. package/project-templates/empty/AGENTS.md +0 -23
  278. package/project-templates/empty/gitignore +0 -31
@@ -1,70 +0,0 @@
1
- import { join } from 'node:path';
2
- import { createCodexAppServerAdapter } from '../codex/app-server.js';
3
- import { verifyProductionLease } from './storage.js';
4
- import { processBirth, stopProductionGroup } from './processes.js';
5
- export async function runProductionNative(state, input, root, record, track) {
6
- if (!input.model)
7
- throw new Error('production_model_configuration_required');
8
- const groups = [];
9
- const adapter = createCodexAppServerAdapter({ config: { ...state.config, workspaceMode: 'single', workspaceRoot: root },
10
- command: process.env.PLAYCRAFT_PRODUCTION_CODEX_COMMAND,
11
- homeDir: process.env.PLAYCRAFT_LAUNCHER_SOCKET
12
- ? `/var/lib/playcraft/codex/production-${input.task.attemptId}` : join(state.config.stateDir, 'production-native', input.task.attemptId),
13
- model: input.model.model, processGroup: true,
14
- fixedThreadConfig: { model: input.model.model, modelProvider: 'playcraft_production', config: {
15
- approval_policy: 'never', sandbox_mode: 'danger-full-access',
16
- 'model_providers.playcraft_production': { name: 'PlayCraft Production', base_url: input.model.endpoint,
17
- wire_api: 'responses', requires_openai_auth: false, http_headers: { Authorization: `Bearer ${input.model.token}` } },
18
- } },
19
- onSkillProcessRestart: async (process) => {
20
- const group = { pid: process.pid, birth: await processBirth(process.pid), stopped: false };
21
- groups.push(group);
22
- await record(group);
23
- },
24
- });
25
- track(() => adapter.close());
26
- const sessionId = `production-${input.task.attemptId}`;
27
- let completion;
28
- const done = new Promise(resolve => { completion = resolve; });
29
- const unsubscribe = adapter.onCollected?.(event => {
30
- if (event.kind === 'process_exit')
31
- completion('unknown');
32
- if (event.method === 'turn/completed') {
33
- const value = event.params;
34
- completion(value.turn?.status === 'completed' ? 'completed' : 'failed');
35
- }
36
- });
37
- try {
38
- await adapter.start();
39
- const process = adapter.executionProcess?.();
40
- if (!process)
41
- throw new Error('production_process_not_started');
42
- const group = { pid: process.pid, birth: await processBirth(process.pid), stopped: false };
43
- groups.push(group);
44
- await record(group);
45
- const thread = await adapter.startThread({ sessionId, cwd: root });
46
- const turn = await adapter.startTurn({ sessionId, threadId: thread.threadId, text: String(input.payload.prompt ?? ''),
47
- clientRequestId: `${input.task.attemptId}:generate`, persistReservation: async () => undefined });
48
- if (turn.outcome !== 'accepted')
49
- throw new Error('production_native_start_unknown');
50
- const timeout = setTimeout(() => completion('unknown'), 30 * 60_000);
51
- let checking = false;
52
- const leaseCheck = setInterval(() => {
53
- if (checking)
54
- return;
55
- checking = true;
56
- void verifyProductionLease(input).catch(async () => { completion('unknown'); await adapter.close?.().catch(() => undefined); }).finally(() => { checking = false; });
57
- }, 5000);
58
- const status = await done;
59
- clearInterval(leaseCheck);
60
- clearTimeout(timeout);
61
- if (status !== 'completed')
62
- throw new Error(status === 'failed' ? 'production_native_failed' : 'production_native_unknown');
63
- }
64
- finally {
65
- unsubscribe?.();
66
- await adapter.close?.().catch(() => undefined);
67
- for (const group of groups)
68
- await record({ ...group, stopped: await stopProductionGroup(group) });
69
- }
70
- }
@@ -1,86 +0,0 @@
1
- import { spawn, execFile } from 'node:child_process';
2
- import { promisify } from 'node:util';
3
- import { readFile } from 'node:fs/promises';
4
- import { setTimeout as delay } from 'node:timers/promises';
5
- import { startLauncherProcess } from '../codex/launcher-client.js';
6
- export async function processBirth(pid) {
7
- if (process.platform !== 'linux') {
8
- return promisify(execFile)('ps', ['-p', String(pid), '-o', 'lstart='], { timeout: 2000 }).then(value => value.stdout.trim() || undefined, () => undefined);
9
- }
10
- try {
11
- const stat = await readFile(`/proc/${pid}/stat`, 'utf8');
12
- return stat.slice(stat.lastIndexOf(')') + 2).split(' ')[19];
13
- }
14
- catch {
15
- return undefined;
16
- }
17
- }
18
- export function groupExited(pid) {
19
- try {
20
- process.kill(-pid, 0);
21
- return false;
22
- }
23
- catch (error) {
24
- return error.code === 'ESRCH';
25
- }
26
- }
27
- export async function stopProductionGroup(record) {
28
- if (record.stopped || groupExited(record.pid))
29
- return true;
30
- if (record.birth && await processBirth(record.pid) !== record.birth)
31
- return false;
32
- for (const signal of ['SIGTERM', 'SIGKILL']) {
33
- try {
34
- process.kill(-record.pid, signal);
35
- }
36
- catch { /* Recheck whether the whole group exited. */ }
37
- const deadline = Date.now() + 2000;
38
- while (Date.now() < deadline) {
39
- if (groupExited(record.pid))
40
- return true;
41
- await delay(25);
42
- }
43
- }
44
- return groupExited(record.pid);
45
- }
46
- /** Only the save stage receives repository credentials; build and install receive none. */
47
- export async function runProductionCommand(state, input) {
48
- const launcher = !input.trusted ? process.env.PLAYCRAFT_LAUNCHER_SOCKET : undefined;
49
- const launched = launcher ? await startLauncherProcess({ socketPath: launcher, processId: input.processId,
50
- runtimeEpoch: state.runtimeEpoch, argv: ['/usr/bin/bash', '-lc', input.command], cwd: input.cwd, env: input.env ?? {} }) : undefined;
51
- const local = launched ? undefined : spawn('bash', ['-lc', input.command], { cwd: input.cwd,
52
- env: { PATH: process.env.PATH, HOME: state.config.stateDir, ...input.env }, detached: true, stdio: ['ignore', 'pipe', 'pipe'] });
53
- const pid = launched?.pid ?? local?.pid;
54
- if (!pid)
55
- throw new Error('production_process_not_started');
56
- const record = { pid, birth: await processBirth(pid), stopped: false };
57
- try {
58
- await input.record(record);
59
- }
60
- catch (error) {
61
- await stopProductionGroup(record);
62
- throw error;
63
- }
64
- let output = '';
65
- const collect = (chunk) => { output = (output + String(chunk)).slice(-64 * 1024); };
66
- (launched?.stdout ?? local.stdout).on('data', collect);
67
- (launched?.stderr ?? local.stderr).on('data', collect);
68
- launched?.stdin?.end();
69
- let timedOut = false;
70
- const code = await new Promise((resolve) => {
71
- const timeout = setTimeout(() => {
72
- timedOut = true;
73
- void (launched ? launched.stop() : stopProductionGroup(record)).catch(() => undefined).finally(() => resolve(undefined));
74
- }, input.timeoutMs);
75
- const finish = (code) => { clearTimeout(timeout); resolve(code); };
76
- if (launched)
77
- launched.onExit(finish);
78
- else {
79
- local.once('error', () => finish(undefined));
80
- local.once('close', finish);
81
- }
82
- });
83
- const stopped = await stopProductionGroup(record);
84
- await input.record({ ...record, stopped });
85
- return { code: timedOut ? undefined : code, output, stopped };
86
- }
@@ -1,20 +0,0 @@
1
- export function productionSaveCommand() {
2
- return `set -eu
3
- status=0
4
- remote=$(git -c protocol.version=0 -c core.hooksPath=/dev/null ls-remote --exit-code --heads "$PRODUCTION_REPOSITORY" "refs/heads/$PRODUCTION_BRANCH") || status=$?
5
- if [ "$status" != 0 ] && [ "$status" != 2 ]; then exit "$status"; fi
6
- if [ "\${PRODUCTION_EXPECTED_PARENT+x}" = x ]; then
7
- actual=$(printf '%s' "$remote" | cut -f1)
8
- test "$actual" = "$PRODUCTION_EXPECTED_PARENT" || { printf 'production_git_parent_changed\\n' >&2; exit 1; }
9
- fi
10
- if [ -n "$remote" ]; then
11
- parent=$(printf '%s' "$remote" | cut -f1)
12
- git -c protocol.version=0 -c core.hooksPath=/dev/null fetch --no-tags --no-recurse-submodules "$PRODUCTION_REPOSITORY" "refs/heads/$PRODUCTION_BRANCH"
13
- test "$(git rev-parse FETCH_HEAD)" = "$parent"
14
- git -c core.hooksPath=/dev/null -c core.fsmonitor=false reset --soft FETCH_HEAD
15
- fi
16
- git -c core.hooksPath=/dev/null -c core.fsmonitor=false add -A .
17
- git diff --cached --quiet || git -c core.hooksPath=/dev/null commit -m "Save generated engineering"
18
- git -c protocol.version=0 -c core.hooksPath=/dev/null push "$PRODUCTION_REPOSITORY" HEAD:"refs/heads/$PRODUCTION_BRANCH"
19
- git rev-parse HEAD`;
20
- }
@@ -1,175 +0,0 @@
1
- import { execFile } from 'node:child_process';
2
- import { createHash } from 'node:crypto';
3
- import { lstat, mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises';
4
- import { dirname, join } from 'node:path';
5
- import { promisify } from 'node:util';
6
- import { batchBaselineSnapshotSchema } from '@playcraft/common/cloud-workspace/runtime';
7
- import { checkoutBatchCommit, freezeBatchCommit } from '../workspaces/batch-commit.js';
8
- import { openDirectoryToDevelopment } from '../workspaces/directory-access.js';
9
- import { registerWorkspaceDirectory } from '../workspaces/directories.js';
10
- import { workspaceRoot, workspaceSessions } from '../workspaces/registry.js';
11
- import { excludeFromGit, settleDependencyInstalls } from '../workspaces/dependencies.js';
12
- import { resolveProductionDependencies, verifyProductionDependencyLocks } from './dependencies.js';
13
- import { productionSaveCommand } from './save-command.js';
14
- import { productionOutputStore, verifyProductionLease } from './storage.js';
15
- import { runProductionCommand } from './processes.js';
16
- import { runProductionNative } from './native.js';
17
- import { classifyPreviewOutput, findExternalLocalReferences } from '../previews/single-file.js';
18
- const execute = promisify(execFile);
19
- const quote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
20
- const hash = (body) => createHash('sha256').update(body).digest('hex');
21
- const record = (value) => value && typeof value === 'object' ? value : {};
22
- export async function executeProductionStage(state, input, processRecord, track) {
23
- await verifyProductionLease(input);
24
- const store = productionOutputStore(input);
25
- const stage = input.stage;
26
- const workspaceId = input.task.workspaceId;
27
- const resultId = String(input.payload.resultVersionId ?? input.task.payload.resultVersionId ?? input.task.attemptId);
28
- const previous = (name) => input.stages?.find(value => value.stage === name && value.status === 'completed')?.result;
29
- const run = async (cwd, command, env, trusted = false) => {
30
- const outcome = await runProductionCommand(state, { processId: `production-${input.task.attemptId}-${stage}`, cwd, command, env, trusted,
31
- timeoutMs: 10 * 60_000, record: processRecord });
32
- if (!outcome.stopped || outcome.code === undefined)
33
- throw new Error('production_process_unknown');
34
- if (outcome.code !== 0)
35
- throw new Error(`production_${stage}_failed`);
36
- return outcome.output;
37
- };
38
- const git = async (root, ...args) => (await execute('git', ['-c', 'core.hooksPath=/dev/null', '-c', 'core.fsmonitor=false', '-C', root, ...args], {
39
- timeout: 20_000, maxBuffer: 1024 * 1024, env: { PATH: process.env.PATH, HOME: state.config.stateDir, GIT_CONFIG_GLOBAL: '/dev/null', GIT_CONFIG_NOSYSTEM: '1' },
40
- })).stdout.trim();
41
- if (stage === 'prepare' && input.directoryName !== 'basic') {
42
- const baseline = record(input.payload.baseline ?? input.task.payload.baseline);
43
- const authority = record(input.payload.workspace);
44
- const sourceCommit = baseline.commitHash;
45
- if (typeof sourceCommit !== 'string')
46
- throw new Error('production_approved_commit_required');
47
- const directory = { workspaceId, directoryName: input.directoryName,
48
- remixId: String(authority.remixId), branchName: String(authority.branchName), sourceCommit };
49
- await registerWorkspaceDirectory(state, directory, state.acceptedLeaseFence);
50
- }
51
- const root = await workspaceRoot(state, workspaceId, input.directoryName);
52
- if (stage === 'capture-source') {
53
- if (input.directoryName !== 'basic')
54
- throw new Error('production_source_directory_mismatch');
55
- if (workspaceSessions(state, workspaceId, 'basic').some(session => session.turnBlock === 'uncertain' || ['starting', 'running', 'waiting_for_user'].includes(session.turnStatus ?? '')))
56
- throw new Error('production_source_busy');
57
- await settleDependencyInstalls(state);
58
- await resolveProductionDependencies(root);
59
- await excludeFromGit(root);
60
- await excludeFromGit(root, '/.env*');
61
- await git(root, 'config', 'user.name', 'PlayCraft');
62
- await git(root, 'config', 'user.email', 'playcraft@localhost');
63
- await run(root, 'git -c core.hooksPath=/dev/null add -A . && (git diff --cached --quiet || git -c core.hooksPath=/dev/null commit -m "Preserve basic before common repair")', undefined, true);
64
- const commitHash = await git(root, 'rev-parse', 'HEAD');
65
- const snapshot = await freezeBatchCommit({ source: { workspaceId, root }, commitHash, snapshotId: `${input.task.attemptId}-capture-source` });
66
- return { sourceSnapshot: snapshot, commitHash };
67
- }
68
- if (stage === 'prepare') {
69
- await settleDependencyInstalls(state);
70
- const policy = await resolveProductionDependencies(root);
71
- await run(root, process.env.PLAYCRAFT_PRODUCTION_INSTALL_COMMAND ?? 'pnpm install --frozen-lockfile --store-dir /var/cache/playcraft/pnpm-store --ignore-scripts');
72
- await verifyProductionDependencyLocks(root, policy);
73
- return { prepared: true, directoryName: input.directoryName };
74
- }
75
- if (stage === 'generate') {
76
- await runProductionNative(state, input, root, processRecord, track);
77
- await resolveProductionDependencies(root);
78
- return { generated: true };
79
- }
80
- if (stage === 'save') {
81
- if (!input.git)
82
- throw new Error('production_git_scope_required');
83
- const binding = state.journal.getWorkspace(workspaceId);
84
- const branch = input.directoryName === 'basic' ? binding.branchName : binding.directories?.find(value => value.directoryName === input.directoryName)?.branchName;
85
- if (input.git.branchName !== branch || await git(root, 'symbolic-ref', '--short', 'HEAD') !== branch)
86
- throw new Error('production_git_binding_mismatch');
87
- await excludeFromGit(root);
88
- await excludeFromGit(root, '/.env*');
89
- await excludeFromGit(root, '/.playcraft-temp/');
90
- await excludeFromGit(root, '/.playcraft-preview-out/');
91
- await git(root, 'config', 'user.name', 'PlayCraft');
92
- await git(root, 'config', 'user.email', 'playcraft@localhost');
93
- const env = { PRODUCTION_REPOSITORY: input.git.repositoryUrl, PRODUCTION_BRANCH: input.git.branchName,
94
- GIT_TERMINAL_PROMPT: '0', GIT_CONFIG_GLOBAL: '/dev/null', GIT_CONFIG_NOSYSTEM: '1' };
95
- if (input.git.username && input.git.password)
96
- Object.assign(env, { GIT_CONFIG_COUNT: '1',
97
- GIT_CONFIG_KEY_0: `http.${input.git.repositoryUrl}.extraHeader`,
98
- GIT_CONFIG_VALUE_0: `Authorization: Basic ${Buffer.from(`${input.git.username}:${input.git.password}`).toString('base64')}` });
99
- if (typeof input.payload.expectedParent === 'string')
100
- env.PRODUCTION_EXPECTED_PARENT = input.payload.expectedParent;
101
- await run(root, productionSaveCommand(), env, true);
102
- const commitHash = await git(root, 'rev-parse', 'HEAD');
103
- const snapshot = await freezeBatchCommit({ source: { workspaceId, root }, commitHash, snapshotId: `${input.task.attemptId}-save` });
104
- return { snapshot, saved: { attemptId: input.task.attemptId, resultVersionId: resultId, workspaceId, sourceDigest: snapshot.checksum, commitHash, pushedCommitHash: commitHash } };
105
- }
106
- if (stage === 'freeze') {
107
- const saved = record(previous('save')?.saved);
108
- if (typeof saved.commitHash !== 'string')
109
- throw new Error('production_saved_commit_required');
110
- const snapshot = await freezeBatchCommit({ source: { workspaceId, root }, commitHash: saved.commitHash, snapshotId: `${input.task.attemptId}-freeze` });
111
- const baseline = batchBaselineSnapshotSchema.parse({ batchId: input.task.batchId, projectId: String(input.payload.projectId ?? input.task.payload.projectId),
112
- snapshotId: snapshot.snapshotId, sourceWorkspaceId: snapshot.sourceWorkspaceId, commitHash: snapshot.commitHash, checksum: snapshot.checksum,
113
- sourceVersion: snapshot.sourceVersion, engine: String(input.payload.engine ?? input.task.payload.engine), skillLockDigest: snapshot.skillLockDigest, frozenAt: snapshot.frozenAt });
114
- return { snapshot, baseline };
115
- }
116
- if (stage === 'build') {
117
- const saved = record(input.payload.saved ?? previous('save')?.saved);
118
- const snapshot = (previous('freeze')?.snapshot ?? previous('save')?.snapshot);
119
- if (!snapshot || snapshot.commitHash !== saved.commitHash || snapshot.checksum !== saved.sourceDigest)
120
- throw new Error('production_saved_snapshot_mismatch');
121
- const destination = join(root, '.playcraft-temp', `production-${input.task.attemptId}`);
122
- await excludeFromGit(root, '/.playcraft-temp/');
123
- await rm(destination, { recursive: true, force: true });
124
- try {
125
- await checkoutBatchCommit({ source: { workspaceId, root }, frozen: snapshot, destination });
126
- await openDirectoryToDevelopment(destination, (await lstat(state.config.workspaceRoot)).gid);
127
- const policy = await resolveProductionDependencies(destination);
128
- await run(destination, process.env.PLAYCRAFT_PRODUCTION_INSTALL_COMMAND ?? 'pnpm install --frozen-lockfile --store-dir /var/cache/playcraft/pnpm-store --ignore-scripts');
129
- await verifyProductionDependencyLocks(destination, policy);
130
- const output = join(destination, '.production-html');
131
- const command = process.env.PLAYCRAFT_PRODUCTION_BUILD_COMMAND ?? 'playcraft build --mode full --platform playcraft --format html';
132
- await run(destination, `${command} --output ${quote(output)} .`);
133
- await verifyProductionDependencyLocks(destination, policy);
134
- const entries = await readdir(output, { withFileTypes: true });
135
- const single = classifyPreviewOutput(entries.map(entry => ({ name: entry.name, kind: entry.isFile() ? 'file' : entry.isDirectory() ? 'dir' : 'symlink' })));
136
- if (!single.ok)
137
- throw new Error('production_preview_not_single_file');
138
- const path = join(output, single.entry);
139
- if (!(await lstat(path)).isFile())
140
- throw new Error('production_preview_invalid');
141
- const body = await readFile(path);
142
- if (findExternalLocalReferences(body.toString()).length)
143
- throw new Error('production_preview_external_reference');
144
- const outputDigest = hash(body), outputKey = `workspaces/${workspaceId}/production/${input.task.attemptId}/${outputDigest}.html`;
145
- await store.put(outputKey, body);
146
- return { outputKey, outputDigest, build: { attemptId: input.task.attemptId, resultVersionId: resultId, workspaceId,
147
- sourceDigest: snapshot.checksum, commitHash: snapshot.commitHash, buildId: `${input.task.attemptId}-build`, succeeded: true } };
148
- }
149
- finally {
150
- await rm(destination, { recursive: true, force: true });
151
- }
152
- }
153
- if (stage === 'preview') {
154
- const built = previous('build');
155
- if (typeof built?.outputKey !== 'string' || typeof built.outputDigest !== 'string')
156
- throw new Error('production_build_required');
157
- const body = await store.get(built.outputKey);
158
- if (hash(body) !== built.outputDigest)
159
- throw new Error('production_preview_digest_mismatch');
160
- const previewPath = join(root, '.playcraft-temp', `${input.task.attemptId}.html`);
161
- await mkdir(dirname(previewPath), { recursive: true });
162
- await writeFile(previewPath, body);
163
- try {
164
- const browser = process.env.PLAYCRAFT_PRODUCTION_PREVIEW_COMMAND ?? 'chromium --headless --no-sandbox --disable-gpu --virtual-time-budget=1000 --dump-dom';
165
- const opened = await run(root, `${browser} ${quote(`file://${previewPath}`)}`);
166
- if (!/<html[\s>]/i.test(opened))
167
- throw new Error('production_preview_not_opened');
168
- return { opened: true, outputDigest: built.outputDigest };
169
- }
170
- finally {
171
- await rm(previewPath, { force: true });
172
- }
173
- }
174
- throw new Error('invalid_production_stage');
175
- }
@@ -1,32 +0,0 @@
1
- const taskUrl = (input) => `${input.callbackUrl.replace(/\/+$/, '')}/internal/cloud-production/tasks/${input.task.taskId}`;
2
- /** Build output travels through the Runtime that leased the task; engineering files travel through Git. */
3
- export function productionOutputStore(input) {
4
- const object = (key) => `${taskUrl(input)}/objects/${encodeURIComponent(key)}`;
5
- const headers = { authorization: `Bearer ${input.runnerToken}`, 'x-playcraft-object-client': 'cli' };
6
- return {
7
- async get(key) {
8
- const response = await fetch(object(key), { headers, redirect: 'error', signal: AbortSignal.timeout(60_000) });
9
- if (response.status === 404)
10
- throw new Error('object_missing');
11
- if (!response.ok)
12
- throw new Error('object_get_failed');
13
- return Buffer.from(await response.arrayBuffer());
14
- },
15
- async put(key, body) {
16
- const response = await fetch(object(key), { method: 'PUT', body: new Uint8Array(body), redirect: 'error', signal: AbortSignal.timeout(60_000),
17
- headers: { ...headers, 'content-type': 'application/octet-stream' } });
18
- if (!response.ok)
19
- throw new Error(response.status === 409 ? 'object_conflict' : 'object_put_failed');
20
- },
21
- };
22
- }
23
- export async function verifyProductionLease(input) {
24
- const response = await fetch(`${taskUrl(input)}/runner`, {
25
- headers: { authorization: `Bearer ${input.runnerToken}` }, redirect: 'error', signal: AbortSignal.timeout(10_000),
26
- });
27
- if (!response.ok)
28
- throw new Error('production_lease_unavailable');
29
- const { task } = await response.json();
30
- if (!task || task.taskId !== input.task.taskId || task.attemptId !== input.task.attemptId || task.leaseOwner !== input.lease.owner || task.leaseFence !== input.lease.fence || task.status !== 'running' || task.cancelRequested)
31
- throw new Error('production_lease_unavailable');
32
- }
@@ -1,199 +0,0 @@
1
- import { closeCliCredentials } from './workspaces/cli-credentials.js';
2
- import { randomUUID } from 'node:crypto';
3
- import { mkdir } from 'node:fs/promises';
4
- import { createServer } from 'node:http';
5
- import { INCOMPATIBLE_EXIT_CODE, negotiateProtocol, } from '@playcraft/common/cloud-workspace/runtime';
6
- import { RuntimeJournal } from './persistence/journal.js';
7
- import { createCodexAppServerAdapter } from './codex/app-server.js';
8
- import { createLoopbackNativeAdapter } from './codex/loopback.js';
9
- import { attachNativeCollector } from './server/dispatch.js';
10
- import { handleRuntimeRequest } from './server/http.js';
11
- import { desktopHost, disposeDesktopHost } from './server/desktop-host.js';
12
- import { disposeTerminalHost, terminalHost } from './server/terminal-host.js';
13
- import { handleExecutionGroupCrash } from './server/execution-group.js';
14
- import { RuntimeProcessLock } from './server/lock.js';
15
- import { probeGitBinary } from './workspaces/git.js';
16
- import { WorkspaceGitCredentialBroker } from './workspaces/git-credential-broker.js';
17
- import { WorkspaceModelBroker } from './codex/model-broker.js';
18
- import { recoverInterruptedPreviews } from './previews/publish.js';
19
- import { recoverInterruptedInstalls } from './workspaces/dependencies.js';
20
- import { loadWorkspaceSkillArtifact } from './workspaces/skill-artifact-loader.js';
21
- import { workspaceContextFromRoot } from './workspaces/registry.js';
22
- export async function startWorkspaceRuntime(config, options = {}) {
23
- process.env.PLAYCRAFT_SKIP_UPDATE_CHECK = '1';
24
- const negotiated = negotiateProtocol(config);
25
- if (!negotiated.ok) {
26
- const error = Object.assign(new Error('protocol_incompatible'), {
27
- code: 'protocol_incompatible',
28
- exitCode: INCOMPATIBLE_EXIT_CODE,
29
- });
30
- throw error;
31
- }
32
- const lock = new RuntimeProcessLock(config.stateDir);
33
- lock.acquire();
34
- let journal;
35
- let gitCredentials;
36
- try {
37
- journal = new RuntimeJournal(config.stateDir);
38
- await journal.load();
39
- const previousEpoch = journal.storedRuntimeEpoch();
40
- const previousGroup = journal.executionGroup();
41
- const previousIdentityMatches = journal.epochsSnapshot().some((epoch) => epoch.runtime_id === config.runtimeId && epoch.generation === config.generation && epoch.runtime_epoch === previousEpoch);
42
- await recoverInterruptedInstalls(journal);
43
- await recoverInterruptedPreviews(journal);
44
- await mkdir(config.workspaceRoot, { recursive: true, mode: 0o700 });
45
- const state = {
46
- config: { ...config, protocolMinor: negotiated.protocolMinor },
47
- configPath: options.configPath,
48
- journal,
49
- acceptedLeaseFence: journal.acceptedLeaseFence(),
50
- gitAvailable: await probeGitBinary(),
51
- native: options.nativeAdapter,
52
- runtimeEpoch: Math.max(config.runtimeEpoch, journal.storedRuntimeEpoch() ?? 0),
53
- executionQueue: Promise.resolve(),
54
- instanceToken: randomUUID(),
55
- };
56
- const native = state.native ??= defaultNativeAdapter(state);
57
- await journal.bindRuntime({
58
- runtimeId: config.runtimeId,
59
- generation: config.generation,
60
- runtimeEpoch: state.runtimeEpoch,
61
- });
62
- if (config.workspaceMode === 'multi') {
63
- gitCredentials = state.gitCredentials = new WorkspaceGitCredentialBroker(state);
64
- await gitCredentials.start();
65
- }
66
- attachNativeCollector(state);
67
- native?.onUnexpectedExit?.((event) => {
68
- void handleExecutionGroupCrash(state, event.pid);
69
- });
70
- try {
71
- await native?.start?.();
72
- const group = native?.executionProcess?.();
73
- if (group) {
74
- let previousStopped = false;
75
- if (previousGroup.pid && previousGroup.pid !== group.pid) {
76
- try {
77
- process.kill(previousGroup.pid, 0);
78
- }
79
- catch (error) {
80
- previousStopped = error.code === 'ESRCH';
81
- }
82
- }
83
- // The supervisor advances the trusted config epoch only after stopping the old
84
- // native group. Reconfirm its recorded PID before publishing that replacement.
85
- if (previousIdentityMatches && previousEpoch !== undefined && config.runtimeEpoch > previousEpoch && previousStopped) {
86
- await journal.writeExecutionEpoch({
87
- runtimeEpoch: state.runtimeEpoch, ...group, previousPid: previousGroup.pid, stopConfirmed: true,
88
- });
89
- await journal.replaceSessions(journal.listSessions().map((session) => ({ ...session, recoverability: 'history_only' })));
90
- }
91
- else {
92
- await journal.noteExecutionGroup(group);
93
- }
94
- }
95
- }
96
- catch (error) {
97
- const code = error instanceof Error ? error.message : '';
98
- state.nativeError = code === 'codex_model_catalog_unavailable' ? code : 'codex_start_failed';
99
- console.error(`[workspace-runtime] ${state.nativeError}`);
100
- }
101
- const server = createServer((request, response) => {
102
- void handleRuntimeRequest(state, request, response);
103
- });
104
- server.on('upgrade', (request, socket, head) => {
105
- const pathname = new URL(request.url ?? '/', 'http://127.0.0.1').pathname;
106
- if (/^\/runtime\/terminals\/[^/]+\/connect$/.test(pathname)) {
107
- void terminalHost(state).handleUpgrade(request, socket, head);
108
- return;
109
- }
110
- if (pathname === '/runtime/desktop/connect') {
111
- desktopHost(state).handleUpgrade(request, socket, head);
112
- return;
113
- }
114
- socket.destroy();
115
- });
116
- const port = await listenOn(server, config.listen.host, config.listen.port);
117
- return {
118
- config: state.config,
119
- state,
120
- url: `http://${formatHost(config.listen.host)}:${port}`,
121
- async close() {
122
- const serverClosed = closeServer(server);
123
- await closeCliCredentials(state);
124
- await gitCredentials?.close();
125
- try {
126
- await disposeDesktopHost(state);
127
- }
128
- finally {
129
- disposeTerminalHost(state);
130
- try {
131
- await native?.close?.();
132
- }
133
- catch {
134
- /* adapter shutdown is best-effort */
135
- }
136
- await serverClosed;
137
- await journal?.close();
138
- lock.release();
139
- }
140
- },
141
- };
142
- }
143
- catch (error) {
144
- await gitCredentials?.close().catch(() => undefined);
145
- await journal?.close().catch(() => undefined);
146
- lock.release();
147
- throw error;
148
- }
149
- }
150
- function listenOn(server, host, port) {
151
- return new Promise((resolve, reject) => {
152
- const onError = (error) => reject(error);
153
- server.once('error', onError);
154
- server.listen(port, host, () => {
155
- server.removeListener('error', onError);
156
- const address = server.address();
157
- if (!address || typeof address === 'string') {
158
- reject(new Error('listen_failed'));
159
- return;
160
- }
161
- resolve(address.port);
162
- });
163
- });
164
- }
165
- function closeServer(server) {
166
- return new Promise((resolve, reject) => {
167
- server.close((error) => {
168
- if (error)
169
- reject(error);
170
- else
171
- resolve();
172
- });
173
- });
174
- }
175
- function formatHost(host) {
176
- return host.includes(':') ? `[${host}]` : host;
177
- }
178
- function defaultNativeAdapter(state) {
179
- const config = state.config;
180
- if (process.env.PLAYCRAFT_NATIVE_ADAPTER === 'loopback')
181
- return createLoopbackNativeAdapter();
182
- const hasLauncher = Boolean(process.env.PLAYCRAFT_LAUNCHER_SOCKET);
183
- const multi = config.workspaceMode === 'multi';
184
- if (process.env.PLAYCRAFT_NATIVE_ADAPTER === 'codex' && !multi && !hasLauncher) {
185
- throw new Error('codex_requires_managed_launcher');
186
- }
187
- if (process.env.PLAYCRAFT_NATIVE_ADAPTER === 'codex' || (multi && hasLauncher)) {
188
- return createCodexAppServerAdapter({
189
- config,
190
- ...(multi ? { modelBroker: new WorkspaceModelBroker(state) } : {}),
191
- loadSkillArtifact: async (cwd, entry) => {
192
- const { workspaceId, directoryName } = await workspaceContextFromRoot(state, cwd);
193
- return loadWorkspaceSkillArtifact(state, workspaceId, entry, directoryName === 'root' ? 'basic' : directoryName);
194
- },
195
- onSkillProcessRestart: process => state.journal.noteExecutionGroup(process),
196
- });
197
- }
198
- return undefined;
199
- }
@@ -1,21 +0,0 @@
1
- import { timingSafeEqual } from 'node:crypto';
2
- export function extractControlToken(headers) {
3
- const authorization = headerValue(headers.authorization);
4
- if (authorization?.startsWith('Bearer ')) {
5
- return authorization.slice('Bearer '.length).trim() || null;
6
- }
7
- const dedicated = headerValue(headers['x-playcraft-control-token']);
8
- return dedicated?.trim() || null;
9
- }
10
- export function controlTokenMatches(expected, actual) {
11
- if (!actual)
12
- return false;
13
- const left = Buffer.from(expected);
14
- const right = Buffer.from(actual);
15
- if (left.length !== right.length)
16
- return false;
17
- return timingSafeEqual(left, right);
18
- }
19
- function headerValue(value) {
20
- return Array.isArray(value) ? value[0] : value;
21
- }