@playcraft/cli 0.1.33 → 0.1.35-prod

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 (346) hide show
  1. package/README.md +3 -56
  2. package/dist/agent/local-backend.js +1 -1
  3. package/dist/cli-root-help.js +0 -2
  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-3d.js +0 -11
  10. package/dist/commands/tools-generation.js +109 -68
  11. package/dist/commands/tools-image-context.js +23 -0
  12. package/dist/commands/tools.js +2 -2
  13. package/dist/config.js +4 -59
  14. package/dist/index.js +3 -36
  15. package/dist/remix/clone.js +0 -2
  16. package/dist/remix/init-template.js +0 -2
  17. package/dist/remix/pull.js +0 -2
  18. package/dist/remix/push.js +1 -21
  19. package/dist/utils/agent-api-client.js +30 -185
  20. package/dist/utils/image-asset-context.js +105 -0
  21. package/dist/utils/playcraft-http-client.js +4 -6
  22. package/package.json +5 -14
  23. package/project-template/.claude/agents/refs/ta-image-generation-detail.md +67 -123
  24. package/project-template/.claude/skills/playcraft-image-generation/SKILL.md +105 -44
  25. package/project-template/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +42 -33
  26. package/project-template/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +18 -20
  27. package/project-template/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +6 -7
  28. package/project-template/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +36 -39
  29. package/project-templates/phaser/.claude/agents/refs/ta-image-generation-detail.md +59 -59
  30. package/project-templates/phaser/.claude/skills/playcraft-image-generation/SKILL.md +105 -44
  31. package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +42 -33
  32. package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +18 -20
  33. package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +6 -7
  34. package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +36 -39
  35. package/project-templates/threejs/.claude/agents/refs/ta-image-generation-detail.md +59 -59
  36. package/project-templates/threejs/.claude/skills/playcraft-image-generation/SKILL.md +105 -44
  37. package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +42 -33
  38. package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +18 -20
  39. package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +6 -7
  40. package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +36 -39
  41. package/dist/auth/account-activation.js +0 -157
  42. package/dist/auth/messages.js +0 -30
  43. package/dist/commands/platform-skills.generated.js +0 -19
  44. package/dist/commands/playable.js +0 -53
  45. package/dist/commands/tools-batch.js +0 -87
  46. package/dist/commands/video.js +0 -111
  47. package/dist/commands/workspace-runtime.js +0 -54
  48. package/dist/commands/workspace.js +0 -35
  49. package/dist/project-skills/builtin-import.js +0 -225
  50. package/dist/project-skills/commands.js +0 -77
  51. package/dist/project-skills/lifecycle.js +0 -28
  52. package/dist/project-skills/local-run.js +0 -112
  53. package/dist/project-skills/local-store.js +0 -221
  54. package/dist/project-skills/messages.js +0 -80
  55. package/dist/project-skills/reconcile.js +0 -62
  56. package/dist/project-skills/remote-cache.js +0 -51
  57. package/dist/project-skills/validation.js +0 -19
  58. package/dist/recording/messages.js +0 -40
  59. package/dist/recording/project-digest.js +0 -85
  60. package/dist/recording/recorder.js +0 -293
  61. package/dist/recording/scenario.js +0 -50
  62. package/dist/utils/environment-endpoints.js +0 -17
  63. package/dist/utils/tool-operation-journal.js +0 -124
  64. package/dist/utils/video-evidence-git.js +0 -81
  65. package/dist/video-replication/client.js +0 -222
  66. package/dist/video-replication/comparison-evidence.js +0 -149
  67. package/dist/video-replication/comparison-files.js +0 -57
  68. package/dist/video-replication/comparison-log.js +0 -72
  69. package/dist/video-replication/comparison-messages.js +0 -104
  70. package/dist/video-replication/comparison-output.js +0 -63
  71. package/dist/video-replication/comparison-preflight.js +0 -66
  72. package/dist/video-replication/comparison.js +0 -216
  73. package/dist/video-replication/evidence.js +0 -165
  74. package/dist/video-replication/media.js +0 -111
  75. package/dist/video-replication/messages.js +0 -89
  76. package/dist/workspace/messages.js +0 -35
  77. package/dist/workspace/transfer-client.js +0 -174
  78. package/dist/workspace-runtime/codex/app-server.js +0 -965
  79. package/dist/workspace-runtime/codex/compact.js +0 -46
  80. package/dist/workspace-runtime/codex/config-toml.js +0 -107
  81. package/dist/workspace-runtime/codex/jsonrpc-stdio.js +0 -145
  82. package/dist/workspace-runtime/codex/launcher-client.js +0 -177
  83. package/dist/workspace-runtime/codex/loopback.js +0 -146
  84. package/dist/workspace-runtime/codex/mcp-config.js +0 -39
  85. package/dist/workspace-runtime/codex/model-broker.js +0 -263
  86. package/dist/workspace-runtime/codex/native-adapter.js +0 -9
  87. package/dist/workspace-runtime/codex/permissions.js +0 -70
  88. package/dist/workspace-runtime/codex/work-mode.js +0 -15
  89. package/dist/workspace-runtime/main.js +0 -10
  90. package/dist/workspace-runtime/persistence/journal.js +0 -355
  91. package/dist/workspace-runtime/persistence/native-subagents.js +0 -162
  92. package/dist/workspace-runtime/persistence/sqlite-client.js +0 -77
  93. package/dist/workspace-runtime/persistence/sqlite-worker.js +0 -1096
  94. package/dist/workspace-runtime/previews/output.js +0 -36
  95. package/dist/workspace-runtime/previews/publish.js +0 -262
  96. package/dist/workspace-runtime/previews/single-file.js +0 -43
  97. package/dist/workspace-runtime/processes/managed-writes.js +0 -69
  98. package/dist/workspace-runtime/production/contracts.js +0 -17
  99. package/dist/workspace-runtime/production/controller.js +0 -207
  100. package/dist/workspace-runtime/production/dependencies.js +0 -60
  101. package/dist/workspace-runtime/production/native.js +0 -203
  102. package/dist/workspace-runtime/production/processes.js +0 -86
  103. package/dist/workspace-runtime/production/save-command.js +0 -35
  104. package/dist/workspace-runtime/production/source-copy.js +0 -91
  105. package/dist/workspace-runtime/production/stages.js +0 -205
  106. package/dist/workspace-runtime/production/storage.js +0 -60
  107. package/dist/workspace-runtime/serve.js +0 -207
  108. package/dist/workspace-runtime/server/auth.js +0 -21
  109. package/dist/workspace-runtime/server/connectivity.js +0 -201
  110. package/dist/workspace-runtime/server/desktop-host.js +0 -429
  111. package/dist/workspace-runtime/server/desktop-preview-config.js +0 -81
  112. package/dist/workspace-runtime/server/desktop-preview-service.js +0 -157
  113. package/dist/workspace-runtime/server/desktop-preview-static.js +0 -53
  114. package/dist/workspace-runtime/server/desktop-preview.js +0 -189
  115. package/dist/workspace-runtime/server/dispatch.js +0 -1174
  116. package/dist/workspace-runtime/server/endpoints.js +0 -19
  117. package/dist/workspace-runtime/server/execution-group.js +0 -85
  118. package/dist/workspace-runtime/server/http.js +0 -1127
  119. package/dist/workspace-runtime/server/lock.js +0 -53
  120. package/dist/workspace-runtime/server/rfb-watch-gate.js +0 -168
  121. package/dist/workspace-runtime/server/terminal-host.js +0 -366
  122. package/dist/workspace-runtime/server/types.js +0 -1
  123. package/dist/workspace-runtime/server/workspace-transfer.js +0 -99
  124. package/dist/workspace-runtime/workspaces/archive.js +0 -207
  125. package/dist/workspace-runtime/workspaces/artifact-events.js +0 -55
  126. package/dist/workspace-runtime/workspaces/artifact-files.js +0 -197
  127. package/dist/workspace-runtime/workspaces/artifact-receipts.js +0 -89
  128. package/dist/workspace-runtime/workspaces/artifacts.js +0 -282
  129. package/dist/workspace-runtime/workspaces/attachment-content.js +0 -123
  130. package/dist/workspace-runtime/workspaces/attachments.js +0 -181
  131. package/dist/workspace-runtime/workspaces/batch-commit.js +0 -142
  132. package/dist/workspace-runtime/workspaces/cli-context.js +0 -23
  133. package/dist/workspace-runtime/workspaces/cli-credentials.js +0 -111
  134. package/dist/workspace-runtime/workspaces/composer-context.js +0 -125
  135. package/dist/workspace-runtime/workspaces/context-error.js +0 -2
  136. package/dist/workspace-runtime/workspaces/dependencies.js +0 -264
  137. package/dist/workspace-runtime/workspaces/developer-command.js +0 -64
  138. package/dist/workspace-runtime/workspaces/directories.js +0 -123
  139. package/dist/workspace-runtime/workspaces/directory-access.js +0 -37
  140. package/dist/workspace-runtime/workspaces/directory-attachments.js +0 -245
  141. package/dist/workspace-runtime/workspaces/directory-maintenance.js +0 -111
  142. package/dist/workspace-runtime/workspaces/directory-stats.js +0 -49
  143. package/dist/workspace-runtime/workspaces/disk-snapshot.js +0 -31
  144. package/dist/workspace-runtime/workspaces/empty-template.js +0 -40
  145. package/dist/workspace-runtime/workspaces/files-search.js +0 -116
  146. package/dist/workspace-runtime/workspaces/files.js +0 -120
  147. package/dist/workspace-runtime/workspaces/git-credential-broker.js +0 -312
  148. package/dist/workspace-runtime/workspaces/git-credential-helper.js +0 -68
  149. package/dist/workspace-runtime/workspaces/git-direct-store.js +0 -155
  150. package/dist/workspace-runtime/workspaces/git.js +0 -658
  151. package/dist/workspace-runtime/workspaces/instructions.js +0 -56
  152. package/dist/workspace-runtime/workspaces/json5-edit.js +0 -170
  153. package/dist/workspace-runtime/workspaces/library-attachments.js +0 -268
  154. package/dist/workspace-runtime/workspaces/parameters.js +0 -272
  155. package/dist/workspace-runtime/workspaces/prepare.js +0 -264
  156. package/dist/workspace-runtime/workspaces/registry.js +0 -236
  157. package/dist/workspace-runtime/workspaces/revisions.js +0 -31
  158. package/dist/workspace-runtime/workspaces/skill-artifact-loader.js +0 -91
  159. package/dist/workspace-runtime/workspaces/skills.js +0 -189
  160. package/dist/workspace-runtime/workspaces/transfer.js +0 -288
  161. package/dist/workspace-runtime/workspaces/turn-changes.js +0 -72
  162. package/dist/workspace-runtime/workspaces/video-attachments.js +0 -49
  163. package/project-template/.claude/skills/playcraft-replica-validation/SKILL.md +0 -25
  164. package/project-template/.claude/skills/playcraft-replica-validation/references/contracts.md +0 -79
  165. package/project-template/.claude/skills/playcraft-video-understanding/SKILL.md +0 -29
  166. package/project-template/.claude/skills/playcraft-video-understanding/references/contracts.md +0 -37
  167. package/project-template-v2/.claude/agents/artist.md +0 -82
  168. package/project-template-v2/.claude/agents/developer.md +0 -153
  169. package/project-template-v2/.claude/agents/game-designer.md +0 -264
  170. package/project-template-v2/.claude/agents/refs/artist-art-style-catalog.md +0 -533
  171. package/project-template-v2/.claude/agents/refs/artist-color-audio-recipes.md +0 -153
  172. package/project-template-v2/.claude/agents/refs/artist-dimension-axis.md +0 -27
  173. package/project-template-v2/.claude/agents/refs/artist-master-composite-recipes.md +0 -208
  174. package/project-template-v2/.claude/agents/refs/atom-skill-library.md +0 -81
  175. package/project-template-v2/.claude/agents/refs/developer-impl-cookbook.md +0 -432
  176. package/project-template-v2/.claude/agents/refs/framework-5-component-filter.md +0 -252
  177. package/project-template-v2/.claude/agents/refs/framework-game-feel-juice.md +0 -266
  178. package/project-template-v2/.claude/agents/refs/framework-mda.md +0 -147
  179. package/project-template-v2/.claude/agents/refs/game-designer-gameplay-sufficiency.md +0 -123
  180. package/project-template-v2/.claude/agents/refs/ta-3d-flip-recipe.md +0 -88
  181. package/project-template-v2/.claude/agents/refs/ta-atlas-deliverable-standard.md +0 -67
  182. package/project-template-v2/.claude/agents/refs/ta-batch-pipeline-recipes.md +0 -120
  183. package/project-template-v2/.claude/agents/refs/ta-image-generation-detail.md +0 -300
  184. package/project-template-v2/.claude/agents/refs/ta-image-ops-reference.md +0 -495
  185. package/project-template-v2/.claude/agents/refs/ta-pipeline-cookbook.md +0 -1141
  186. package/project-template-v2/.claude/agents/refs/ta-tools-reference.md +0 -111
  187. package/project-template-v2/.claude/agents/refs/ta-vfx-preset-catalog.md +0 -365
  188. package/project-template-v2/.claude/agents/refs/threejs-cannon-pitfalls.md +0 -412
  189. package/project-template-v2/.claude/agents/reviewer.md +0 -75
  190. package/project-template-v2/.claude/agents/technical-artist.md +0 -86
  191. package/project-template-v2/.claude/hooks/snapshot-milestone.mjs +0 -243
  192. package/project-template-v2/.claude/hooks/user-prompt.mjs +0 -133
  193. package/project-template-v2/.claude/settings.json +0 -33
  194. package/project-template-v2/.claude/skills/brainstorming/SKILL.md +0 -161
  195. package/project-template-v2/.claude/skills/brainstorming/scripts/frame-template.html +0 -270
  196. package/project-template-v2/.claude/skills/brainstorming/scripts/helper.js +0 -177
  197. package/project-template-v2/.claude/skills/brainstorming/scripts/server.cjs +0 -354
  198. package/project-template-v2/.claude/skills/brainstorming/scripts/start-server.sh +0 -148
  199. package/project-template-v2/.claude/skills/brainstorming/scripts/stop-server.sh +0 -56
  200. package/project-template-v2/.claude/skills/brainstorming/scripts/wait-for-selection.sh +0 -62
  201. package/project-template-v2/.claude/skills/brainstorming/spec-document-reviewer-prompt.md +0 -49
  202. package/project-template-v2/.claude/skills/brainstorming/visual-companion.md +0 -309
  203. package/project-template-v2/.claude/skills/playcraft-ad-psychology/SKILL.md +0 -182
  204. package/project-template-v2/.claude/skills/playcraft-art-style-guide/SKILL.md +0 -123
  205. package/project-template-v2/.claude/skills/playcraft-asset-state-sheet/SKILL.md +0 -205
  206. package/project-template-v2/.claude/skills/playcraft-audio-generation/SKILL.md +0 -280
  207. package/project-template-v2/.claude/skills/playcraft-batch-pipeline/SKILL.md +0 -184
  208. package/project-template-v2/.claude/skills/playcraft-build-optimizer/SKILL.md +0 -306
  209. package/project-template-v2/.claude/skills/playcraft-image-generation/SKILL.md +0 -298
  210. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/build-sprite-sheet.template.mjs +0 -123
  211. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/compare-style.template.mjs +0 -254
  212. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +0 -324
  213. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +0 -97
  214. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-edit-variants.template.mjs +0 -118
  215. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +0 -137
  216. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +0 -397
  217. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/validate-sprite-sheet.template.mjs +0 -296
  218. package/project-template-v2/.claude/skills/playcraft-image-ops/SKILL.md +0 -122
  219. package/project-template-v2/.claude/skills/playcraft-image-processing/SKILL.md +0 -219
  220. package/project-template-v2/.claude/skills/playcraft-masking/SKILL.md +0 -373
  221. package/project-template-v2/.claude/skills/playcraft-playable-optimization/SKILL.md +0 -161
  222. package/project-template-v2/.claude/skills/playcraft-research/SKILL.md +0 -215
  223. package/project-template-v2/.claude/skills/playcraft-skill-recommender/SKILL.md +0 -382
  224. package/project-template-v2/.claude/skills/playcraft-sprite-generation/SKILL.md +0 -423
  225. package/project-template-v2/.claude/skills/playcraft-sprite-remix/SKILL.md +0 -158
  226. package/project-template-v2/.claude/skills/playcraft-sprite-sheet/SKILL.md +0 -100
  227. package/project-template-v2/.claude/skills/playcraft-storyboard/SKILL.md +0 -167
  228. package/project-template-v2/.claude/skills/playcraft-style-qa/SKILL.md +0 -270
  229. package/project-template-v2/.claude/skills/playcraft-text-rendering/SKILL.md +0 -236
  230. package/project-template-v2/.claude/skills/playcraft-vfx-animation/SKILL.md +0 -130
  231. package/project-template-v2/.claude/skills/playwright-cli/SKILL.md +0 -390
  232. package/project-template-v2/.claude/skills/playwright-cli/references/element-attributes.md +0 -23
  233. package/project-template-v2/.claude/skills/playwright-cli/references/playwright-tests.md +0 -39
  234. package/project-template-v2/.claude/skills/playwright-cli/references/request-mocking.md +0 -87
  235. package/project-template-v2/.claude/skills/playwright-cli/references/running-code.md +0 -240
  236. package/project-template-v2/.claude/skills/playwright-cli/references/session-management.md +0 -226
  237. package/project-template-v2/.claude/skills/playwright-cli/references/spec-driven-testing.md +0 -308
  238. package/project-template-v2/.claude/skills/playwright-cli/references/storage-state.md +0 -275
  239. package/project-template-v2/.claude/skills/playwright-cli/references/test-generation.md +0 -134
  240. package/project-template-v2/.claude/skills/playwright-cli/references/tracing.md +0 -142
  241. package/project-template-v2/.claude/skills/playwright-cli/references/video-recording.md +0 -153
  242. package/project-template-v2/.claude/skills/session-analyzer/SKILL.md +0 -386
  243. package/project-template-v2/.claude/skills/session-analyzer/scripts/execution-breakdown.mjs +0 -182
  244. package/project-template-v2/.claude/skills/session-analyzer/scripts/find-turns.mjs +0 -72
  245. package/project-template-v2/.claude/skills/session-analyzer/scripts/heavy-output.mjs +0 -121
  246. package/project-template-v2/.claude/skills/session-analyzer/scripts/resolve-session.mjs +0 -102
  247. package/project-template-v2/.claude/skills/session-analyzer/scripts/subagent-stats.mjs +0 -127
  248. package/project-template-v2/.claude/skills/session-analyzer/scripts/subagent-tool-timeline.mjs +0 -106
  249. package/project-template-v2/.claude/skills/session-analyzer/scripts/time-gaps.mjs +0 -128
  250. package/project-template-v2/.claude/skills/session-analyzer/scripts/turn-timeline.mjs +0 -67
  251. package/project-template-v2/.claude/snapshot.mjs +0 -263
  252. package/project-template-v2/.playcraft/skills.lock.json +0 -152
  253. package/project-template-v2/CLAUDE.md +0 -146
  254. package/project-template-v2/assets/audio/bgm/.gitkeep +0 -0
  255. package/project-template-v2/assets/audio/sfx/.gitkeep +0 -0
  256. package/project-template-v2/assets/bundles/.gitkeep +0 -0
  257. package/project-template-v2/assets/images/bg/.gitkeep +0 -0
  258. package/project-template-v2/assets/images/reference/.gitkeep +0 -0
  259. package/project-template-v2/assets/images/storyboard/.gitkeep +0 -0
  260. package/project-template-v2/assets/images/tiles/.gitkeep +0 -0
  261. package/project-template-v2/assets/images/ui/.gitkeep +0 -0
  262. package/project-template-v2/assets/images/vfx/.gitkeep +0 -0
  263. package/project-template-v2/assets/models/.gitkeep +0 -0
  264. package/project-template-v2/docs/harness/iteration-1/context-flow.md +0 -254
  265. package/project-template-v2/docs/harness/iteration-1/generate-flow.md +0 -91
  266. package/project-template-v2/docs/harness/iteration-1/ideate-flow.md +0 -214
  267. package/project-template-v2/docs/harness/iteration-1/optimize-flow.md +0 -75
  268. package/project-template-v2/docs/harness/iteration-1/wrapup-flow.md +0 -63
  269. package/project-template-v2/docs/harness/iteration-2/context-flow.md +0 -223
  270. package/project-template-v2/docs/harness/iteration-2/generate-flow.md +0 -129
  271. package/project-template-v2/docs/harness/iteration-2/ideate-flow.md +0 -267
  272. package/project-template-v2/docs/harness/iteration-2/optimize-flow.md +0 -164
  273. package/project-template-v2/docs/harness/iteration-2/wrapup-flow.md +0 -115
  274. package/project-template-v2/docs/harness/orchestrator-flow.md +0 -364
  275. package/project-template-v2/docs/project-state.json +0 -60
  276. package/project-template-v2/docs/project-state.md +0 -72
  277. package/project-template-v2/docs/standards/README.md +0 -225
  278. package/project-template-v2/docs/standards/agent-behavior-standards.md +0 -174
  279. package/project-template-v2/docs/standards/artifacts/design-brief.md +0 -19
  280. package/project-template-v2/docs/standards/artifacts/design.md +0 -22
  281. package/project-template-v2/docs/standards/artifacts/game-code.md +0 -41
  282. package/project-template-v2/docs/standards/artifacts/todo-list.md +0 -41
  283. package/project-template-v2/docs/standards/iter1-agent-behavior-standards.md +0 -343
  284. package/project-template-v2/game/index.ts +0 -18
  285. package/project-template-v2/globals.d.ts +0 -51
  286. package/project-template-v2/index.css +0 -34
  287. package/project-template-v2/index.html +0 -18
  288. package/project-template-v2/main.ts +0 -9
  289. package/project-template-v2/package.json +0 -46
  290. package/project-template-v2/skills/_shared/scripts/dispatch-clear.mjs +0 -31
  291. package/project-template-v2/skills/_shared/scripts/dispatch-set.mjs +0 -86
  292. package/project-template-v2/skills/_shared/scripts/dod-check.mjs +0 -153
  293. package/project-template-v2/skills/_shared/scripts/handoff-append.mjs +0 -70
  294. package/project-template-v2/skills/_shared/scripts/lib/validator-artifacts.mjs +0 -91
  295. package/project-template-v2/skills/_shared/scripts/pipeline/dod-config.mjs +0 -131
  296. package/project-template-v2/skills/_shared/scripts/pipeline/index.mjs +0 -80
  297. package/project-template-v2/skills/_shared/scripts/pipeline/iteration-1-core.mjs +0 -90
  298. package/project-template-v2/skills/_shared/scripts/pipeline/iteration-2-wrap.mjs +0 -93
  299. package/project-template-v2/skills/_shared/scripts/pipeline/iteration-3-visual.mjs +0 -23
  300. package/project-template-v2/skills/_shared/scripts/render-project-state.mjs +0 -230
  301. package/project-template-v2/skills/_shared/scripts/state-advance-stage.mjs +0 -58
  302. package/project-template-v2/skills/_shared/scripts/state-get.mjs +0 -339
  303. package/project-template-v2/skills/_shared/scripts/state-handoff.mjs +0 -39
  304. package/project-template-v2/skills/_shared/scripts/state-set.mjs +0 -94
  305. package/project-template-v2/skills/_shared/scripts/state-store.mjs +0 -783
  306. package/project-template-v2/skills/_shared/scripts/todo-add.mjs +0 -88
  307. package/project-template-v2/skills/_shared/scripts/todo-get.mjs +0 -130
  308. package/project-template-v2/skills/_shared/scripts/todo-remove.mjs +0 -33
  309. package/project-template-v2/skills/_shared/scripts/todo-set.mjs +0 -47
  310. package/project-template-v2/skills/_shared/scripts/verify-asset-code-sync.mjs +0 -268
  311. package/project-template-v2/skills/_shared/scripts/verify-env.mjs +0 -285
  312. package/project-template-v2/skills/_shared/scripts/verify-placeholders.mjs +0 -161
  313. package/project-template-v2/skills/playable-autoplay/SKILL.md +0 -176
  314. package/project-template-v2/skills/playable-autoplay/agents/openai.yaml +0 -4
  315. package/project-template-v2/skills/playable-debug/SKILL.md +0 -116
  316. package/project-template-v2/skills/playable-debug/agents/openai.yaml +0 -4
  317. package/project-template-v2/skills/playable-debug/references/debug-config.md +0 -40
  318. package/project-template-v2/skills/playable-record/SKILL.md +0 -140
  319. package/project-template-v2/skills/playable-record/scripts/lib/dev-server.mjs +0 -104
  320. package/project-template-v2/skills/playable-record/scripts/lib/record-audio-bridge.js +0 -141
  321. package/project-template-v2/skills/playable-record/scripts/record-playable.mjs +0 -425
  322. package/project-template-v2/skills/playable-record/scripts/verify-contract.mjs +0 -261
  323. package/project-template-v2/skills/playable-record/scripts/verify-firstwin.mjs +0 -398
  324. package/project-template-v2/skills/playable-record/scripts/verify-fusion.mjs +0 -322
  325. package/project-template-v2/skills/playable-record/scripts/verify-lifecycle.mjs +0 -105
  326. package/project-template-v2/skills/playable-record/scripts/verify-vlm-video.mjs +0 -230
  327. package/project-template-v2/skills/playable-validate/SKILL.md +0 -61
  328. package/project-template-v2/skills/playable-validate/validation-rules.md +0 -14
  329. package/project-template-v2/skills/playable-verify-ui/SKILL.md +0 -63
  330. package/project-template-v2/skills/playable-verify-ui/scripts/lib/__init__.py +0 -1
  331. package/project-template-v2/skills/playable-verify-ui/scripts/lib/config_expr.js +0 -218
  332. package/project-template-v2/skills/playable-verify-ui/scripts/lib/node_utils.js +0 -51
  333. package/project-template-v2/skills/playable-verify-ui/scripts/lib/profile_loader.py +0 -59
  334. package/project-template-v2/skills/playable-verify-ui/scripts/lib/report.py +0 -106
  335. package/project-template-v2/skills/playable-verify-ui/scripts/lib/runtime_contract.js +0 -116
  336. package/project-template-v2/skills/playable-verify-ui/scripts/verify_fetch_antipatterns.py +0 -59
  337. package/project-template-v2/skills/playable-verify-ui/scripts/verify_hardcoded_layout.js +0 -64
  338. package/project-template-v2/skills/playable-verify-ui/scripts/verify_runtime_contract.js +0 -58
  339. package/project-template-v2/skills/playable-verify-ui/scripts/verify_text_overlap.py +0 -333
  340. package/project-template-v2/ta-workspace/scripts/.gitkeep +0 -0
  341. package/project-template-v2/tsconfig.json +0 -20
  342. package/project-template-v2/vite.config.ts +0 -27
  343. package/project-templates/batch-locks/phaser.yaml +0 -922
  344. package/project-templates/batch-locks/threejs.yaml +0 -978
  345. package/project-templates/empty/AGENTS.md +0 -39
  346. package/project-templates/empty/gitignore +0 -33
