prompt_objects 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (292) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +142 -0
  3. data/README.md +211 -19
  4. data/exe/poop +5 -0
  5. data/exe/prompt_objects +1 -1108
  6. data/exe/prompt_objects_mcp +5 -3
  7. data/lib/prompt_objects/capability.rb +33 -0
  8. data/lib/prompt_objects/cli/diagnostics_command.rb +69 -0
  9. data/lib/prompt_objects/cli/env_command.rb +435 -0
  10. data/lib/prompt_objects/cli/events_command.rb +145 -0
  11. data/lib/prompt_objects/cli/explore_command.rb +155 -0
  12. data/lib/prompt_objects/cli/helpers.rb +91 -0
  13. data/lib/prompt_objects/cli/message_command.rb +216 -0
  14. data/lib/prompt_objects/cli/repl_command.rb +366 -0
  15. data/lib/prompt_objects/cli/serve_command.rb +162 -0
  16. data/lib/prompt_objects/cli.rb +64 -422
  17. data/lib/prompt_objects/connectors/base.rb +16 -29
  18. data/lib/prompt_objects/connectors/mcp.rb +82 -98
  19. data/lib/prompt_objects/domain/records.rb +45 -0
  20. data/lib/prompt_objects/environment/exporter.rb +5 -4
  21. data/lib/prompt_objects/environment/git.rb +21 -1
  22. data/lib/prompt_objects/environment/importer.rb +5 -4
  23. data/lib/prompt_objects/environment/manager.rb +44 -6
  24. data/lib/prompt_objects/environment/manifest.rb +26 -4
  25. data/lib/prompt_objects/environment.rb +1350 -18
  26. data/lib/prompt_objects/execution/blocking_executor.rb +88 -0
  27. data/lib/prompt_objects/execution/context.rb +137 -0
  28. data/lib/prompt_objects/execution/contracts.rb +59 -0
  29. data/lib/prompt_objects/execution/permit_pool.rb +49 -0
  30. data/lib/prompt_objects/execution/policy.rb +47 -0
  31. data/lib/prompt_objects/execution/resource_lock_manager.rb +63 -0
  32. data/lib/prompt_objects/execution/runner.rb +498 -0
  33. data/lib/prompt_objects/execution/scheduler.rb +325 -0
  34. data/lib/prompt_objects/llm/factory.rb +4 -4
  35. data/lib/prompt_objects/llm/gemini_adapter.rb +25 -8
  36. data/lib/prompt_objects/llm/openai_adapter.rb +9 -1
  37. data/lib/prompt_objects/llm/pricing.rb +9 -2
  38. data/lib/prompt_objects/llm/response.rb +9 -4
  39. data/lib/prompt_objects/loader.rb +28 -1
  40. data/lib/prompt_objects/mcp/server.rb +6 -3
  41. data/lib/prompt_objects/mcp/tools/get_conversation.rb +15 -11
  42. data/lib/prompt_objects/mcp/tools/get_pending_requests.rb +2 -2
  43. data/lib/prompt_objects/mcp/tools/respond_to_request.rb +3 -3
  44. data/lib/prompt_objects/mcp/tools/send_message.rb +42 -32
  45. data/lib/prompt_objects/operations/diagnostics.rb +115 -0
  46. data/lib/prompt_objects/operations/retention.rb +75 -0
  47. data/lib/prompt_objects/operations/run_trace_builder.rb +152 -0
  48. data/lib/prompt_objects/primitives/http_get.rb +2 -0
  49. data/lib/prompt_objects/primitives/list_files.rb +2 -0
  50. data/lib/prompt_objects/primitives/read_file.rb +2 -0
  51. data/lib/prompt_objects/primitives/think.rb +36 -0
  52. data/lib/prompt_objects/primitives/write_file.rb +9 -0
  53. data/lib/prompt_objects/prompt_object.rb +190 -432
  54. data/lib/prompt_objects/repositories/artifact_repository.rb +109 -0
  55. data/lib/prompt_objects/repositories/base_repository.rb +37 -0
  56. data/lib/prompt_objects/repositories/definition_repository.rb +36 -0
  57. data/lib/prompt_objects/repositories/env_data_repository.rb +103 -0
  58. data/lib/prompt_objects/repositories/event_repository.rb +105 -0
  59. data/lib/prompt_objects/repositories/message_repository.rb +73 -0
  60. data/lib/prompt_objects/repositories/run_repository.rb +254 -0
  61. data/lib/prompt_objects/repositories/thread_repository.rb +87 -0
  62. data/lib/prompt_objects/repositories/tool_execution_repository.rb +88 -0
  63. data/lib/prompt_objects/repositories/workspace_session_repository.rb +61 -0
  64. data/lib/prompt_objects/server/api/routes.rb +561 -52
  65. data/lib/prompt_objects/server/app.rb +134 -1
  66. data/lib/prompt_objects/server/file_watcher.rb +9 -0
  67. data/lib/prompt_objects/server/public/assets/index-BjKlODDD.js +90 -0
  68. data/lib/prompt_objects/server/public/assets/index-w3fpIb5c.css +1 -0
  69. data/lib/prompt_objects/server/public/index.html +2 -2
  70. data/lib/prompt_objects/server/websocket_handler.rb +202 -207
  71. data/lib/prompt_objects/server.rb +170 -1
  72. data/lib/prompt_objects/service.rb +129 -0
  73. data/lib/prompt_objects/session/store.rb +774 -15
  74. data/lib/prompt_objects/universal/add_capability.rb +1 -1
  75. data/lib/prompt_objects/universal/create_capability.rb +4 -2
  76. data/lib/prompt_objects/universal/delete_env_data.rb +78 -0
  77. data/lib/prompt_objects/universal/delete_primitive.rb +1 -1
  78. data/lib/prompt_objects/universal/dispatch_po.rb +54 -0
  79. data/lib/prompt_objects/universal/get_env_data.rb +58 -0
  80. data/lib/prompt_objects/universal/get_run_status.rb +30 -0
  81. data/lib/prompt_objects/universal/join_runs.rb +35 -0
  82. data/lib/prompt_objects/universal/list_env_data.rb +57 -0
  83. data/lib/prompt_objects/universal/modify_primitive.rb +1 -1
  84. data/lib/prompt_objects/universal/render_artifact.rb +91 -0
  85. data/lib/prompt_objects/universal/request_primitive.rb +1 -1
  86. data/lib/prompt_objects/universal/set_env_data.rb +101 -0
  87. data/lib/prompt_objects/version.rb +5 -0
  88. data/lib/prompt_objects.rb +36 -6
  89. data/objects/coordinator.md +8 -4
  90. data/templates/arc-agi-1/objects/data_manager.md +2 -2
  91. data/templates/arc-agi-1/objects/observer.md +6 -2
  92. data/templates/arc-agi-1/objects/solver.md +14 -4
  93. data/templates/arc-agi-1/objects/verifier.md +6 -2
  94. data/templates/arc-agi-3/manifest.yml +19 -0
  95. data/templates/arc-agi-3/objects/analyst.md +79 -0
  96. data/templates/arc-agi-3/objects/player.md +76 -0
  97. data/templates/arc-agi-3/objects/strategist.md +97 -0
  98. data/templates/arc-agi-3/primitives/action_log.rb +133 -0
  99. data/templates/arc-agi-3/primitives/arc3_api.rb +223 -0
  100. data/templates/arc-agi-3/primitives/arc3_scorecard.rb +131 -0
  101. data/templates/arc-agi-3/primitives/frame_diff.rb +143 -0
  102. data/templates/arc-agi-3/primitives/frame_info.rb +96 -0
  103. data/templates/arc-agi-3/primitives/render_frame.rb +140 -0
  104. data/templates/basic/objects/basic.md +11 -3
  105. data/templates/chiptune/extras/sonic_pi_boot.rb +220 -0
  106. data/templates/chiptune/manifest.yml +20 -0
  107. data/templates/chiptune/objects/conductor.md +184 -0
  108. data/templates/chiptune/objects/sonic_pi.md +65 -0
  109. data/templates/chiptune/objects/voice_bass.md +44 -0
  110. data/templates/chiptune/objects/voice_lead.md +44 -0
  111. data/templates/chiptune/objects/voice_pad.md +43 -0
  112. data/templates/chiptune/objects/voice_perc.md +45 -0
  113. data/templates/chiptune/primitives/play_note.rb +59 -0
  114. data/templates/chiptune/primitives/play_sequence.rb +55 -0
  115. data/templates/chiptune/primitives/query_sonic_pi.rb +141 -0
  116. data/templates/chiptune/primitives/set_synth.rb +57 -0
  117. data/templates/chiptune/primitives/set_tempo.rb +45 -0
  118. data/templates/chiptune/primitives/stop_voice.rb +46 -0
  119. data/templates/chiptune/primitives/support/osc_bridge.rb +33 -0
  120. data/templates/chiptune/vendor/osc-ruby-1.1.5/LICENSE +19 -0
  121. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/address_pattern.rb +58 -0
  122. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/broadcast_client.rb +24 -0
  123. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/bundle.rb +46 -0
  124. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/client.rb +13 -0
  125. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/core_ext/time.rb +6 -0
  126. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/em_server.rb +63 -0
  127. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/message.rb +46 -0
  128. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/network_packet.rb +42 -0
  129. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_argument.rb +26 -0
  130. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_packet.rb +140 -0
  131. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_types.rb +53 -0
  132. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/server.rb +91 -0
  133. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby.rb +18 -0
  134. data/templates/developer/objects/code_reviewer.md +1 -1
  135. data/templates/developer/objects/coordinator.md +16 -3
  136. data/templates/developer/objects/debugger.md +3 -3
  137. data/templates/empty/objects/assistant.md +10 -5
  138. data/templates/minimal/objects/assistant.md +10 -5
  139. data/templates/music/extras/sonic_pi_boot.rb +220 -0
  140. data/templates/music/manifest.yml +16 -0
  141. data/templates/music/objects/conductor.md +154 -0
  142. data/templates/music/objects/sonic_pi.md +65 -0
  143. data/templates/music/primitives/play_note.rb +55 -0
  144. data/templates/music/primitives/play_sequence.rb +51 -0
  145. data/templates/music/primitives/query_sonic_pi.rb +139 -0
  146. data/templates/music/primitives/set_synth.rb +53 -0
  147. data/templates/music/primitives/set_tempo.rb +43 -0
  148. data/templates/music/primitives/stop_voice.rb +44 -0
  149. data/templates/music/primitives/support/osc_bridge.rb +33 -0
  150. data/templates/music/vendor/osc-ruby-1.1.5/LICENSE +19 -0
  151. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/address_pattern.rb +58 -0
  152. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/broadcast_client.rb +24 -0
  153. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/bundle.rb +46 -0
  154. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/client.rb +13 -0
  155. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/core_ext/time.rb +6 -0
  156. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/em_server.rb +63 -0
  157. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/message.rb +46 -0
  158. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/network_packet.rb +42 -0
  159. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_argument.rb +26 -0
  160. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_packet.rb +140 -0
  161. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_types.rb +53 -0
  162. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/server.rb +91 -0
  163. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby.rb +18 -0
  164. data/templates/music-beginner/README.md +100 -0
  165. data/templates/music-beginner/extras/README.md +27 -0
  166. data/templates/music-beginner/extras/sonic_pi_boot.rb +297 -0
  167. data/templates/music-beginner/manifest.yml +25 -0
  168. data/templates/music-beginner/objects/conductor.md +222 -0
  169. data/templates/music-beginner/objects/engine_setup.md +54 -0
  170. data/templates/music-beginner/objects/voice_bass.md +43 -0
  171. data/templates/music-beginner/objects/voice_keys.md +48 -0
  172. data/templates/music-beginner/objects/voice_pad.md +46 -0
  173. data/templates/music-beginner/objects/voice_percussion.md +45 -0
  174. data/templates/music-beginner/objects/voice_texture.md +37 -0
  175. data/templates/music-beginner/primitives/play_pattern.rb +118 -0
  176. data/templates/music-beginner/primitives/query_music_engine.rb +94 -0
  177. data/templates/music-beginner/primitives/set_instrument.rb +47 -0
  178. data/templates/music-beginner/primitives/set_tempo.rb +43 -0
  179. data/templates/music-beginner/primitives/set_voice_level.rb +46 -0
  180. data/templates/music-beginner/primitives/stop_voice.rb +37 -0
  181. data/templates/music-beginner/primitives/support/osc_bridge.rb +25 -0
  182. data/templates/music-beginner/vendor/osc-ruby-1.1.5/LICENSE +19 -0
  183. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/address_pattern.rb +58 -0
  184. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/broadcast_client.rb +24 -0
  185. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/bundle.rb +46 -0
  186. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/client.rb +13 -0
  187. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/core_ext/time.rb +6 -0
  188. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/em_server.rb +63 -0
  189. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/message.rb +46 -0
  190. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/network_packet.rb +42 -0
  191. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_argument.rb +26 -0
  192. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_packet.rb +140 -0
  193. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_types.rb +53 -0
  194. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/server.rb +91 -0
  195. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby.rb +18 -0
  196. data/templates/music-pro/extras/AUDITION.md +100 -0
  197. data/templates/music-pro/extras/sonic_pi_boot.rb +546 -0
  198. data/templates/music-pro/manifest.yml +19 -0
  199. data/templates/music-pro/objects/conductor.md +126 -0
  200. data/templates/music-pro/objects/sonic_pi.md +35 -0
  201. data/templates/music-pro/objects/voice_harmony.md +48 -0
  202. data/templates/music-pro/objects/voice_rhythm.md +43 -0
  203. data/templates/music-pro/objects/voice_texture.md +42 -0
  204. data/templates/music-pro/primitives/define_patch.rb +46 -0
  205. data/templates/music-pro/primitives/query_music_engine.rb +69 -0
  206. data/templates/music-pro/primitives/set_live_pattern.rb +70 -0
  207. data/templates/music-pro/primitives/set_tempo.rb +31 -0
  208. data/templates/music-pro/primitives/stop_voice.rb +31 -0
  209. data/templates/music-pro/primitives/support/music_protocol.rb +136 -0
  210. data/templates/music-pro/primitives/support/osc_bridge.rb +33 -0
  211. data/templates/music-pro/primitives/trigger_event.rb +44 -0
  212. data/templates/music-pro/vendor/osc-ruby-1.1.5/LICENSE +19 -0
  213. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/address_pattern.rb +58 -0
  214. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/broadcast_client.rb +24 -0
  215. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/bundle.rb +46 -0
  216. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/client.rb +13 -0
  217. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/core_ext/time.rb +6 -0
  218. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/em_server.rb +63 -0
  219. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/message.rb +46 -0
  220. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/network_packet.rb +42 -0
  221. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_argument.rb +26 -0
  222. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_packet.rb +140 -0
  223. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_types.rb +53 -0
  224. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/server.rb +91 -0
  225. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby.rb +18 -0
  226. data/templates/writer/objects/coordinator.md +14 -3
  227. data/templates/writer/objects/editor.md +1 -1
  228. data/templates/writer/objects/researcher.md +2 -2
  229. data/tools/thread-explorer.html +28 -1
  230. metadata +175 -65
  231. data/CLAUDE.md +0 -176
  232. data/Gemfile +0 -10
  233. data/Gemfile.lock +0 -233
  234. data/IMPLEMENTATION_PLAN.md +0 -1073
  235. data/Rakefile +0 -27
  236. data/design-doc-v2.md +0 -1232
  237. data/frontend/.gitignore +0 -3
  238. data/frontend/index.html +0 -17
  239. data/frontend/package-lock.json +0 -4540
  240. data/frontend/package.json +0 -36
  241. data/frontend/postcss.config.js +0 -6
  242. data/frontend/src/App.tsx +0 -103
  243. data/frontend/src/canvas/CanvasView.tsx +0 -113
  244. data/frontend/src/canvas/ForceLayout.ts +0 -115
  245. data/frontend/src/canvas/SceneManager.ts +0 -587
  246. data/frontend/src/canvas/canvasStore.ts +0 -47
  247. data/frontend/src/canvas/constants.ts +0 -95
  248. data/frontend/src/canvas/controls/CameraControls.ts +0 -98
  249. data/frontend/src/canvas/edges/MessageArc.ts +0 -149
  250. data/frontend/src/canvas/inspector/InspectorPanel.tsx +0 -31
  251. data/frontend/src/canvas/inspector/POInspector.tsx +0 -262
  252. data/frontend/src/canvas/inspector/ToolCallInspector.tsx +0 -67
  253. data/frontend/src/canvas/nodes/PONode.ts +0 -249
  254. data/frontend/src/canvas/nodes/ToolCallNode.ts +0 -116
  255. data/frontend/src/canvas/types.ts +0 -24
  256. data/frontend/src/components/ContextMenu.tsx +0 -68
  257. data/frontend/src/components/Inspector.tsx +0 -232
  258. data/frontend/src/components/MarkdownMessage.tsx +0 -155
  259. data/frontend/src/components/MethodList.tsx +0 -90
  260. data/frontend/src/components/ModelSelector.tsx +0 -115
  261. data/frontend/src/components/NotificationPanel.tsx +0 -130
  262. data/frontend/src/components/ObjectList.tsx +0 -78
  263. data/frontend/src/components/PaneSlot.tsx +0 -30
  264. data/frontend/src/components/SourcePane.tsx +0 -202
  265. data/frontend/src/components/SystemBar.tsx +0 -74
  266. data/frontend/src/components/Transcript.tsx +0 -76
  267. data/frontend/src/components/UsagePanel.tsx +0 -105
  268. data/frontend/src/components/Workspace.tsx +0 -260
  269. data/frontend/src/components/index.ts +0 -12
  270. data/frontend/src/hooks/useResize.ts +0 -55
  271. data/frontend/src/hooks/useWebSocket.ts +0 -517
  272. data/frontend/src/index.css +0 -103
  273. data/frontend/src/main.tsx +0 -10
  274. data/frontend/src/store/index.ts +0 -279
  275. data/frontend/src/types/index.ts +0 -165
  276. data/frontend/tailwind.config.js +0 -44
  277. data/frontend/tsconfig.json +0 -30
  278. data/frontend/vite.config.ts +0 -29
  279. data/lib/prompt_objects/server/public/assets/index-D1myxE0l.js +0 -4345
  280. data/lib/prompt_objects/server/public/assets/index-DdCcwC-Z.css +0 -1
  281. data/lib/prompt_objects/universal/add_primitive.rb +0 -118
  282. data/lib/prompt_objects/universal/ask_human.rb +0 -109
  283. data/lib/prompt_objects/universal/create_primitive.rb +0 -174
  284. data/lib/prompt_objects/universal/list_primitives.rb +0 -145
  285. data/lib/prompt_objects/universal/think.rb +0 -41
  286. data/prompt_objects.gemspec +0 -50
  287. data/templates/basic/.gitignore +0 -2
  288. data/templates/developer/.gitignore +0 -5
  289. data/templates/empty/.gitignore +0 -5
  290. data/templates/empty/objects/.gitkeep +0 -0
  291. data/templates/minimal/.gitignore +0 -5
  292. data/templates/writer/.gitignore +0 -5
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PromptObjects
4
+ module Repositories
5
+ class ArtifactRepository < BaseRepository
6
+ Mutation = Data.define(:artifact, :cursor)
7
+
8
+ def upsert(**attributes)
9
+ upsert_with_cursor(**attributes).artifact
10
+ end
11
+
12
+ def upsert_with_cursor(workspace_session_id:, owner_po:, owner_thread_id:, producing_run_id:,
13
+ title:, html:, mode: "replace", metadata: {}, id: nil)
14
+ raise ArgumentError, "unknown artifact mode: #{mode}" unless %w[replace version].include?(mode.to_s)
15
+
16
+ transaction do |db|
17
+ existing = if mode.to_s == "replace"
18
+ db.get_first_row(<<~SQL, [workspace_session_id, title])
19
+ SELECT * FROM artifacts
20
+ WHERE workspace_session_id = ? AND title = ? AND mode = 'replace'
21
+ ORDER BY updated_at DESC LIMIT 1
22
+ SQL
23
+ end
24
+ artifact_id = existing ? existing["id"] : (id || "artifact_#{SecureRandom.uuid}")
25
+ revision = existing ? existing["revision"].to_i + 1 : 1
26
+ now = Time.now.utc.iso8601
27
+ created_at = existing ? existing["created_at"] : now
28
+ params = [
29
+ artifact_id, workspace_session_id, owner_po, owner_thread_id,
30
+ producing_run_id, title, html, revision, mode.to_s, created_at,
31
+ now, JSON.generate(metadata)
32
+ ]
33
+ db.execute(<<~SQL, params)
34
+ INSERT INTO artifacts (
35
+ id, workspace_session_id, owner_po, owner_thread_id, producing_run_id,
36
+ title, html, revision, mode, created_at, updated_at, metadata
37
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
38
+ ON CONFLICT(id) DO UPDATE SET
39
+ owner_po = excluded.owner_po,
40
+ owner_thread_id = excluded.owner_thread_id,
41
+ producing_run_id = excluded.producing_run_id,
42
+ title = excluded.title,
43
+ html = excluded.html,
44
+ revision = excluded.revision,
45
+ mode = excluded.mode,
46
+ updated_at = excluded.updated_at,
47
+ metadata = excluded.metadata
48
+ SQL
49
+ row = db.get_first_row("SELECT * FROM artifacts WHERE id = ?", [artifact_id])
50
+ artifact = parse(row)
51
+ cursor = append_workspace_event(db, artifact)
52
+ Mutation.new(artifact: artifact, cursor: cursor)
53
+ end
54
+ end
55
+
56
+ def get(id)
57
+ row = read { |db| db.get_first_row("SELECT * FROM artifacts WHERE id = ?", [id]) }
58
+ row && parse(row)
59
+ end
60
+
61
+ def list(workspace_session_id:)
62
+ read do |db|
63
+ db.execute(
64
+ "SELECT * FROM artifacts WHERE workspace_session_id = ? ORDER BY updated_at ASC, id ASC",
65
+ [workspace_session_id]
66
+ ).map { |row| parse(row) }
67
+ end
68
+ end
69
+
70
+ def delete(id)
71
+ transaction do |db|
72
+ artifact = db.get_first_row("SELECT * FROM artifacts WHERE id = ?", [id])
73
+ next false unless artifact
74
+
75
+ db.execute("DELETE FROM artifacts WHERE id = ?", [id])
76
+ now = Time.now.utc.iso8601
77
+ db.execute(<<~SQL, [artifact["workspace_session_id"], id, now])
78
+ INSERT INTO workspace_events (
79
+ workspace_session_id, event_type, entity_type, entity_id, payload, created_at
80
+ ) VALUES (?, 'artifact_removed', 'artifact', ?, '{}', ?)
81
+ SQL
82
+ true
83
+ end
84
+ end
85
+
86
+ private
87
+
88
+ def append_workspace_event(db, artifact)
89
+ payload = { artifact_id: artifact.id, revision: artifact.revision }
90
+ db.execute(<<~SQL, [artifact.workspace_session_id, artifact.id, JSON.generate(payload), Time.now.utc.iso8601])
91
+ INSERT INTO workspace_events (
92
+ workspace_session_id, event_type, entity_type, entity_id, payload, created_at
93
+ ) VALUES (?, 'artifact_upserted', 'artifact', ?, ?, ?)
94
+ SQL
95
+ db.last_insert_row_id
96
+ end
97
+
98
+ def parse(row)
99
+ Domain::Artifact.new(
100
+ id: row["id"], workspace_session_id: row["workspace_session_id"],
101
+ owner_po: row["owner_po"], owner_thread_id: row["owner_thread_id"],
102
+ producing_run_id: row["producing_run_id"], title: row["title"], html: row["html"],
103
+ revision: row["revision"], mode: row["mode"], created_at: timestamp(row["created_at"]),
104
+ updated_at: timestamp(row["updated_at"]), metadata: json(row["metadata"])
105
+ )
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PromptObjects
4
+ module Repositories
5
+ class BaseRepository
6
+ def initialize(store)
7
+ @store = store
8
+ end
9
+
10
+ private
11
+
12
+ def read(&block)
13
+ @store.with_connection(&block)
14
+ end
15
+
16
+ def transaction(&block)
17
+ @store.transaction(&block)
18
+ end
19
+
20
+ def json(value, default: {})
21
+ return default if value.nil?
22
+
23
+ JSON.parse(value, symbolize_names: true)
24
+ end
25
+
26
+ def json_array(value)
27
+ return [] if value.nil?
28
+
29
+ JSON.parse(value)
30
+ end
31
+
32
+ def timestamp(value)
33
+ value ? Time.parse(value) : nil
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PromptObjects
4
+ module Repositories
5
+ # Persists the exact Prompt Object definition used by a run.
6
+ class DefinitionRepository < BaseRepository
7
+ def attach(run_id:, definition:)
8
+ transaction do |db|
9
+ params = [
10
+ run_id, definition.po_name, definition.version, definition.prompt,
11
+ JSON.generate(definition.config), definition.provider, definition.model,
12
+ definition.capability_policy_version, Time.now.utc.iso8601
13
+ ]
14
+ db.execute(<<~SQL, params)
15
+ INSERT INTO run_definitions (
16
+ run_id, po_name, version, prompt, config, provider, model,
17
+ capability_policy_version, created_at
18
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
19
+ SQL
20
+ end
21
+ get(run_id)
22
+ end
23
+
24
+ def get(run_id)
25
+ row = read { |db| db.get_first_row("SELECT * FROM run_definitions WHERE run_id = ?", [run_id]) }
26
+ return nil unless row
27
+
28
+ Domain::PromptObjectDefinition.new(
29
+ po_name: row["po_name"], version: row["version"], prompt: row["prompt"],
30
+ config: JSON.parse(row["config"]), provider: row["provider"], model: row["model"],
31
+ capability_policy_version: row["capability_policy_version"]
32
+ )
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PromptObjects
4
+ module Repositories
5
+ class EnvDataRepository < BaseRepository
6
+ Mutation = Data.define(:entry, :cursor)
7
+ Removal = Data.define(:deleted, :cursor)
8
+
9
+ def set(**attributes)
10
+ set_with_cursor(**attributes).entry
11
+ end
12
+
13
+ def set_with_cursor(workspace_session_id:, key:, short_description:, value:, stored_by:)
14
+ now = Time.now.utc.iso8601
15
+ transaction do |db|
16
+ params = [
17
+ workspace_session_id, workspace_session_id, workspace_session_id,
18
+ key, short_description, JSON.generate(value), stored_by, now, now
19
+ ]
20
+ db.execute(<<~SQL, params)
21
+ INSERT INTO env_data (
22
+ root_thread_id, session_id, workspace_session_id, key,
23
+ short_description, value, stored_by, created_at, updated_at
24
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
25
+ ON CONFLICT(root_thread_id, key) DO UPDATE SET
26
+ workspace_session_id = excluded.workspace_session_id,
27
+ session_id = excluded.session_id,
28
+ short_description = excluded.short_description,
29
+ value = excluded.value,
30
+ stored_by = excluded.stored_by,
31
+ updated_at = excluded.updated_at
32
+ SQL
33
+ cursor = append_event(db, workspace_session_id, "store", key, stored_by, now)
34
+ entry = parse(db.get_first_row(<<~SQL, [workspace_session_id, key]))
35
+ SELECT * FROM env_data WHERE workspace_session_id = ? AND key = ?
36
+ SQL
37
+ Mutation.new(entry: entry, cursor: cursor)
38
+ end
39
+ end
40
+
41
+ def get(workspace_session_id:, key:)
42
+ row = read do |db|
43
+ db.get_first_row(
44
+ "SELECT * FROM env_data WHERE workspace_session_id = ? AND key = ?",
45
+ [workspace_session_id, key]
46
+ )
47
+ end
48
+ row && parse(row)
49
+ end
50
+
51
+ def list(workspace_session_id:)
52
+ read do |db|
53
+ db.execute(
54
+ "SELECT * FROM env_data WHERE workspace_session_id = ? ORDER BY key ASC",
55
+ [workspace_session_id]
56
+ ).map { |row| parse(row) }
57
+ end
58
+ end
59
+
60
+ def delete(workspace_session_id:, key:, stored_by:)
61
+ delete_with_cursor(
62
+ workspace_session_id: workspace_session_id,
63
+ key: key,
64
+ stored_by: stored_by
65
+ ).deleted
66
+ end
67
+
68
+ def delete_with_cursor(workspace_session_id:, key:, stored_by:)
69
+ transaction do |db|
70
+ db.execute(
71
+ "DELETE FROM env_data WHERE workspace_session_id = ? AND key = ?",
72
+ [workspace_session_id, key]
73
+ )
74
+ next Removal.new(deleted: false, cursor: nil) unless db.changes.positive?
75
+
76
+ cursor = append_event(db, workspace_session_id, "delete", key, stored_by, Time.now.utc.iso8601)
77
+ Removal.new(deleted: true, cursor: cursor)
78
+ end
79
+ end
80
+
81
+ private
82
+
83
+ def append_event(db, workspace_session_id, action, key, stored_by, now)
84
+ payload = { action: action, key: key, stored_by: stored_by }
85
+ db.execute(<<~SQL, [workspace_session_id, key, JSON.generate(payload), now])
86
+ INSERT INTO workspace_events (
87
+ workspace_session_id, event_type, entity_type, entity_id, payload, created_at
88
+ ) VALUES (?, 'env_data_changed', 'env_data', ?, ?, ?)
89
+ SQL
90
+ db.last_insert_row_id
91
+ end
92
+
93
+ def parse(row)
94
+ {
95
+ id: row["id"], workspace_session_id: row["workspace_session_id"],
96
+ key: row["key"], short_description: row["short_description"],
97
+ value: JSON.parse(row["value"], symbolize_names: true), stored_by: row["stored_by"],
98
+ created_at: timestamp(row["created_at"]), updated_at: timestamp(row["updated_at"])
99
+ }.freeze
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PromptObjects
4
+ module Repositories
5
+ class EventRepository < BaseRepository
6
+ def append_run_event(run_id:, kind:, data: {}, timestamp: Time.now.utc)
7
+ validate_kind!(kind)
8
+ transaction do |db|
9
+ run = db.get_first_row("SELECT * FROM runs WHERE id = ?", [run_id])
10
+ raise ArgumentError, "unknown run: #{run_id}" unless run
11
+
12
+ sequence = db.get_first_value(
13
+ "SELECT COALESCE(MAX(sequence), 0) + 1 FROM run_events WHERE run_id = ?",
14
+ [run_id]
15
+ )
16
+ occurred_at = timestamp.iso8601
17
+ workspace_cursor = append_workspace_event(
18
+ db,
19
+ workspace_session_id: run["workspace_session_id"],
20
+ event_type: "run_event",
21
+ entity_type: "run",
22
+ entity_id: run_id,
23
+ payload: { run_id: run_id, sequence: sequence, kind: kind.to_s, data: data },
24
+ created_at: occurred_at
25
+ )
26
+ params = [
27
+ run_id, sequence, workspace_cursor, run["workspace_session_id"],
28
+ run["thread_id"], kind.to_s, occurred_at, JSON.generate(data)
29
+ ]
30
+ db.execute(<<~SQL, params)
31
+ INSERT INTO run_events (
32
+ run_id, sequence, workspace_cursor, workspace_session_id,
33
+ thread_id, kind, timestamp, data
34
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
35
+ SQL
36
+ parse_run_event(db.get_first_row("SELECT * FROM run_events WHERE id = last_insert_rowid()"))
37
+ end
38
+ end
39
+
40
+ def run_events(run_id:, after_sequence: 0)
41
+ rows = read do |db|
42
+ db.execute(
43
+ "SELECT * FROM run_events WHERE run_id = ? AND sequence > ? ORDER BY sequence ASC",
44
+ [run_id, after_sequence]
45
+ )
46
+ end
47
+ rows.map { |row| parse_run_event(row) }
48
+ end
49
+
50
+ def workspace_events(workspace_session_id:, after_cursor: 0, limit: 500)
51
+ read do |db|
52
+ rows = db.execute(<<~SQL, [workspace_session_id, after_cursor, limit])
53
+ SELECT * FROM workspace_events
54
+ WHERE workspace_session_id = ? AND id > ?
55
+ ORDER BY id ASC
56
+ LIMIT ?
57
+ SQL
58
+ rows.map do |row|
59
+ {
60
+ cursor: row["id"], workspace_session_id: row["workspace_session_id"],
61
+ event_type: row["event_type"], entity_type: row["entity_type"],
62
+ entity_id: row["entity_id"], payload: json(row["payload"]),
63
+ created_at: timestamp(row["created_at"])
64
+ }.freeze
65
+ end
66
+ end
67
+ end
68
+
69
+ def current_cursor(workspace_session_id:)
70
+ read do |db|
71
+ db.get_first_value(
72
+ "SELECT COALESCE(MAX(id), 0) FROM workspace_events WHERE workspace_session_id = ?",
73
+ [workspace_session_id]
74
+ )
75
+ end
76
+ end
77
+
78
+ private
79
+
80
+ def validate_kind!(kind)
81
+ return if Execution::Contracts::RUN_EVENT_KINDS.include?(kind.to_s)
82
+
83
+ raise ArgumentError, "unknown run event kind: #{kind}"
84
+ end
85
+
86
+ def append_workspace_event(db, workspace_session_id:, event_type:, entity_type:, entity_id:, payload:, created_at:)
87
+ db.execute(<<~SQL, [workspace_session_id, event_type, entity_type, entity_id, JSON.generate(payload), created_at])
88
+ INSERT INTO workspace_events (
89
+ workspace_session_id, event_type, entity_type, entity_id, payload, created_at
90
+ ) VALUES (?, ?, ?, ?, ?, ?)
91
+ SQL
92
+ db.last_insert_row_id
93
+ end
94
+
95
+ def parse_run_event(row)
96
+ Domain::RunEvent.new(
97
+ id: row["id"], run_id: row["run_id"], sequence: row["sequence"],
98
+ workspace_cursor: row["workspace_cursor"],
99
+ workspace_session_id: row["workspace_session_id"], thread_id: row["thread_id"],
100
+ kind: row["kind"], timestamp: timestamp(row["timestamp"]), data: json(row["data"])
101
+ )
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PromptObjects
4
+ module Repositories
5
+ class MessageRepository < BaseRepository
6
+ def append(thread_id:, role:, content: nil, run_id: nil, turn_ordinal: nil,
7
+ position: nil, from_po: nil, tool_calls: nil, tool_results: nil,
8
+ usage: nil, message_uid: SecureRandom.uuid)
9
+ now = Time.now.utc.iso8601
10
+ transaction do |db|
11
+ position ||= next_position(db, thread_id, turn_ordinal)
12
+ params = [
13
+ thread_id, role.to_s, content, from_po, tool_calls && JSON.generate(tool_calls),
14
+ tool_results && JSON.generate(tool_results), usage && JSON.generate(usage), now,
15
+ message_uid, run_id, turn_ordinal, position
16
+ ]
17
+ db.execute(<<~SQL, params)
18
+ INSERT INTO messages (
19
+ session_id, role, content, from_po, tool_calls, tool_results, usage,
20
+ created_at, message_uid, run_id, turn_ordinal, position
21
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
22
+ SQL
23
+ db.execute("UPDATE sessions SET updated_at = ?, last_activity_at = ? WHERE id = ?", [now, now, thread_id])
24
+ parse(db.get_first_row("SELECT * FROM messages WHERE id = last_insert_rowid()"))
25
+ end
26
+ end
27
+
28
+ def list(thread_id:, through_turn: nil, after_id: 0, limit: 100)
29
+ rows = read do |db|
30
+ if through_turn
31
+ db.execute(<<~SQL, [thread_id, through_turn, after_id, limit])
32
+ SELECT * FROM messages
33
+ WHERE session_id = ? AND (turn_ordinal IS NULL OR turn_ordinal <= ?) AND id > ?
34
+ ORDER BY COALESCE(turn_ordinal, 0), COALESCE(position, id), id
35
+ LIMIT ?
36
+ SQL
37
+ else
38
+ db.execute(<<~SQL, [thread_id, after_id, limit])
39
+ SELECT * FROM messages
40
+ WHERE session_id = ? AND id > ?
41
+ ORDER BY COALESCE(turn_ordinal, 0), COALESCE(position, id), id
42
+ LIMIT ?
43
+ SQL
44
+ end
45
+ end
46
+ rows.map { |row| parse(row) }
47
+ end
48
+
49
+ private
50
+
51
+ def next_position(db, thread_id, turn_ordinal)
52
+ return nil unless turn_ordinal
53
+
54
+ db.get_first_value(
55
+ "SELECT COALESCE(MAX(position), -1) + 1 FROM messages WHERE session_id = ? AND turn_ordinal = ?",
56
+ [thread_id, turn_ordinal]
57
+ )
58
+ end
59
+
60
+ def parse(row)
61
+ {
62
+ id: row["id"], message_uid: row["message_uid"], thread_id: row["session_id"],
63
+ run_id: row["run_id"], turn_ordinal: row["turn_ordinal"], position: row["position"],
64
+ role: row["role"].to_sym, content: row["content"], from_po: row["from_po"],
65
+ tool_calls: row["tool_calls"] && JSON.parse(row["tool_calls"], symbolize_names: true),
66
+ tool_results: row["tool_results"] && JSON.parse(row["tool_results"], symbolize_names: true),
67
+ usage: row["usage"] && JSON.parse(row["usage"], symbolize_names: true),
68
+ created_at: timestamp(row["created_at"])
69
+ }.freeze
70
+ end
71
+ end
72
+ end
73
+ end