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
@@ -18,15 +18,30 @@ module PromptObjects
18
18
  message: {
19
19
  type: "string",
20
20
  description: "The message to send"
21
+ },
22
+ thread_id: {
23
+ type: "string",
24
+ description: "Optional runtime thread ID"
25
+ },
26
+ workspace_session_id: {
27
+ type: "string",
28
+ description: "Optional workspace session ID"
29
+ },
30
+ client_request_id: {
31
+ type: "string",
32
+ description: "Optional idempotency key"
33
+ },
34
+ deadline: {
35
+ type: "string",
36
+ description: "Optional ISO 8601 deadline for queued and running work"
21
37
  }
22
38
  },
23
39
  required: %w[po_name message]
24
40
  )
25
41
 
26
- def self.call(po_name:, message:, server_context:)
42
+ def self.call(po_name:, message:, thread_id: nil, workspace_session_id: nil,
43
+ client_request_id: nil, deadline: nil, server_context:)
27
44
  env = server_context[:env]
28
- context = server_context[:context]
29
-
30
45
  po = env.registry.get(po_name)
31
46
  unless po.is_a?(PromptObjects::PromptObject)
32
47
  return ::MCP::Tool::Response.new([{
@@ -35,35 +50,30 @@ module PromptObjects
35
50
  }])
36
51
  end
37
52
 
38
- # Log to message bus
39
- env.bus.publish(from: "mcp_client", to: po_name, message: message)
40
-
41
- # Set context for this interaction
42
- context.current_capability = po_name
43
- po.state = :working
44
-
45
- begin
46
- response = po.receive(message, context: context)
47
- po.state = :idle
48
-
49
- # Log response
50
- env.bus.publish(from: po_name, to: "mcp_client", message: response)
51
-
52
- ::MCP::Tool::Response.new([{
53
- type: "text",
54
- text: JSON.generate({
55
- po_name: po_name,
56
- response: response,
57
- history_length: po.history.length
58
- })
59
- }])
60
- rescue StandardError => e
61
- po.state = :idle
62
- ::MCP::Tool::Response.new([{
63
- type: "text",
64
- text: JSON.generate({ error: e.message, backtrace: e.backtrace.first(5) })
65
- }])
66
- end
53
+ run, response = env.execute_message_run(
54
+ target_po: po_name,
55
+ content: message,
56
+ from_source: "service:mcp",
57
+ workspace_session_id: workspace_session_id,
58
+ thread_id: thread_id,
59
+ client_request_id: client_request_id,
60
+ deadline: deadline,
61
+ tui_mode: true
62
+ )
63
+ history_length = Repositories::MessageRepository.new(env.session_store)
64
+ .list(thread_id: run.thread_id, limit: 10_000).length
65
+ ::MCP::Tool::Response.new([{
66
+ type: "text",
67
+ text: JSON.generate({
68
+ po_name: po_name,
69
+ response: response,
70
+ workspace_session_id: run.workspace_session_id,
71
+ thread_id: run.thread_id,
72
+ run_id: run.id,
73
+ status: run.status,
74
+ history_length: history_length
75
+ })
76
+ }])
67
77
  end
68
78
  end
69
79
  end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PromptObjects