@@ -1,1096 +0,0 @@
1
- /**
2
- * Dedicated SQLite writer for the CLI platform state file.
3
- * The main thread never opens runtime-state.sqlite.
4
- *
5
- * Binding pin: better-sqlite3@13.0.3 vendors SQLite 3.53.4, which includes the
6
- * official WAL-reset fix (3.51.3). Node's built-in node:sqlite on Node 22.22.1
7
- * reports 3.51.2 and must not be used for this file.
8
- * Do not read Codex JSONL from this worker.
9
- */
10
- import { createHash, randomBytes, randomUUID } from 'node:crypto';
11
- import { chmodSync, mkdirSync } from 'node:fs';
12
- import { dirname } from 'node:path';
13
- import { parentPort } from 'node:worker_threads';
14
- import Database from 'better-sqlite3-pinned';
15
- import { clearPendingNativeFollowups, learnNativeSubagents, nativeEventOwner, nativeSubagentThreads } from './native-subagents.js';
16
- import { hashOperationPayload, operationRecordSchema, pendingSourceEventSchema, terminalRecordSchema, workspaceRegistrationSchema, transitionOperation, canTransitionTurn, } from '@playcraft/common/cloud-workspace/runtime';
17
- const SCHEMA_VERSION = 1;
18
- const MIN_SQLITE = [3, 51, 3];
19
- const BINDING = 'better-sqlite3@13.0.3';
20
- const MAX_PINS = 32;
21
- const MAX_PIN_MS = 120_000;
22
- let db;
23
- const port = parentPort;
24
- if (!port)
25
- throw new Error('sqlite_worker_without_parent');
26
- port.on('message', (message) => {
27
- try {
28
- const result = dispatch(message.type, message.payload ?? {});
29
- port.postMessage({ id: message.id, ok: true, result });
30
- }
31
- catch (error) {
32
- port.postMessage({
33
- id: message.id,
34
- ok: false,
35
- error: error instanceof Error ? error.message : 'sqlite_worker_failed',
36
- });
37
- }
38
- });
39
- function dispatch(type, payload) {
40
- if (type === 'open')
41
- return openDatabase(asRecord(payload));
42
- if (type === 'close') {
43
- db?.close();
44
- db = undefined;
45
- return { closed: true };
46
- }
47
- const database = requireDb();
48
- const body = asRecord(payload);
49
- const write = database.transaction(() => {
50
- switch (type) {
51
- case 'upsertRecord':
52
- upsertRecord(database, String(body.bucket), String(body.key), body.body);
53
- return;
54
- case 'deleteRecord':
55
- database.prepare('DELETE FROM records WHERE bucket = ? AND record_key = ?').run(body.bucket, body.key);
56
- return;
57
- case 'replaceBucket':
58
- replaceBucket(database, String(body.bucket), Array.isArray(body.entries) ? body.entries : []);
59
- return;
60
- case 'forgetWorkspaces':
61
- database.prepare(`DELETE FROM records WHERE bucket = 'workspaces'`).run();
62
- return;
63
- case 'putOperation':
64
- putOperation(database, String(body.scopeKey), body.operation);
65
- return;
66
- case 'setAcceptedFence':
67
- setMeta(database, 'accepted_lease_fence', String(Math.max(metaNumber(database, 'accepted_lease_fence'), Number(body.value) || 0)));
68
- return;
69
- case 'setDestroyed':
70
- setMeta(database, 'destroyed', '1');
71
- return;
72
- case 'noteExecutionGroup':
73
- setMeta(database, 'execution_group', JSON.stringify({ ...(executionGroup(database)), ...asRecord(body.group) }));
74
- return;
75
- case 'setStopConfirmed':
76
- setMeta(database, 'execution_group', JSON.stringify({ ...executionGroup(database), stopConfirmed: body.stopConfirmed === true }));
77
- return;
78
- case 'writeExecutionEpoch':
79
- writeExecutionEpoch(database, body);
80
- return;
81
- case 'bindRuntime':
82
- bindRuntime(database, body);
83
- return;
84
- case 'allocateEvent':
85
- return allocateEvent(database, body);
86
- case 'commitIdentity':
87
- commitIdentity(database, body.event);
88
- return;
89
- case 'ingest':
90
- return ingest(database, body);
91
- case 'bindTurn':
92
- return bindTurn(database, body);
93
- case 'ack':
94
- acknowledge(database, body);
95
- return;
96
- case 'createPin':
97
- return createPin(database, body);
98
- case 'deletePin':
99
- database.prepare('DELETE FROM replay_pins WHERE pin_id = ?').run(String(body.pinId));
100
- return { deleted: true };
101
- case 'reclaim':
102
- reclaim(database, Number(body.minRetainMs) || 0);
103
- return;
104
- case 'maintain':
105
- database.pragma('wal_checkpoint(PASSIVE)');
106
- return;
107
- default:
108
- throw new Error(`unknown_sqlite_command:${type}`);
109
- }
110
- });
111
- const extra = write();
112
- return { ...dump(database), ...(extra && typeof extra === 'object' ? extra : {}) };
113
- }
114
- function openDatabase(payload) {
115
- if (db)
116
- throw new Error('sqlite_already_open');
117
- const filePath = String(payload.filePath ?? '');
118
- if (!filePath)
119
- throw new Error('sqlite_path_missing');
120
- mkdirSync(dirname(filePath), { recursive: true, mode: 0o700 });
121
- const database = new Database(filePath);
122
- db = database;
123
- const version = String(database.prepare('select sqlite_version() as v').get().v);
124
- if (!sqliteAtLeast(version, MIN_SQLITE)) {
125
- database.close();
126
- db = undefined;
127
- throw new Error(`sqlite_engine_too_old:${version}`);
128
- }
129
- const existing = readSchemaVersion(database);
130
- if (existing !== null && existing !== SCHEMA_VERSION) {
131
- database.close();
132
- db = undefined;
133
- throw new Error(`unsupported_runtime_state:${existing}`);
134
- }
135
- database.pragma('journal_mode = WAL');
136
- database.pragma('synchronous = FULL');
137
- database.pragma('wal_autocheckpoint = 0');
138
- database.pragma('busy_timeout = 2000');
139
- const journalMode = String(database.pragma('journal_mode', { simple: true }));
140
- const synchronous = Number(database.pragma('synchronous', { simple: true }));
141
- if (journalMode !== 'wal')
142
- throw new Error(`journal_mode_not_wal:${journalMode}`);
143
- if (synchronous !== 2)
144
- throw new Error(`synchronous_not_full:${synchronous}`);
145
- chmodSync(filePath, 0o600);
146
- let imported = false;
147
- if (existing === null) {
148
- createSchema(database);
149
- if (payload.legacy && typeof payload.legacy === 'object') {
150
- importLegacy(database, payload.legacy);
151
- imported = true;
152
- }
153
- quarantineReservedTurns(database);
154
- }
155
- else {
156
- quarantineReservedTurns(database);
157
- }
158
- database.exec(`
159
- CREATE INDEX IF NOT EXISTS native_inbox_pending_thread
160
- ON native_inbox(thread_id, ingress_sequence) WHERE converted = 0;
161
- CREATE INDEX IF NOT EXISTS records_native_session_thread
162
- ON records(json_extract(body_json, '$.threadId')) WHERE bucket = 'sessions';
163
- CREATE INDEX IF NOT EXISTS native_inbox_pending_parent
164
- ON native_inbox(COALESCE(json_extract(payload_json, '$.thread.parentThreadId'),
165
- json_extract(payload_json, '$.thread.source.subAgent.thread_spawn.parent_thread_id')))
166
- WHERE converted = 0 AND method = 'thread/started';
167
- `);
168
- return {
169
- ...dump(database),
170
- imported,
171
- engine: { version, journalMode, synchronous, binding: BINDING },
172
- };
173
- }
174
- function createSchema(database) {
175
- database.exec(`
176
- CREATE TABLE IF NOT EXISTS library_meta (
177
- key TEXT PRIMARY KEY,
178
- value TEXT NOT NULL
179
- );
180
- CREATE TABLE IF NOT EXISTS source_epochs (
181
- runtime_id TEXT NOT NULL,
182
- generation INTEGER NOT NULL,
183
- runtime_epoch INTEGER NOT NULL,
184
- next_sequence INTEGER NOT NULL,
185
- local_durable_through INTEGER NOT NULL,
186
- cloud_ack_through INTEGER NOT NULL,
187
- retained_from INTEGER NOT NULL,
188
- PRIMARY KEY (runtime_id, generation, runtime_epoch)
189
- );
190
- CREATE TABLE IF NOT EXISTS source_events (
191
- runtime_id TEXT NOT NULL,
192
- generation INTEGER NOT NULL,
193
- runtime_epoch INTEGER NOT NULL,
194
- source_sequence INTEGER NOT NULL,
195
- payload_hash TEXT NOT NULL,
196
- workspace_id TEXT,
197
- session_id TEXT,
198
- operation_id TEXT,
199
- thread_id TEXT,
200
- turn_id TEXT,
201
- item_id TEXT,
202
- type TEXT NOT NULL,
203
- payload_json TEXT NOT NULL,
204
- occurred_at TEXT NOT NULL,
205
- PRIMARY KEY (runtime_id, generation, runtime_epoch, source_sequence)
206
- );
207
- CREATE TABLE IF NOT EXISTS native_inbox (
208
- ingress_sequence INTEGER PRIMARY KEY,
209
- runtime_epoch INTEGER NOT NULL,
210
- kind TEXT NOT NULL,
211
- method TEXT,
212
- thread_id TEXT,
213
- turn_id TEXT,
214
- item_id TEXT,
215
- payload_json TEXT NOT NULL,
216
- converted INTEGER NOT NULL DEFAULT 0,
217
- source_sequence INTEGER,
218
- received_at TEXT NOT NULL
219
- );
220
- CREATE TABLE IF NOT EXISTS operation_intents (
221
- scope_key TEXT PRIMARY KEY,
222
- operation_json TEXT NOT NULL,
223
- gateway_operation_id TEXT,
224
- sandbox_operation_id TEXT NOT NULL,
225
- payload_hash TEXT NOT NULL,
226
- native_request_id TEXT,
227
- thread_id TEXT,
228
- turn_id TEXT,
229
- reconcile_status TEXT NOT NULL
230
- );
231
- CREATE TABLE IF NOT EXISTS replay_pins (
232
- pin_id TEXT PRIMARY KEY,
233
- epochs_json TEXT NOT NULL,
234
- created_at TEXT NOT NULL,
235
- expires_at TEXT NOT NULL
236
- );
237
- CREATE TABLE IF NOT EXISTS records (
238
- bucket TEXT NOT NULL,
239
- record_key TEXT NOT NULL,
240
- body_json TEXT NOT NULL,
241
- PRIMARY KEY (bucket, record_key)
242
- );
243
- `);
244
- setMeta(database, 'schema_version', String(SCHEMA_VERSION));
245
- setMeta(database, 'migration_status', 'complete');
246
- setMeta(database, 'source_read_token', randomBytes(32).toString('base64url'));
247
- setMeta(database, 'accepted_lease_fence', '0');
248
- setMeta(database, 'destroyed', '0');
249
- setMeta(database, 'runtime_epoch', '0');
250
- setMeta(database, 'ingress_counter', '0');
251
- setMeta(database, 'execution_group', JSON.stringify({ stopConfirmed: false, replaced: false }));
252
- }
253
- function readSchemaVersion(database) {
254
- const table = database
255
- .prepare(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'library_meta'`)
256
- .get();
257
- if (!table)
258
- return null;
259
- const row = database.prepare(`SELECT value FROM library_meta WHERE key = 'schema_version'`).get();
260
- if (!row?.value)
261
- return null;
262
- const parsed = Number(row.value);
263
- return Number.isInteger(parsed) ? parsed : null;
264
- }
265
- function importLegacy(database, legacy) {
266
- const snapshot = asRecord(legacy);
267
- const operations = asRecord(snapshot.operations);
268
- for (const [scopeKey, value] of Object.entries(operations)) {
269
- const parsed = operationRecordSchema.safeParse(value);
270
- if (!parsed.success)
271
- continue;
272
- putOperation(database, scopeKey, parsed.data);
273
- }
274
- const events = asRecord(snapshot.events);
275
- const sequences = [];
276
- for (const value of Object.values(events)) {
277
- const parsed = pendingSourceEventSchema.safeParse(value);
278
- if (!parsed.success)
279
- continue;
280
- const hash = hashOperationPayload(parsed.data.payload);
281
- if (hash !== parsed.data.payloadHash)
282
- continue;
283
- insertEventRow(database, parsed.data, {});
284
- sequences.push(parsed.data.source.sourceSequence);
285
- }
286
- const declared = Number(snapshot.lastSourceSequence) || 0;
287
- const maxImported = sequences.reduce((max, value) => Math.max(max, value), 0);
288
- const counter = Math.max(declared, maxImported, 0);
289
- const runtimeId = firstString(snapshot, 'runtimeId') || eventRuntime(events, 'runtimeId');
290
- const generation = Number.isInteger(snapshot.generation) ? Number(snapshot.generation) : eventRuntimeNumber(events, 'generation');
291
- const runtimeEpoch = Number.isInteger(snapshot.runtimeEpoch)
292
- ? Number(snapshot.runtimeEpoch)
293
- : eventRuntimeNumber(events, 'runtimeEpoch');
294
- if (runtimeId) {
295
- ensureEpoch(database, runtimeId, generation, runtimeEpoch);
296
- database
297
- .prepare(`UPDATE source_epochs SET next_sequence = ?, local_durable_through = ?
298
- WHERE runtime_id = ? AND generation = ? AND runtime_epoch = ?`)
299
- .run(counter, counter, runtimeId, generation, runtimeEpoch);
300
- setMeta(database, 'runtime_id', runtimeId);
301
- setMeta(database, 'generation', String(generation));
302
- }
303
- importBucket(database, 'workspaces', asRecord(snapshot.workspaces), (value) => workspaceRegistrationSchema.safeParse(value).success);
304
- importBucket(database, 'sessions', asRecord(snapshot.sessions), (value) => {
305
- const session = asRecord(value);
306
- return typeof session.sessionId === 'string' && typeof session.threadId === 'string';
307
- });
308
- importBucket(database, 'terminals', asRecord(snapshot.terminals), (value) => terminalRecordSchema.safeParse(value).success);
309
- importBucket(database, 'approvals', asRecord(snapshot.approvals), (value) => isApproval(value));
310
- importBucket(database, 'managed_writes', asRecord(snapshot.managedWrites), () => true);
311
- importBucket(database, 'file_revisions', asRecord(snapshot.fileRevisions), () => true);
312
- importBucket(database, 'receipts', asRecord(snapshot.receipts), () => true);
313
- const workspaceFiles = asRecord(snapshot.workspaceFileRevisions);
314
- for (const [workspaceId, files] of Object.entries(workspaceFiles)) {
315
- importBucket(database, `workspace_files:${workspaceId}`, asRecord(files), () => true);
316
- }
317
- setMeta(database, 'accepted_lease_fence', String(Number(snapshot.acceptedLeaseFence) || 0));
318
- setMeta(database, 'destroyed', snapshot.destroyed === true ? '1' : '0');
319
- setMeta(database, 'runtime_epoch', String(runtimeEpoch));
320
- if (snapshot.executionGroup && typeof snapshot.executionGroup === 'object') {
321
- setMeta(database, 'execution_group', JSON.stringify(snapshot.executionGroup));
322
- }
323
- setMeta(database, 'migration_status', 'complete');
324
- }
325
- function quarantineReservedTurns(database) {
326
- const rows = database.prepare('SELECT scope_key, operation_json FROM operation_intents').all();
327
- for (const row of rows) {
328
- const parsed = operationRecordSchema.safeParse(JSON.parse(row.operation_json));
329
- if (!parsed.success)
330
- continue;
331
- const operation = parsed.data;
332
- if (!['session.turn', 'session.review', 'session.compact', 'session.steer'].includes(operation.kind) || operation.status !== 'dispatched')
333
- continue;
334
- if (!operation.nativeRequestId || (operation.kind !== 'session.steer' && operation.nativeTurnId))
335
- continue;
336
- const uncertain = transitionOperation(operation, 'uncertain', { error: 'native_receipt_lost' });
337
- putOperation(database, row.scope_key, uncertain);
338
- if (operation.sessionId && operation.kind !== 'session.steer') {
339
- const session = readSession(database, operation.sessionId);
340
- if (session) {
341
- upsertRecord(database, 'sessions', operation.sessionId, { ...session, turnBlock: 'uncertain', turnStatus: 'starting' });
342
- }
343
- }
344
- }
345
- }
346
- function allocateEvent(database, body) {
347
- const runtimeId = String(body.runtimeId);
348
- const generation = Number(body.generation);
349
- const runtimeEpoch = Number(body.runtimeEpoch);
350
- ensureEpoch(database, runtimeId, generation, runtimeEpoch);
351
- const allocated = database
352
- .prepare(`UPDATE source_epochs
353
- SET next_sequence = next_sequence + 1,
354
- local_durable_through = next_sequence + 1
355
- WHERE runtime_id = ? AND generation = ? AND runtime_epoch = ?
356
- RETURNING next_sequence`)
357
- .get(runtimeId, generation, runtimeEpoch);
358
- const payload = body.payload ?? {};
359
- const event = pendingSourceEventSchema.parse({
360
- source: { runtimeId, generation, runtimeEpoch, sourceSequence: allocated.next_sequence },
361
- payloadHash: hashOperationPayload(payload),
362
- type: String(body.type),
363
- payload,
364
- occurredAt: String(body.occurredAt),
365
- });
366
- insertEventRow(database, event, {
367
- workspaceId: optionalString(body.workspaceId),
368
- sessionId: optionalString(body.sessionId),
369
- operationId: optionalString(body.operationId),
370
- threadId: optionalString(body.threadId),
371
- turnId: optionalString(body.turnId),
372
- itemId: optionalString(body.itemId),
373
- });
374
- return { committedEvents: [event] };
375
- }
376
- function commitIdentity(database, value) {
377
- const event = pendingSourceEventSchema.parse(value);
378
- const existing = database
379
- .prepare(`SELECT payload_hash, payload_json, type, occurred_at FROM source_events
380
- WHERE runtime_id = ? AND generation = ? AND runtime_epoch = ? AND source_sequence = ?`)
381
- .get(event.source.runtimeId, event.source.generation, event.source.runtimeEpoch, event.source.sourceSequence);
382
- if (existing) {
383
- if (existing.payload_hash !== event.payloadHash)
384
- throw new Error('source_payload_mismatch');
385
- return { committedEvents: [] };
386
- }
387
- ensureEpoch(database, event.source.runtimeId, event.source.generation, event.source.runtimeEpoch);
388
- insertEventRow(database, event, {});
389
- database
390
- .prepare(`UPDATE source_epochs
391
- SET next_sequence = CASE WHEN next_sequence < ? THEN ? ELSE next_sequence END,
392
- local_durable_through = CASE WHEN local_durable_through < ? THEN ? ELSE local_durable_through END
393
- WHERE runtime_id = ? AND generation = ? AND runtime_epoch = ?`)
394
- .run(event.source.sourceSequence, event.source.sourceSequence, event.source.sourceSequence, event.source.sourceSequence, event.source.runtimeId, event.source.generation, event.source.runtimeEpoch);
395
- return { committedEvents: [event] };
396
- }
397
- function ingest(database, body) {
398
- const native = nativeIds(body.params);
399
- const threadId = optionalString(body.threadId) || native.threadId;
400
- const turnId = optionalString(body.turnId) || native.turnId;
401
- const itemId = optionalString(body.itemId) || native.itemId;
402
- const ingress = nextIngress(database);
403
- const kind = String(body.kind);
404
- const method = optionalString(body.method);
405
- const hintedTurn = turnId || nativeIds(body.params).turnId;
406
- database
407
- .prepare(`INSERT INTO native_inbox (
408
- ingress_sequence, runtime_epoch, kind, method, thread_id, turn_id, item_id, payload_json, converted, received_at
409
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`)
410
- .run(ingress, Number(body.runtimeEpoch) || 0, kind, method ?? null, threadId ?? null, hintedTurn ?? null, itemId ?? null, JSON.stringify(body.params ?? {}), String(body.occurredAt));
411
- return { committedEvents: releaseInbox(database, { ingress }, { runtimeId: String(body.runtimeId), generation: Number(body.generation) || 0 }) };
412
- }
413
- function bindTurn(database, body) {
414
- const operation = operationRecordSchema.parse(body.operation);
415
- putOperation(database, String(body.scopeKey), operation);
416
- const sessionId = String(body.sessionId);
417
- const session = readSession(database, sessionId);
418
- if (session) {
419
- upsertRecord(database, 'sessions', sessionId, {
420
- ...session,
421
- turnStatus: 'running',
422
- turnBlock: undefined,
423
- activeTurnId: String(body.turnId),
424
- threadId: String(body.threadId),
425
- recoveryContextPending: undefined,
426
- });
427
- }
428
- const threadId = String(body.threadId);
429
- const turnId = String(body.turnId);
430
- const released = releaseInbox(database, { rootThreadId: threadId, turnId });
431
- applyRecordedTurn(database, threadId, turnId);
432
- return { committedEvents: released };
433
- }
434
- function releaseInbox(database, selection, identity) {
435
- const events = [];
436
- const rows = [];
437
- const queued = new Set();
438
- const enqueue = (pending) => {
439
- for (const row of pending) {
440
- const ingress = Number(row.ingress_sequence);
441
- if (!queued.has(ingress)) {
442
- queued.add(ingress);
443
- rows.push(row);
444
- }
445
- }
446
- };
447
- const enqueueChildren = (parentId) => enqueue(database.prepare(`
448
- SELECT * FROM native_inbox WHERE converted = 0 AND method = 'thread/started'
449
- AND COALESCE(json_extract(payload_json, '$.thread.parentThreadId'),
450
- json_extract(payload_json, '$.thread.source.subAgent.thread_spawn.parent_thread_id')) = ?
451
- ORDER BY ingress_sequence
452
- `).all(parentId));
453
- const enqueueThread = (threadId) => {
454
- enqueue(database.prepare('SELECT * FROM native_inbox WHERE converted = 0 AND thread_id = ? ORDER BY ingress_sequence').all(threadId));
455
- enqueueChildren(threadId);
456
- };
457
- if ('ingress' in selection) {
458
- const current = database.prepare('SELECT * FROM native_inbox WHERE converted = 0 AND ingress_sequence = ?').get(selection.ingress);
459
- if (current) {
460
- const threadId = optionalString(current.thread_id), turnId = optionalString(current.turn_id);
461
- if (nativeEventOwner(database, threadId, turnId)) {
462
- // A receipt can bind between notifications. Drain that thread's earlier events first.
463
- enqueue(database.prepare('SELECT * FROM native_inbox WHERE converted = 0 AND thread_id = ? AND (turn_id = ? OR turn_id IS NULL) AND ingress_sequence <= ? ORDER BY ingress_sequence')
464
- .all(threadId, turnId ?? null, selection.ingress));
465
- }
466
- else
467
- enqueue([current]);
468
- }
469
- }
470
- else {
471
- enqueue(database.prepare('SELECT * FROM native_inbox WHERE converted = 0 AND thread_id = ? AND (turn_id = ? OR turn_id IS NULL) ORDER BY ingress_sequence')
472
- .all(selection.rootThreadId, selection.turnId));
473
- enqueueChildren(selection.rootThreadId);
474
- for (const threadId of nativeSubagentThreads(database, selection.rootThreadId))
475
- enqueueThread(threadId);
476
- }
477
- for (const stored of rows) {
478
- const threadId = optionalString(stored.thread_id);
479
- const turnId = optionalString(stored.turn_id);
480
- const method = optionalString(stored.method);
481
- const params = JSON.parse(String(stored.payload_json || '{}'));
482
- if (stored.kind === 'process_exit')
483
- for (const child of clearPendingNativeFollowups(database))
484
- enqueueThread(child);
485
- for (const child of learnNativeSubagents(database, method ?? '', params, threadId, turnId))
486
- enqueueThread(child);
487
- const owner = nativeEventOwner(database, threadId, turnId);
488
- if (stored.kind !== 'process_exit' && !owner) {
489
- queued.delete(Number(stored.ingress_sequence));
490
- continue;
491
- }
492
- const event = convertInboxRow(database, Number(stored.ingress_sequence), {
493
- runtimeId: identity?.runtimeId ?? (metaString(database, 'runtime_id') || 'runtime'),
494
- generation: identity?.generation ?? metaNumber(database, 'generation'),
495
- runtimeEpoch: Number(stored.runtime_epoch),
496
- kind: String(stored.kind), method, threadId, turnId,
497
- itemId: optionalString(stored.item_id), occurredAt: String(stored.received_at),
498
- session: owner?.session, operation: owner?.operation, subAgent: owner?.subAgent, params,
499
- });
500
- if (event)
501
- events.push(event);
502
- if (!owner?.subAgent && threadId && terminalTurn(method ?? '', asRecord(params)))
503
- for (const child of nativeSubagentThreads(database, threadId))
504
- enqueueThread(child);
505
- }
506
- return events;
507
- }
508
- function convertInboxRow(database, ingress, input) {
509
- const row = database.prepare('SELECT payload_json, converted FROM native_inbox WHERE ingress_sequence = ?').get(ingress);
510
- if (!row || row.converted)
511
- return undefined;
512
- const params = input.params ?? JSON.parse(row.payload_json || '{}');
513
- const payload = linkedPayload(params, input.session, input.subAgent);
514
- const nativeApprovalId = optionalString(payload.approvalId) || optionalString(payload.itemId) || input.itemId;
515
- const approvalRequest = input.method?.includes('requestApproval') || input.method === 'item/tool/requestUserInput';
516
- const childApproval = input.subAgent && nativeApprovalId && approvalRequest;
517
- if (input.subAgent && nativeApprovalId && (childApproval || input.method === 'approval.resolved' || input.method === 'serverRequest/resolved')) {
518
- payload.approvalId = `subagent_${createHash('sha256').update(JSON.stringify([input.subAgent.threadId, nativeApprovalId])).digest('hex')}`;
519
- }
520
- const type = input.kind === 'process_exit' ? 'process.exited' : input.method || input.kind;
521
- const allocated = allocateEvent(database, {
522
- runtimeId: input.runtimeId || metaString(database, 'runtime_id'),
523
- generation: input.generation,
524
- runtimeEpoch: input.runtimeEpoch,
525
- type,
526
- payload,
527
- occurredAt: input.occurredAt,
528
- workspaceId: optionalString(input.session?.workspaceId),
529
- sessionId: optionalString(input.session?.sessionId),
530
- operationId: input.operation?.operation.operationId,
531
- threadId: input.threadId,
532
- turnId: input.turnId,
533
- itemId: input.itemId,
534
- });
535
- const event = allocated.committedEvents[0];
536
- if (!event)
537
- return undefined;
538
- database
539
- .prepare(`UPDATE native_inbox
540
- SET converted = 1, source_sequence = ?, payload_json = '{}'
541
- WHERE ingress_sequence = ?`)
542
- .run(event.source.sourceSequence, ingress);
543
- if (childApproval && input.operation?.operation.sessionId) {
544
- upsertRecord(database, 'approvals', `${input.operation.operation.sessionId}:${String(payload.approvalId)}`, {
545
- approvalId: payload.approvalId,
546
- sessionId: input.operation.operation.sessionId,
547
- runtimeEpoch: input.runtimeEpoch,
548
- nativeRequestId: optionalString(payload.requestId) ?? `approval:${nativeApprovalId}`,
549
- nativeApprovalId,
550
- nativeThreadId: input.subAgent.threadId,
551
- nativeTurnId: input.turnId,
552
- rootThreadId: input.subAgent.rootThreadId,
553
- rootTurnId: input.operation.operation.nativeTurnId,
554
- method: input.method,
555
- request: payload,
556
- status: 'pending',
557
- });
558
- }
559
- settleNativeApprovals(database, input, payload);
560
- if (!input.subAgent)
561
- applyNativeToState(database, { ...input, operation: resolveOperation(database, input, payload) }, payload);
562
- return event;
563
- }
564
- /** An ended native request must never reappear as a pending approval after reconnect. */
565
- function settleNativeApprovals(database, input, payload) {
566
- const resolved = input.method === 'approval.resolved' || input.method === 'serverRequest/resolved';
567
- const terminal = Boolean(terminalTurn(input.method ?? '', payload));
568
- if (!resolved && !terminal && input.kind !== 'process_exit')
569
- return;
570
- const rows = database.prepare("SELECT record_key, body_json FROM records WHERE bucket = 'approvals'").all();
571
- for (const row of rows) {
572
- const approval = asRecord(JSON.parse(row.body_json));
573
- if (approval.status !== 'pending')
574
- continue;
575
- const matchesThread = approval.nativeThreadId === input.threadId;
576
- const matchesRequest = (payload.approvalId && approval.approvalId === payload.approvalId)
577
- || (payload.requestId !== undefined && String(approval.nativeRequestId) === String(payload.requestId));
578
- const endedParent = terminal && approval.rootThreadId === input.threadId && approval.rootTurnId === input.turnId;
579
- if (input.kind !== 'process_exit' && !endedParent && (!matchesThread || (resolved ? !matchesRequest
580
- : input.turnId && approval.nativeTurnId && approval.nativeTurnId !== input.turnId)))
581
- continue;
582
- upsertRecord(database, 'approvals', row.record_key, { ...approval,
583
- status: input.method === 'approval.resolved' ? payload.accepted === false ? 'rejected' : 'approved' : 'expired' });
584
- }
585
- }
586
- function applyRecordedTurn(database, threadId, turnId) {
587
- const rows = database
588
- .prepare(`SELECT type, payload_json, thread_id, session_id FROM source_events
589
- WHERE (thread_id = ? OR thread_id IS NULL) AND (turn_id = ? OR payload_json LIKE ?)
590
- ORDER BY source_sequence`)
591
- .all(threadId, turnId, `%"${turnId}"%`);
592
- for (const row of rows) {
593
- if (!row.type.includes('requestApproval') && !row.type.includes('requestUserInput') && !row.type.includes('completed') && !row.type.includes('interrupted') && row.type !== 'turn.failed' && row.type !== 'approval.resolved' && row.type !== 'serverRequest/resolved') {
594
- continue;
595
- }
596
- const payload = asRecord(JSON.parse(row.payload_json));
597
- if (payload.subAgent)
598
- continue;
599
- const session = sessionByThread(database, threadId) ?? (row.session_id ? readSession(database, row.session_id) : undefined);
600
- applyNativeToState(database, {
601
- method: row.type,
602
- kind: 'notification',
603
- threadId: optionalString(row.thread_id) || threadId,
604
- turnId,
605
- session,
606
- operation: operationByTurn(database, turnId, threadId),
607
- }, payload);
608
- }
609
- }
610
- function resolveOperation(database, input, payload) {
611
- const turnId = input.turnId || nativeIds(payload).turnId;
612
- if (turnId)
613
- return input.operation ?? operationByTurn(database, turnId, input.threadId ?? nativeIds(payload).threadId);
614
- return input.operation;
615
- }
616
- function applyNativeToState(database, input, payload) {
617
- const method = input.method ?? '';
618
- settleNativeApprovals(database, input, payload);
619
- const session = input.session;
620
- if (session?.sessionId) {
621
- const next = { ...session };
622
- const approval = method.includes('requestApproval') || method.includes('requestUserInput');
623
- if (approval)
624
- next.turnStatus = moveTurn(String(session.turnStatus ?? 'running'), 'waiting_for_user');
625
- else if (method === 'approval.resolved' || method === 'serverRequest/resolved') {
626
- const waiting = database.prepare("SELECT 1 FROM records WHERE bucket = 'approvals' AND json_extract(body_json, '$.sessionId') = ? AND json_extract(body_json, '$.status') = 'pending' AND json_extract(body_json, '$.nativeThreadId') = ? LIMIT 1")
627
- .get(session.sessionId, session.threadId);
628
- if (!waiting)
629
- next.turnStatus = moveTurn(String(session.turnStatus ?? 'waiting_for_user'), 'running');
630
- }
631
- else if (method.includes('delta') || method === 'turn/started' || method === 'turn.started') {
632
- const current = String(session.turnStatus ?? 'starting');
633
- if (current !== 'waiting_for_user')
634
- next.turnStatus = moveTurn(current, 'running');
635
- }
636
- const terminal = terminalTurn(method, payload);
637
- if (terminal) {
638
- let status = String(next.turnStatus ?? session.turnStatus ?? 'running');
639
- if (status === 'waiting_for_user')
640
- status = moveTurn(status, 'running');
641
- next.turnStatus = moveTurn(status, terminal);
642
- next.turnBlock = undefined;
643
- }
644
- upsertRecord(database, 'sessions', String(session.sessionId), next);
645
- }
646
- if (!input.operation)
647
- return;
648
- let operation = input.operation.operation;
649
- const approvalId = optionalString(payload.approvalId) || optionalString(payload.itemId) || input.itemId;
650
- if ((method.includes('requestApproval') || method.includes('requestUserInput')) && approvalId && operation.sessionId) {
651
- upsertRecord(database, 'approvals', `${operation.sessionId}:${approvalId}`, {
652
- approvalId,
653
- sessionId: operation.sessionId,
654
- runtimeEpoch: operation.runtimeEpoch ?? 0,
655
- nativeRequestId: optionalString(payload.requestId) ?? `approval:${approvalId}`,
656
- nativeThreadId: input.threadId,
657
- nativeTurnId: input.turnId,
658
- nativeApprovalId: approvalId,
659
- method,
660
- request: payload,
661
- status: 'pending',
662
- });
663
- operation = operationRecordSchema.parse({
664
- ...operation,
665
- nativeApprovalId: approvalId,
666
- updatedAt: new Date().toISOString(),
667
- });
668
- putOperation(database, input.operation.scopeKey, operation);
669
- }
670
- const terminal = terminalTurn(method, payload);
671
- if (terminal && operation.status === 'accepted') {
672
- putOperation(database, input.operation.scopeKey, transitionOperation(operation, terminal, {
673
- nativeTurnId: input.turnId ?? operation.nativeTurnId,
674
- nativeThreadId: input.threadId ?? operation.nativeThreadId,
675
- }));
676
- }
677
- }
678
- function acknowledge(database, body) {
679
- const leaseFence = Number(body.leaseFence);
680
- const accepted = metaNumber(database, 'accepted_lease_fence');
681
- if (!Number.isInteger(leaseFence) || leaseFence !== accepted)
682
- throw new Error('lease_fence_mismatch');
683
- const epochs = Array.isArray(body.epochs) ? body.epochs : [];
684
- for (const value of epochs) {
685
- const epoch = asRecord(value);
686
- const row = epochRow(database, String(epoch.runtimeId), Number(epoch.generation), Number(epoch.runtimeEpoch));
687
- if (!row)
688
- throw new Error('unknown_source_epoch');
689
- const next = Number(epoch.confirmedThrough);
690
- if (!Number.isInteger(next) || next < row.cloud_ack_through)
691
- throw new Error('ack_not_monotonic');
692
- if (next > row.local_durable_through)
693
- throw new Error('ack_ahead_of_local');
694
- database
695
- .prepare(`UPDATE source_epochs SET cloud_ack_through = ?
696
- WHERE runtime_id = ? AND generation = ? AND runtime_epoch = ?`)
697
- .run(next, epoch.runtimeId, epoch.generation, epoch.runtimeEpoch);
698
- }
699
- }
700
- function createPin(database, body) {
701
- const now = Date.now();
702
- database.prepare('DELETE FROM replay_pins WHERE expires_at <= ?').run(new Date(now).toISOString());
703
- const count = database.prepare('SELECT count(*) AS n FROM replay_pins').get().n;
704
- if (count >= MAX_PINS)
705
- throw new Error('pin_quota');
706
- const ttl = Math.min(Number(body.ttlMs) || 60_000, MAX_PIN_MS);
707
- const identity = asRecord(body.identity);
708
- ensureEpoch(database, String(identity.runtimeId), Number(identity.generation), Number(identity.runtimeEpoch));
709
- const epochs = database.prepare('SELECT * FROM source_epochs').all().map((row) => ({
710
- runtimeId: row.runtime_id,
711
- generation: row.generation,
712
- runtimeEpoch: row.runtime_epoch,
713
- retainedFrom: row.retained_from,
714
- highWater: row.local_durable_through,
715
- }));
716
- const current = epochs.find((epoch) => epoch.runtimeId === identity.runtimeId &&
717
- epoch.generation === Number(identity.generation) &&
718
- epoch.runtimeEpoch === Number(identity.runtimeEpoch));
719
- const pinId = `pin_${randomUUID()}`;
720
- const createdAt = new Date(now).toISOString();
721
- const expiresAt = new Date(now + ttl).toISOString();
722
- database
723
- .prepare('INSERT INTO replay_pins (pin_id, epochs_json, created_at, expires_at) VALUES (?, ?, ?, ?)')
724
- .run(pinId, JSON.stringify(epochs), createdAt, expiresAt);
725
- const highWater = current?.highWater ?? 0;
726
- return {
727
- pin: {
728
- pinId,
729
- epochs,
730
- followBoundary: {
731
- runtimeId: String(identity.runtimeId),
732
- generation: Number(identity.generation),
733
- runtimeEpoch: Number(identity.runtimeEpoch),
734
- sourceSequence: Math.max(highWater, 1),
735
- },
736
- },
737
- };
738
- }
739
- function reclaim(database, minRetainMs) {
740
- const pins = activePins(database);
741
- const epochs = database.prepare('SELECT * FROM source_epochs').all();
742
- const now = Date.now();
743
- for (const epoch of epochs) {
744
- let retained = epoch.retained_from;
745
- while (true) {
746
- const row = database
747
- .prepare(`SELECT source_sequence, occurred_at FROM source_events
748
- WHERE runtime_id = ? AND generation = ? AND runtime_epoch = ? AND source_sequence >= ?
749
- ORDER BY source_sequence LIMIT 1`)
750
- .get(epoch.runtime_id, epoch.generation, epoch.runtime_epoch, retained);
751
- if (!row || row.source_sequence !== retained)
752
- break;
753
- if (row.source_sequence > epoch.cloud_ack_through)
754
- break;
755
- if (pins.some((pin) => row.source_sequence >= pin.retainedFrom && row.source_sequence <= pin.highWater && pin.runtimeId === epoch.runtime_id && pin.generation === epoch.generation && pin.runtimeEpoch === epoch.runtime_epoch)) {
756
- break;
757
- }
758
- const age = now - Date.parse(row.occurred_at);
759
- if (Number.isFinite(age) && age < minRetainMs)
760
- break;
761
- database
762
- .prepare(`DELETE FROM source_events
763
- WHERE runtime_id = ? AND generation = ? AND runtime_epoch = ? AND source_sequence = ?`)
764
- .run(epoch.runtime_id, epoch.generation, epoch.runtime_epoch, row.source_sequence);
765
- retained = row.source_sequence + 1;
766
- }
767
- if (retained !== epoch.retained_from) {
768
- database
769
- .prepare(`UPDATE source_epochs SET retained_from = ?
770
- WHERE runtime_id = ? AND generation = ? AND runtime_epoch = ?`)
771
- .run(retained, epoch.runtime_id, epoch.generation, epoch.runtime_epoch);
772
- }
773
- }
774
- }
775
- function dump(database) {
776
- const snapshot = {
777
- workspaces: bucketObject(database, 'workspaces'),
778
- workspaceFileRevisions: workspaceFiles(database),
779
- operations: operationsObject(database),
780
- receipts: bucketObject(database, 'receipts'),
781
- events: eventsObject(database),
782
- sessions: bucketObject(database, 'sessions'),
783
- terminals: bucketObject(database, 'terminals'),
784
- fileRevisions: bucketObject(database, 'file_revisions'),
785
- approvals: bucketObject(database, 'approvals'),
786
- managedWrites: bucketObject(database, 'managed_writes'),
787
- workspaceDependencies: bucketObject(database, 'workspace_dependencies'),
788
- executionGroup: executionGroup(database),
789
- runtimeEpoch: metaNumber(database, 'runtime_epoch'),
790
- lastSourceSequence: currentCounter(database),
791
- acceptedLeaseFence: metaNumber(database, 'accepted_lease_fence'),
792
- destroyed: metaString(database, 'destroyed') === '1',
793
- };
794
- return {
795
- snapshot,
796
- epochs: database.prepare('SELECT * FROM source_epochs').all(),
797
- sourceReadToken: metaString(database, 'source_read_token'),
798
- pendingInbox: database.prepare('SELECT count(*) AS n FROM native_inbox WHERE converted = 0').get().n,
799
- };
800
- }
801
- function eventsObject(database) {
802
- const rows = database.prepare('SELECT * FROM source_events ORDER BY runtime_epoch, source_sequence').all();
803
- const events = {};
804
- for (const row of rows) {
805
- const event = pendingSourceEventSchema.parse({
806
- source: {
807
- runtimeId: row.runtime_id,
808
- generation: row.generation,
809
- runtimeEpoch: row.runtime_epoch,
810
- sourceSequence: row.source_sequence,
811
- },
812
- payloadHash: row.payload_hash,
813
- type: row.type,
814
- payload: JSON.parse(String(row.payload_json)),
815
- occurredAt: row.occurred_at,
816
- });
817
- events[`${event.source.runtimeId}:${event.source.generation}:${event.source.runtimeEpoch}:${event.source.sourceSequence}`] = event;
818
- }
819
- return events;
820
- }
821
- function operationsObject(database) {
822
- const rows = database.prepare('SELECT scope_key, operation_json FROM operation_intents').all();
823
- const operations = {};
824
- for (const row of rows) {
825
- const parsed = operationRecordSchema.safeParse(JSON.parse(row.operation_json));
826
- if (parsed.success)
827
- operations[row.scope_key] = parsed.data;
828
- }
829
- return operations;
830
- }
831
- function putOperation(database, scopeKey, value) {
832
- const operation = operationRecordSchema.parse(value);
833
- database
834
- .prepare(`INSERT INTO operation_intents (
835
- scope_key, operation_json, gateway_operation_id, sandbox_operation_id, payload_hash,
836
- native_request_id, thread_id, turn_id, reconcile_status
837
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
838
- ON CONFLICT(scope_key) DO UPDATE SET
839
- operation_json = excluded.operation_json,
840
- gateway_operation_id = excluded.gateway_operation_id,
841
- sandbox_operation_id = excluded.sandbox_operation_id,
842
- payload_hash = excluded.payload_hash,
843
- native_request_id = excluded.native_request_id,
844
- thread_id = excluded.thread_id,
845
- turn_id = excluded.turn_id,
846
- reconcile_status = excluded.reconcile_status`)
847
- .run(scopeKey, JSON.stringify(operation), null, operation.sandboxOperationId ?? operation.operationId, operation.payloadHash, operation.nativeRequestId ?? null, operation.nativeThreadId ?? null, operation.nativeTurnId ?? null, operation.status);
848
- }
849
- function insertEventRow(database, event, links) {
850
- database
851
- .prepare(`INSERT INTO source_events (
852
- runtime_id, generation, runtime_epoch, source_sequence, payload_hash, workspace_id, session_id,
853
- operation_id, thread_id, turn_id, item_id, type, payload_json, occurred_at
854
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
855
- .run(event.source.runtimeId, event.source.generation, event.source.runtimeEpoch, event.source.sourceSequence, event.payloadHash, links.workspaceId ?? null, links.sessionId ?? null, links.operationId ?? null, links.threadId ?? null, links.turnId ?? null, links.itemId ?? null, event.type, JSON.stringify(event.payload), event.occurredAt);
856
- }
857
- function ensureEpoch(database, runtimeId, generation, runtimeEpoch) {
858
- database
859
- .prepare(`INSERT INTO source_epochs (
860
- runtime_id, generation, runtime_epoch, next_sequence, local_durable_through, cloud_ack_through, retained_from
861
- ) VALUES (?, ?, ?, 0, 0, 0, 1)
862
- ON CONFLICT DO NOTHING`)
863
- .run(runtimeId, generation, runtimeEpoch);
864
- }
865
- function writeExecutionEpoch(database, body) {
866
- const runtimeEpoch = Number(body.runtimeEpoch);
867
- setMeta(database, 'runtime_epoch', String(runtimeEpoch));
868
- setMeta(database, 'execution_group', JSON.stringify({
869
- pid: body.pid,
870
- startedAt: body.startedAt,
871
- previousPid: body.previousPid,
872
- stopConfirmed: body.stopConfirmed === true,
873
- replaced: true,
874
- }));
875
- const approvals = database.prepare(`SELECT record_key, body_json FROM records WHERE bucket = 'approvals'`).all();
876
- for (const row of approvals) {
877
- const approval = asRecord(JSON.parse(row.body_json));
878
- if (approval.status === 'pending') {
879
- upsertRecord(database, 'approvals', row.record_key, { ...approval, status: 'expired' });
880
- }
881
- }
882
- const runtimeId = metaString(database, 'runtime_id');
883
- const generation = metaNumber(database, 'generation');
884
- if (runtimeId)
885
- ensureEpoch(database, runtimeId, generation, runtimeEpoch);
886
- }
887
- function bindRuntime(database, body) {
888
- setMeta(database, 'runtime_id', String(body.runtimeId));
889
- setMeta(database, 'generation', String(Number(body.generation) || 0));
890
- const current = metaNumber(database, 'runtime_epoch');
891
- const next = Math.max(current, Number(body.runtimeEpoch) || 0);
892
- setMeta(database, 'runtime_epoch', String(next));
893
- ensureEpoch(database, String(body.runtimeId), Number(body.generation) || 0, next);
894
- }
895
- function upsertRecord(database, bucket, key, body) {
896
- database
897
- .prepare(`INSERT INTO records (bucket, record_key, body_json) VALUES (?, ?, ?)
898
- ON CONFLICT(bucket, record_key) DO UPDATE SET body_json = excluded.body_json`)
899
- .run(bucket, key, JSON.stringify(body));
900
- }
901
- function replaceBucket(database, bucket, entries) {
902
- database.prepare('DELETE FROM records WHERE bucket = ?').run(bucket);
903
- for (const entry of entries) {
904
- const record = asRecord(entry);
905
- upsertRecord(database, bucket, String(record.key), record.body);
906
- }
907
- }
908
- function importBucket(database, bucket, records, accept) {
909
- for (const [key, value] of Object.entries(records)) {
910
- if (accept(value))
911
- upsertRecord(database, bucket, key, value);
912
- }
913
- }
914
- function bucketObject(database, bucket) {
915
- const rows = database.prepare('SELECT record_key, body_json FROM records WHERE bucket = ? ORDER BY rowid').all(bucket);
916
- return Object.fromEntries(rows.map((row) => [row.record_key, JSON.parse(row.body_json)]));
917
- }
918
- function workspaceFiles(database) {
919
- const rows = database
920
- .prepare(`SELECT bucket, record_key, body_json FROM records WHERE bucket LIKE 'workspace_files:%'`)
921
- .all();
922
- const files = {};
923
- for (const row of rows) {
924
- const workspaceId = row.bucket.slice('workspace_files:'.length);
925
- (files[workspaceId] ??= {})[row.record_key] = JSON.parse(row.body_json);
926
- }
927
- return files;
928
- }
929
- function readSession(database, sessionId) {
930
- const row = database
931
- .prepare(`SELECT body_json FROM records WHERE bucket = 'sessions' AND record_key = ?`)
932
- .get(sessionId);
933
- return row ? asRecord(JSON.parse(row.body_json)) : undefined;
934
- }
935
- function sessionByThread(database, threadId) {
936
- const rows = database.prepare(`SELECT body_json FROM records WHERE bucket = 'sessions'`).all();
937
- const matches = rows.map((row) => asRecord(JSON.parse(row.body_json))).filter((session) => session.threadId === threadId);
938
- if (matches.length !== 1)
939
- return undefined;
940
- return matches[0];
941
- }
942
- function operationByTurn(database, turnId, threadId) {
943
- const row = database
944
- .prepare(`SELECT scope_key, operation_json FROM operation_intents WHERE turn_id = ?${threadId ? ' AND thread_id = ?' : ''} AND json_extract(operation_json, '$.kind') IN ('session.turn', 'session.review', 'session.compact')`)
945
- .get(...(threadId ? [turnId, threadId] : [turnId]));
946
- if (!row)
947
- return undefined;
948
- const parsed = operationRecordSchema.safeParse(JSON.parse(row.operation_json));
949
- if (!parsed.success)
950
- return undefined;
951
- return { scopeKey: row.scope_key, operation: parsed.data };
952
- }
953
- function linkedPayload(params, session, subAgent) {
954
- const payload = { ...asRecord(params) };
955
- delete payload.workspaceId;
956
- delete payload.sessionId;
957
- delete payload.subAgent;
958
- if (typeof session?.workspaceId === 'string')
959
- payload.workspaceId = session.workspaceId;
960
- if (typeof session?.sessionId === 'string')
961
- payload.sessionId = session.sessionId;
962
- if (subAgent)
963
- payload.subAgent = subAgent;
964
- return payload;
965
- }
966
- function nativeIds(params) {
967
- const record = asRecord(params);
968
- const turn = asRecord(record.turn);
969
- const thread = asRecord(record.thread);
970
- return {
971
- threadId: optionalString(record.threadId) || optionalString(thread.id),
972
- turnId: optionalString(record.turnId) || optionalString(turn.id),
973
- itemId: optionalString(record.itemId) || optionalString(record.approvalId) || optionalString(asRecord(record.item).id),
974
- };
975
- }
976
- function terminalTurn(method, payload) {
977
- if (method !== 'turn/completed' && method !== 'turn.completed' && method !== 'turn.interrupted' && method !== 'turn.failed') {
978
- return undefined;
979
- }
980
- if (method === 'turn.interrupted')
981
- return 'interrupted';
982
- if (method === 'turn.failed')
983
- return 'failed';
984
- const status = String(asRecord(payload.turn).status ?? payload.status ?? 'completed');
985
- if (status === 'interrupted')
986
- return 'interrupted';
987
- if (status === 'failed')
988
- return 'failed';
989
- return 'completed';
990
- }
991
- function moveTurn(current, to) {
992
- if (current === to)
993
- return to;
994
- if (current === 'starting' || current === 'running' || current === 'waiting_for_user' || current === 'completed' || current === 'failed' || current === 'interrupted') {
995
- return canTransitionTurn(current, to) ? to : current;
996
- }
997
- return to;
998
- }
999
- function nextIngress(database) {
1000
- const next = metaNumber(database, 'ingress_counter') + 1;
1001
- setMeta(database, 'ingress_counter', String(next));
1002
- return next;
1003
- }
1004
- function epochRow(database, runtimeId, generation, runtimeEpoch) {
1005
- return database
1006
- .prepare('SELECT * FROM source_epochs WHERE runtime_id = ? AND generation = ? AND runtime_epoch = ?')
1007
- .get(runtimeId, generation, runtimeEpoch);
1008
- }
1009
- function activePins(database) {
1010
- const now = new Date().toISOString();
1011
- const rows = database.prepare('SELECT epochs_json, expires_at FROM replay_pins').all();
1012
- const pins = [];
1013
- for (const row of rows) {
1014
- if (row.expires_at <= now)
1015
- continue;
1016
- const epochs = JSON.parse(row.epochs_json);
1017
- for (const epoch of epochs)
1018
- pins.push(epoch);
1019
- }
1020
- return pins;
1021
- }
1022
- function currentCounter(database) {
1023
- const row = epochRow(database, metaString(database, 'runtime_id'), metaNumber(database, 'generation'), metaNumber(database, 'runtime_epoch'));
1024
- return row?.next_sequence ?? 0;
1025
- }
1026
- function executionGroup(database) {
1027
- const raw = metaString(database, 'execution_group');
1028
- if (!raw)
1029
- return { stopConfirmed: false, replaced: false };
1030
- try {
1031
- return asRecord(JSON.parse(raw));
1032
- }
1033
- catch {
1034
- return { stopConfirmed: false, replaced: false };
1035
- }
1036
- }
1037
- function setMeta(database, key, value) {
1038
- database
1039
- .prepare(`INSERT INTO library_meta (key, value) VALUES (?, ?)
1040
- ON CONFLICT(key) DO UPDATE SET value = excluded.value`)
1041
- .run(key, value);
1042
- }
1043
- function metaString(database, key) {
1044
- const row = database.prepare('SELECT value FROM library_meta WHERE key = ?').get(key);
1045
- return row?.value ?? '';
1046
- }
1047
- function metaNumber(database, key) {
1048
- const parsed = Number(metaString(database, key));
1049
- return Number.isFinite(parsed) ? parsed : 0;
1050
- }
1051
- function requireDb() {
1052
- if (!db)
1053
- throw new Error('sqlite_not_open');
1054
- return db;
1055
- }
1056
- function sqliteAtLeast(version, minimum) {
1057
- const parts = version.split('.').map((item) => Number(item));
1058
- for (let index = 0; index < minimum.length; index += 1) {
1059
- const left = parts[index] ?? 0;
1060
- const right = minimum[index] ?? 0;
1061
- if (left > right)
1062
- return true;
1063
- if (left < right)
1064
- return false;
1065
- }
1066
- return true;
1067
- }
1068
- function asRecord(value) {
1069
- return value && typeof value === 'object' && !Array.isArray(value) ? { ...value } : {};
1070
- }
1071
- function optionalString(value) {
1072
- return typeof value === 'string' && value.length > 0 ? value : undefined;
1073
- }
1074
- function firstString(snapshot, key) {
1075
- return typeof snapshot[key] === 'string' ? String(snapshot[key]) : undefined;
1076
- }
1077
- function eventRuntime(events, key) {
1078
- for (const value of Object.values(events)) {
1079
- const source = asRecord(asRecord(value).source);
1080
- if (typeof source[key] === 'string')
1081
- return String(source[key]);
1082
- }
1083
- return undefined;
1084
- }
1085
- function eventRuntimeNumber(events, key) {
1086
- for (const value of Object.values(events)) {
1087
- const source = asRecord(asRecord(value).source);
1088
- if (Number.isInteger(source[key]))
1089
- return Number(source[key]);
1090
- }
1091
- return 0;
1092
- }
1093
- function isApproval(value) {
1094
- const record = asRecord(value);
1095
- return typeof record.approvalId === 'string' && typeof record.sessionId === 'string' && typeof record.nativeRequestId === 'string';
1096
- }