4
+ module Operations
5
+ # Content-free operational snapshot for health checks and support bundles.
6
+ class Diagnostics
7
+ TABLES = %w[
8
+ workspace_sessions sessions runs messages run_events workspace_events
9
+ tool_executions artifacts env_data
10
+ ].freeze
11
+
12
+ def initialize(store)
13
+ @store = store
14
+ end
15
+
16
+ def snapshot(scheduler: {}, limits: {})
17
+ data = @store.with_connection do |db|
18
+ runs = db.execute(<<~SQL)
19
+ SELECT status, provider, model, enqueued_at, started_at, finished_at, usage
20
+ FROM runs
21
+ SQL
22
+ tools = db.execute("SELECT status, started_at, finished_at FROM tool_executions")
23
+ {
24
+ schema_version: db.get_first_value("PRAGMA user_version").to_i,
25
+ database: database_stats(db),
26
+ counts: TABLES.to_h { |table| [table.to_sym, db.get_first_value("SELECT COUNT(*) FROM #{table}").to_i] },
27
+ runs: run_stats(runs),
28
+ tools: tool_stats(tools)
29
+ }
30
+ end
31
+ data.merge(
32
+ generated_at: Time.now.utc.iso8601,
33
+ scheduler: scheduler,
34
+ limits: limits
35
+ )
36
+ end
37
+
38
+ private
39
+
40
+ def database_stats(db)
41
+ path = db.execute("PRAGMA database_list").find { |row| row["name"] == "main" }&.dig("file")
42
+ files = [path, "#{path}-wal", "#{path}-shm"].compact
43
+ {
44
+ filename: path && File.basename(path),
45
+ bytes: files.sum { |file| File.file?(file) ? File.size(file) : 0 },
46
+ journal_mode: db.get_first_value("PRAGMA journal_mode"),
47
+ integrity: db.get_first_value("PRAGMA quick_check")
48
+ }
49
+ end
50
+
51
+ def run_stats(rows)
52
+ status_counts = rows.group_by { |row| row["status"] }.transform_values(&:length)
53
+ terminal = rows.count { |row| %w[completed failed cancelled skipped interrupted].include?(row["status"]) }
54
+ failures = rows.count { |row| %w[failed interrupted].include?(row["status"]) }
55
+ {
56
+ by_status: status_counts,
57
+ active: rows.count { |row| %w[queued running waiting].include?(row["status"]) },
58
+ failure_rate: terminal.zero? ? 0.0 : (failures.to_f / terminal).round(4),
59
+ queue_wait_ms: duration_stats(rows, "enqueued_at", "started_at"),
60
+ duration_ms: duration_stats(rows, "started_at", "finished_at"),
61
+ provider_usage: provider_usage(rows)
62
+ }
63
+ end
64
+
65
+ def tool_stats(rows)
66
+ {
67
+ by_status: rows.group_by { |row| row["status"] }.transform_values(&:length),
68
+ duration_ms: duration_stats(rows, "started_at", "finished_at")
69
+ }
70
+ end
71
+
72
+ def duration_stats(rows, start_key, finish_key)
73
+ values = rows.filter_map do |row|
74
+ next unless row[start_key] && row[finish_key]
75
+
76
+ ((Time.parse(row[finish_key]) - Time.parse(row[start_key])) * 1000).round(2)
77
+ end.sort
78
+ return { count: 0, average: 0.0, p95: 0.0, maximum: 0.0 } if values.empty?
79
+
80
+ {
81
+ count: values.length,
82
+ average: (values.sum / values.length.to_f).round(2),
83
+ p95: values[[(values.length * 0.95).ceil - 1, 0].max],
84
+ maximum: values.last
85
+ }
86
+ end
87
+
88
+ def provider_usage(rows)
89
+ rows.group_by { |row| [row["provider"] || "unknown", row["model"] || "unknown"] }
90
+ .to_h do |(provider, model), provider_rows|
91
+ usage = provider_rows.filter_map { |row| parse_json(row["usage"]) }
92
+ [[provider, model].join("/"), {
93
+ runs: provider_rows.length,
94
+ input_tokens: usage.sum { |value| token_value(value, :input_tokens, :prompt_tokens) },
95
+ output_tokens: usage.sum { |value| token_value(value, :output_tokens, :completion_tokens) }
96
+ }]
97
+ end
98
+ end
99
+
100
+ def parse_json(value)
101
+ value && JSON.parse(value, symbolize_names: true)
102
+ rescue JSON::ParserError
103
+ nil
104
+ end
105
+
106
+ def token_value(usage, *keys)
107
+ keys.each do |key|
108
+ value = usage[key]
109
+ return value.to_i if value
110
+ end
111
+ 0
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PromptObjects
4
+ module Operations
5
+ # Bounds replay logs while preserving current snapshots and durable domain
6
+ # rows. Pruned clients recover from workspace/thread snapshots.
7
+ class Retention
8
+ DEFAULT_RUN_EVENT_LIMIT = 500
9
+ DEFAULT_WORKSPACE_EVENT_LIMIT = 5_000
10
+
11
+ def initialize(store)
12
+ @store = store
13
+ end
14
+
15
+ def prune_events!(run_event_limit: DEFAULT_RUN_EVENT_LIMIT,
16
+ workspace_event_limit: DEFAULT_WORKSPACE_EVENT_LIMIT)
17
+ raise ArgumentError, "run_event_limit must be positive" unless run_event_limit.positive?
18
+ raise ArgumentError, "workspace_event_limit must be positive" unless workspace_event_limit.positive?
19
+
20
+ @store.transaction do |db|
21
+ run_event_ids = db.execute(<<~SQL, [run_event_limit]).map { |row| row["id"] }
22
+ SELECT id FROM (
23
+ SELECT id, ROW_NUMBER() OVER (PARTITION BY run_id ORDER BY sequence DESC) AS position
24
+ FROM run_events
25
+ ) WHERE position > ?
26
+ SQL
27
+ workspace_cursors = run_event_ids.empty? ? [] : db.execute(
28
+ "SELECT workspace_cursor FROM run_events WHERE id IN (#{placeholders(run_event_ids)})",
29
+ run_event_ids
30
+ ).map { |row| row["workspace_cursor"] }
31
+ delete_ids(db, "run_events", run_event_ids)
32
+ delete_ids(db, "workspace_events", workspace_cursors)
33
+
34
+ workspace_event_ids = db.execute(<<~SQL, [workspace_event_limit]).map { |row| row["id"] }
35
+ SELECT id FROM (
36
+ SELECT id, ROW_NUMBER() OVER (
37
+ PARTITION BY workspace_session_id ORDER BY id DESC
38
+ ) AS position
39
+ FROM workspace_events
40
+ ) WHERE position > ?
41
+ SQL
42
+ workspace_event_ids.reject! do |id|
43
+ db.get_first_value("SELECT 1 FROM run_events WHERE workspace_cursor = ?", [id])
44
+ end
45
+ delete_ids(db, "workspace_events", workspace_event_ids)
46
+
47
+ {
48
+ run_events_deleted: run_event_ids.length,
49
+ workspace_events_deleted: (workspace_cursors + workspace_event_ids).uniq.length
50
+ }
51
+ end
52
+ end
53
+
54
+ def compact!
55
+ @store.with_connection do |db|
56
+ db.execute("PRAGMA wal_checkpoint(TRUNCATE)")
57
+ db.execute("VACUUM")
58
+ end
59
+ true
60
+ end
61
+
62
+ private
63
+
64
+ def delete_ids(db, table, ids)
65
+ return if ids.empty?
66
+
67
+ db.execute("DELETE FROM #{table} WHERE id IN (#{placeholders(ids)})", ids)
68
+ end
69
+
70
+ def placeholders(values)
71
+ Array.new(values.length, "?").join(", ")
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,152 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PromptObjects
4
+ module Operations
5
+ # Projects the durable runtime records for one execution tree into a
6
+ # debugger-friendly read model. The projection deliberately labels model
7
+ # context as reconstructed until exact provider exchanges are persisted.
8
+ class RunTraceBuilder
9
+ CATEGORY_BY_KIND = {
10
+ "model_started" => "model",
11
+ "model_completed" => "model",
12
+ "tool_batch_started" => "tool_batch",
13
+ "tool_started" => "tool",
14
+ "tool_completed" => "tool",
15
+ "tool_failed" => "tool",
16
+ "tool_cancelled" => "tool",
17
+ "delegation_started" => "delegation",
18
+ "delegation_completed" => "delegation",
19
+ "artifact_upserted" => "state",
20
+ "env_data_changed" => "state"
21
+ }.freeze
22
+
23
+ def initialize(store)
24
+ @runs = Repositories::RunRepository.new(store)
25
+ @definitions = Repositories::DefinitionRepository.new(store)
26
+ @messages = Repositories::MessageRepository.new(store)
27
+ @events = Repositories::EventRepository.new(store)
28
+ @tools = Repositories::ToolExecutionRepository.new(store)
29
+ @artifacts = Repositories::ArtifactRepository.new(store)
30
+ end
31
+
32
+ def build(run_id)
33
+ requested = @runs.get(run_id)
34
+ raise ArgumentError, "unknown run: #{run_id}" unless requested
35
+
36
+ root_run_id = requested.root_run_id || requested.id
37
+ related_runs = @runs.tree(root_run_id: root_run_id)
38
+ workspace_artifacts = @artifacts.list(workspace_session_id: requested.workspace_session_id)
39
+
40
+ {
41
+ requested_run_id: requested.id,
42
+ root_run_id: root_run_id,
43
+ workspace_session_id: requested.workspace_session_id,
44
+ completeness: {
45
+ run_records: "exact",
46
+ definitions: "exact_when_available",
47
+ messages: "exact",
48
+ tools: "exact",
49
+ model_context: "reconstructed",
50
+ state_history: "current_revision_only"
51
+ },
52
+ runs: related_runs.map { |run| build_run(run, related_runs, workspace_artifacts) }
53
+ }
54
+ end
55
+
56
+ private
57
+
58
+ def build_run(run, related_runs, workspace_artifacts)
59
+ events = @events.run_events(run_id: run.id)
60
+ tools = @tools.list(run_id: run.id)
61
+ definition = @definitions.get(run.id)
62
+ context_messages = @messages.list(
63
+ thread_id: run.thread_id,
64
+ through_turn: run.turn_ordinal,
65
+ limit: 10_000
66
+ )
67
+ run_messages = context_messages.select { |message| message[:run_id] == run.id }
68
+ produced_artifacts = workspace_artifacts.select { |artifact| artifact.producing_run_id == run.id }
69
+
70
+ {
71
+ run: run.to_h,
72
+ definition: definition&.to_h,
73
+ completeness: {
74
+ definition: definition ? "exact" : "unavailable",
75
+ model_context: "reconstructed",
76
+ state_history: produced_artifacts.empty? ? "unavailable" : "current_revision_only"
77
+ },
78
+ context_messages: context_messages,
79
+ messages: run_messages,
80
+ tool_executions: tools.map(&:to_h),
81
+ artifacts: produced_artifacts.map(&:to_h),
82
+ nodes: events.map do |event|
83
+ build_node(event, tools, related_runs, produced_artifacts)
84
+ end
85
+ }
86
+ end
87
+
88
+ def build_node(event, tools, related_runs, artifacts)
89
+ tool = tool_for(event, tools)
90
+ child = child_for(event, related_runs)
91
+ artifact = artifact_for(event, artifacts)
92
+ {
93
+ id: "#{event.run_id}:#{event.sequence}",
94
+ run_id: event.run_id,
95
+ sequence: event.sequence,
96
+ workspace_cursor: event.workspace_cursor,
97
+ kind: event.kind,
98
+ category: category_for(event.kind),
99
+ title: title_for(event, tool, child, artifact),
100
+ status: status_for(event.kind),
101
+ timestamp: event.timestamp,
102
+ data: event.data,
103
+ tool_execution: tool&.to_h,
104
+ child_run: child&.to_h,
105
+ artifact: artifact&.to_h
106
+ }
107
+ end
108
+
109
+ def tool_for(event, tools)
110
+ execution_id = event.data[:tool_execution_id] || event.data["tool_execution_id"]
111
+ tools.find { |tool| tool.id == execution_id }
112
+ end
113
+
114
+ def child_for(event, related_runs)
115
+ child_run_id = event.data[:child_run_id] || event.data["child_run_id"]
116
+ related_runs.find { |run| run.id == child_run_id }
117
+ end
118
+
119
+ def artifact_for(event, artifacts)
120
+ artifact_id = event.data[:artifact_id] || event.data["artifact_id"]
121
+ artifacts.find { |artifact| artifact.id == artifact_id }
122
+ end
123
+
124
+ def category_for(kind)
125
+ CATEGORY_BY_KIND.fetch(kind, "lifecycle")
126
+ end
127
+
128
+ def status_for(kind)
129
+ return "failed" if kind.end_with?("_failed")
130
+ return "cancelled" if kind.end_with?("_cancelled") || kind.end_with?("_interrupted")
131
+ return "completed" if kind.end_with?("_completed") || kind == "artifact_upserted"
132
+ return "active" if kind.end_with?("_started") || kind.end_with?("_waiting")
133
+
134
+ "info"
135
+ end
136
+
137
+ def title_for(event, tool, child, artifact)
138
+ case event.kind
139
+ when "tool_started", "tool_completed", "tool_failed", "tool_cancelled"
140
+ [event.kind.tr("_", " "), tool&.capability_name || event.data[:name]].compact.join(" · ")
141
+ when "delegation_started", "delegation_completed"
142
+ target = child&.po_name || event.data[:target_po] || event.data["target_po"]
143
+ [event.kind.tr("_", " "), target].compact.join(" · ")
144
+ when "artifact_upserted"
145
+ ["artifact upserted", artifact&.title || event.data[:title] || event.data["title"]].compact.join(" · ")
146
+ else
147
+ event.kind.tr("_", " ")
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
@@ -7,6 +7,8 @@ module PromptObjects
7
7
  module Primitives
8
8
  # Primitive capability to fetch content from a URL.
9
9
  class HttpGet < Primitive
10
+ execution_policy parallel_safe: true, side_effect: :read
11
+
10
12
  def name
11
13
  "http_get"
12
14
  end
@@ -4,6 +4,8 @@ module PromptObjects
4
4
  module Primitives
5
5
  # Primitive capability to list files in a directory.
6
6
  class ListFiles < Primitive
7
+ execution_policy parallel_safe: true, side_effect: :read
8
+
7
9
  def name
8
10
  "list_files"
9
11
  end
@@ -4,6 +4,8 @@ module PromptObjects
4
4
  module Primitives
5
5
  # Primitive capability to read file contents.
6
6
  class ReadFile < Primitive
7
+ execution_policy parallel_safe: true, side_effect: :read
8
+
7
9
  def name
8
10
  "read_file"
9
11
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PromptObjects
4
+ module Primitives
5
+ # Optional standard-library capability for visible reasoning notes.
6
+ # Prompt Objects must declare this tool explicitly when its trace is useful.
7
+ class Think < Primitive
8
+ execution_policy parallel_safe: true, side_effect: :read
9
+
10
+ def name
11
+ "think"
12
+ end
13
+
14
+ def description
15
+ "Record a visible reasoning note before acting. Use only when an explicit thinking trace helps the human follow the work."
16
+ end
17
+
18
+ def parameters
19
+ {
20
+ type: "object",
21
+ properties: {
22
+ thought: {
23
+ type: "string",
24
+ description: "Reasoning to show in the conversation"
25
+ }
26
+ },
27
+ required: ["thought"]
28
+ }
29
+ end
30
+
31
+ def receive(message, context:)
32
+ "Thought recorded."
33
+ end
34
+ end
35
+ end
36
+ end
@@ -4,6 +4,15 @@ module PromptObjects
4
4
  module Primitives
5
5
  # Primitive capability to write content to a file.
6
6
  class WriteFile < Primitive
7
+ execution_policy(
8
+ parallel_safe: true,
9
+ side_effect: :write,
10
+ resource_keys: ->(arguments, _context, _capability) {
11
+ path = arguments[:path] || arguments["path"]
12
+ path ? ["file:#{File.expand_path(path)}"] : []
13
+ }
14
+ )
15
+
7
16
  def name
8
17
  "write_file"
9
18
  end