prompt_objects 0.6.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 (291) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +126 -0
  3. data/README.md +93 -22
  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 +1337 -20
  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 +171 -519
  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 +549 -53
  65. data/lib/prompt_objects/server/app.rb +133 -14
  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 -227
  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 +600 -13
  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 +23 -15
  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 +8 -14
  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 +10 -14
  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/{store_env_data.rb → set_env_data.rb} +34 -20
  87. data/lib/prompt_objects/version.rb +5 -0
  88. data/lib/prompt_objects.rb +33 -8
  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 +2 -2
  92. data/templates/arc-agi-1/objects/solver.md +6 -5
  93. data/templates/arc-agi-1/objects/verifier.md +2 -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 +1 -1
  230. metadata +172 -65
  231. data/CLAUDE.md +0 -177
  232. data/Gemfile +0 -10
  233. data/Rakefile +0 -27
  234. data/frontend/.gitignore +0 -3
  235. data/frontend/index.html +0 -17
  236. data/frontend/package-lock.json +0 -4540
  237. data/frontend/package.json +0 -36
  238. data/frontend/postcss.config.js +0 -6
  239. data/frontend/src/App.tsx +0 -104
  240. data/frontend/src/canvas/CanvasView.tsx +0 -113
  241. data/frontend/src/canvas/ForceLayout.ts +0 -115
  242. data/frontend/src/canvas/SceneManager.ts +0 -587
  243. data/frontend/src/canvas/canvasStore.ts +0 -47
  244. data/frontend/src/canvas/constants.ts +0 -95
  245. data/frontend/src/canvas/controls/CameraControls.ts +0 -98
  246. data/frontend/src/canvas/edges/MessageArc.ts +0 -149
  247. data/frontend/src/canvas/inspector/InspectorPanel.tsx +0 -31
  248. data/frontend/src/canvas/inspector/POInspector.tsx +0 -262
  249. data/frontend/src/canvas/inspector/ToolCallInspector.tsx +0 -67
  250. data/frontend/src/canvas/nodes/PONode.ts +0 -249
  251. data/frontend/src/canvas/nodes/ToolCallNode.ts +0 -116
  252. data/frontend/src/canvas/types.ts +0 -24
  253. data/frontend/src/components/ContextMenu.tsx +0 -68
  254. data/frontend/src/components/EnvDataPane.tsx +0 -69
  255. data/frontend/src/components/Inspector.tsx +0 -263
  256. data/frontend/src/components/MarkdownMessage.tsx +0 -155
  257. data/frontend/src/components/MethodList.tsx +0 -90
  258. data/frontend/src/components/ModelSelector.tsx +0 -115
  259. data/frontend/src/components/NotificationPanel.tsx +0 -130
  260. data/frontend/src/components/ObjectList.tsx +0 -78
  261. data/frontend/src/components/PaneSlot.tsx +0 -30
  262. data/frontend/src/components/SourcePane.tsx +0 -202
  263. data/frontend/src/components/SystemBar.tsx +0 -74
  264. data/frontend/src/components/Transcript.tsx +0 -76
  265. data/frontend/src/components/UsagePanel.tsx +0 -105
  266. data/frontend/src/components/Workspace.tsx +0 -260
  267. data/frontend/src/components/index.ts +0 -12
  268. data/frontend/src/hooks/useResize.ts +0 -55
  269. data/frontend/src/hooks/useWebSocket.ts +0 -559
  270. data/frontend/src/index.css +0 -102
  271. data/frontend/src/main.tsx +0 -10
  272. data/frontend/src/store/index.ts +0 -311
  273. data/frontend/src/types/index.ts +0 -176
  274. data/frontend/tailwind.config.js +0 -44
  275. data/frontend/tsconfig.json +0 -30
  276. data/frontend/vite.config.ts +0 -29
  277. data/lib/prompt_objects/server/public/assets/index-DEPawnfZ.js +0 -4345
  278. data/lib/prompt_objects/server/public/assets/index-oMrRce1m.css +0 -1
  279. data/lib/prompt_objects/universal/add_primitive.rb +0 -118
  280. data/lib/prompt_objects/universal/ask_human.rb +0 -109
  281. data/lib/prompt_objects/universal/create_primitive.rb +0 -174
  282. data/lib/prompt_objects/universal/list_primitives.rb +0 -145
  283. data/lib/prompt_objects/universal/think.rb +0 -41
  284. data/lib/prompt_objects/universal/update_env_data.rb +0 -88
  285. data/prompt_objects.gemspec +0 -50
  286. data/templates/basic/.gitignore +0 -2
  287. data/templates/developer/.gitignore +0 -5
  288. data/templates/empty/.gitignore +0 -5
  289. data/templates/empty/objects/.gitkeep +0 -0
  290. data/templates/minimal/.gitignore +0 -5
  291. data/templates/writer/.gitignore +0 -5
@@ -0,0 +1,325 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "timeout"
4
+
5
+ module PromptObjects
6
+ module Execution
7
+ # Thread-aware in-process scheduler. It owns admission, per-thread writer
8
+ # leases, worker slots, and cross-thread priority selection; Runner owns the
9
+ # execution lifecycle after a lease is granted.
10
+ class Scheduler
11
+ class RejectedError < StandardError; end
12
+ class ClosedError < StandardError; end
13
+
14
+ Entry = Data.define(:run, :ticket, :deadline, :enqueue_sequence, :options)
15
+
16
+ class Ticket
17
+ def initialize(run_id)
18
+ @run_id = run_id
19
+ @mutex = Mutex.new
20
+ @condition = ConditionVariable.new
21
+ @complete = false
22
+ @value = nil
23
+ @error = nil
24
+ end
25
+
26
+ attr_reader :run_id
27
+
28
+ def wait(timeout: nil)
29
+ deadline = timeout && monotonic_time + timeout
30
+ @mutex.synchronize do
31
+ until @complete
32
+ remaining = deadline && deadline - monotonic_time
33
+ raise Timeout::Error, "run #{@run_id} did not finish" if remaining && !remaining.positive?
34
+
35
+ @condition.wait(@mutex, remaining)
36
+ end
37
+ raise @error if @error
38
+
39
+ @value
40
+ end
41
+ end
42
+
43
+ def complete?
44
+ @mutex.synchronize { @complete }
45
+ end
46
+
47
+ def resolve(value)
48
+ finish(value: value)
49
+ end
50
+
51
+ def reject(error)
52
+ finish(error: error)
53
+ end
54
+
55
+ private
56
+
57
+ def finish(value: nil, error: nil)
58
+ @mutex.synchronize do
59
+ return if @complete
60
+
61
+ @value = value
62
+ @error = error
63
+ @complete = true
64
+ @condition.broadcast
65
+ end
66
+ end
67
+
68
+ def monotonic_time
69
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
70
+ end
71
+ end
72
+
73
+ def initialize(worker_limit:, max_active_runs:, max_active_per_po:, max_queued_per_thread:,
74
+ execute:, ready: nil, cancel_queued: nil)
75
+ raise ArgumentError, "max_active_runs must be positive" unless max_active_runs.positive?
76
+ raise ArgumentError, "max_active_per_po must be positive" unless max_active_per_po.positive?
77
+ raise ArgumentError, "max_queued_per_thread must be positive" unless max_queued_per_thread.positive?
78
+
79
+ @execute = execute
80
+ @ready = ready || ->(_run) { true }
81
+ @cancel_queued = cancel_queued
82
+ @max_active_runs = max_active_runs
83
+ @max_active_per_po = max_active_per_po
84
+ @max_queued_per_thread = max_queued_per_thread
85
+ @queues = Hash.new { |hash, key| hash[key] = [] }
86
+ @entries_by_run_id = {}
87
+ @running_tokens = {}
88
+ @active_po_counts = Hash.new(0)
89
+ @active_threads = {}
90
+ @mutex = Mutex.new
91
+ @condition = ConditionVariable.new
92
+ @closed = false
93
+ @enqueue_sequence = 0
94
+ @workers = Array.new(worker_limit) { start_worker }
95
+ @deadline_monitor = start_deadline_monitor
96
+ end
97
+
98
+ def submit(run, deadline: nil, **options)
99
+ @mutex.synchronize do
100
+ raise ClosedError, "scheduler is closed" if @closed
101
+ return @entries_by_run_id.fetch(run.id).ticket if @entries_by_run_id.key?(run.id)
102
+ raise RejectedError, "workspace active-run limit reached" if @entries_by_run_id.size >= @max_active_runs
103
+
104
+ queue = @queues[run.thread_id]
105
+ if queue.size >= @max_queued_per_thread
106
+ raise RejectedError, "thread queue limit reached for #{run.thread_id}"
107
+ end
108
+
109
+ @enqueue_sequence += 1
110
+ ticket = Ticket.new(run.id)
111
+ entry = Entry.new(
112
+ run: run,
113
+ ticket: ticket,
114
+ deadline: deadline,
115
+ enqueue_sequence: @enqueue_sequence,
116
+ options: options.freeze
117
+ )
118
+ queue << entry
119
+ queue.sort_by! { |queued| [queued.run.turn_ordinal, queued.enqueue_sequence] }
120
+ @entries_by_run_id[run.id] = entry
121
+ @condition.broadcast
122
+ ticket
123
+ end
124
+ end
125
+
126
+ def cancel(run_id, reason: "cancelled")
127
+ queued = nil
128
+ token = nil
129
+ @mutex.synchronize do
130
+ entry = @entries_by_run_id[run_id]
131
+ return false unless entry
132
+
133
+ token = @running_tokens[run_id]
134
+ unless token
135
+ @queues[entry.run.thread_id].delete(entry)
136
+ cleanup_queue(entry.run.thread_id)
137
+ @entries_by_run_id.delete(run_id)
138
+ queued = entry
139
+ end
140
+ @condition.broadcast
141
+ end
142
+
143
+ if token
144
+ token.cancel(reason)
145
+ else
146
+ @cancel_queued&.call(queued.run, reason)
147
+ queued.ticket.resolve(:cancelled)
148
+ end
149
+ true
150
+ end
151
+
152
+ def queued_count
153
+ @mutex.synchronize { @queues.values.sum(&:size) }
154
+ end
155
+
156
+ def running_count
157
+ @mutex.synchronize { @running_tokens.size }
158
+ end
159
+
160
+ def ticket(run_id)
161
+ @mutex.synchronize { @entries_by_run_id[run_id]&.ticket }
162
+ end
163
+
164
+ # Execute a queued run on the caller's current capacity. This is used by
165
+ # a parent joining its child so a one-worker scheduler cannot deadlock
166
+ # with the parent waiting and the child unable to start.
167
+ def help(run_id)
168
+ entry, token = claim_queued(run_id)
169
+ return ticket(run_id) unless entry
170
+
171
+ execute(entry, token)
172
+ entry.ticket
173
+ end
174
+
175
+ def wake
176
+ @mutex.synchronize { @condition.broadcast }
177
+ end
178
+
179
+ def shutdown(wait: true)
180
+ queued = []
181
+ @mutex.synchronize do
182
+ return if @closed
183
+
184
+ @closed = true
185
+ queued = @queues.values.flatten
186
+ @queues.clear
187
+ queued.each { |entry| @entries_by_run_id.delete(entry.run.id) }
188
+ @running_tokens.each_value { |token| token.cancel("scheduler shutdown") }
189
+ @condition.broadcast
190
+ end
191
+ queued.each { |entry| entry.ticket.reject(ClosedError.new("scheduler shut down")) }
192
+ @workers.each(&:join) if wait
193
+ @deadline_monitor.join if wait
194
+ end
195
+
196
+ private
197
+
198
+ def start_worker
199
+ Thread.new do
200
+ Thread.current.name = "prompt-objects-scheduler" if Thread.current.respond_to?(:name=)
201
+ worker_loop
202
+ end
203
+ end
204
+
205
+ def start_deadline_monitor
206
+ Thread.new do
207
+ Thread.current.name = "prompt-objects-deadlines" if Thread.current.respond_to?(:name=)
208
+ @mutex.synchronize do
209
+ loop do
210
+ break if @closed
211
+
212
+ expire_queued_entries
213
+ @condition.wait(@mutex, next_deadline_wait)
214
+ end
215
+ end
216
+ end
217
+ end
218
+
219
+ def worker_loop
220
+ loop do
221
+ entry, token = next_entry
222
+ break unless entry
223
+
224
+ execute(entry, token)
225
+ end
226
+ end
227
+
228
+ def next_entry
229
+ @mutex.synchronize do
230
+ loop do
231
+ return [nil, nil] if @closed
232
+
233
+ expire_queued_entries
234
+ entry = runnable_heads.max_by do |candidate|
235
+ [candidate.run.priority, -candidate.enqueue_sequence]
236
+ end
237
+ if entry
238
+ queue = @queues[entry.run.thread_id]
239
+ queue.shift
240
+ cleanup_queue(entry.run.thread_id)
241
+ token = CancellationToken.new
242
+ @active_threads[entry.run.thread_id] = entry.run.id
243
+ @running_tokens[entry.run.id] = token
244
+ @active_po_counts[entry.run.po_name] += 1
245
+ return [entry, token]
246
+ end
247
+
248
+ @condition.wait(@mutex, next_deadline_wait)
249
+ end
250
+ end
251
+ end
252
+
253
+ def claim_queued(run_id)
254
+ @mutex.synchronize do
255
+ entry = @entries_by_run_id[run_id]
256
+ return [nil, nil] unless entry
257
+ return [nil, nil] if @running_tokens.key?(run_id)
258
+ return [nil, nil] if @active_threads.key?(entry.run.thread_id)
259
+ return [nil, nil] if po_at_capacity?(entry.run)
260
+ return [nil, nil] unless @ready.call(entry.run)
261
+
262
+ @queues[entry.run.thread_id].delete(entry)
263
+ cleanup_queue(entry.run.thread_id)
264
+ token = CancellationToken.new
265
+ @active_threads[entry.run.thread_id] = run_id
266
+ @running_tokens[run_id] = token
267
+ @active_po_counts[entry.run.po_name] += 1
268
+ [entry, token]
269
+ end
270
+ end
271
+
272
+ def runnable_heads
273
+ @queues.filter_map do |thread_id, queue|
274
+ next if queue.empty? || @active_threads.key?(thread_id)
275
+
276
+ head = queue.first
277
+ head if !po_at_capacity?(head.run) && @ready.call(head.run)
278
+ end
279
+ end
280
+
281
+ def expire_queued_entries
282
+ now = Time.now
283
+ expired = @queues.values.flatten.select { |entry| entry.deadline && entry.deadline <= now }
284
+ expired.each do |entry|
285
+ @queues[entry.run.thread_id].delete(entry)
286
+ cleanup_queue(entry.run.thread_id)
287
+ @entries_by_run_id.delete(entry.run.id)
288
+ @cancel_queued&.call(entry.run, "deadline exceeded")
289
+ entry.ticket.resolve(:cancelled)
290
+ end
291
+ end
292
+
293
+ def next_deadline_wait
294
+ deadline = @queues.values.flatten.filter_map(&:deadline).min
295
+ return nil unless deadline
296
+
297
+ [deadline - Time.now, 0].max
298
+ end
299
+
300
+ def execute(entry, token)
301
+ value = @execute.call(entry.run, token, entry.options)
302
+ entry.ticket.resolve(value)
303
+ rescue => e
304
+ entry.ticket.reject(e)
305
+ ensure
306
+ @mutex.synchronize do
307
+ @running_tokens.delete(entry.run.id)
308
+ @active_threads.delete(entry.run.thread_id)
309
+ @active_po_counts[entry.run.po_name] -= 1
310
+ @active_po_counts.delete(entry.run.po_name) unless @active_po_counts[entry.run.po_name].positive?
311
+ @entries_by_run_id.delete(entry.run.id)
312
+ @condition.broadcast
313
+ end
314
+ end
315
+
316
+ def cleanup_queue(thread_id)
317
+ @queues.delete(thread_id) if @queues[thread_id].empty?
318
+ end
319
+
320
+ def po_at_capacity?(run)
321
+ @active_po_counts[run.po_name] >= @max_active_per_po
322
+ end
323
+ end
324
+ end
325
+ end
@@ -12,8 +12,8 @@ module PromptObjects
12
12
  "openai" => {
13
13
  adapter: "OpenAIAdapter",
14
14
  env_key: "OPENAI_API_KEY",
15
- default_model: "gpt-5.2",
16
- models: %w[gpt-5.2 gpt-4.1 gpt-4.1-mini gpt-4.5-preview o3-mini o1]
15
+ default_model: "gpt-5.6-luna",
16
+ models: %w[gpt-5.6-luna gpt-5.6-terra gpt-5.6-sol gpt-5.4-mini gpt-5.4-nano gpt-5.2 gpt-4.1 gpt-4.1-mini]
17
17
  },
18
18
  "anthropic" => {
19
19
  adapter: "AnthropicAdapter",
@@ -24,8 +24,8 @@ module PromptObjects
24
24
  "gemini" => {
25
25
  adapter: "GeminiAdapter",
26
26
  env_key: "GEMINI_API_KEY",
27
- default_model: "gemini-3-flash-preview",
28
- models: %w[gemini-3-flash-preview gemini-2.5-pro gemini-2.5-flash]
27
+ default_model: "gemini-3.5-flash",
28
+ models: %w[gemini-3.5-flash gemini-3.1-flash-lite gemini-3-flash-preview gemini-2.5-pro gemini-2.5-flash]
29
29
  },
30
30
  "ollama" => {
31
31
  adapter: "OpenAIAdapter",
@@ -10,7 +10,7 @@ module PromptObjects
10
10
  # Google Gemini API adapter for LLM calls.
11
11
  # Uses direct HTTP calls to the Gemini REST API.
12
12
  class GeminiAdapter
13
- DEFAULT_MODEL = "gemini-3-flash-preview"
13
+ DEFAULT_MODEL = "gemini-3.5-flash"
14
14
  API_BASE_URL = "https://generativelanguage.googleapis.com/v1beta"
15
15
 
16
16
  def initialize(api_key: nil, model: nil)
@@ -72,14 +72,22 @@ module PromptObjects
72
72
  tc_id = tc.respond_to?(:id) ? tc.id : (tc[:id] || tc["id"])
73
73
  tc_name = tc.respond_to?(:name) ? tc.name : (tc[:name] || tc["name"])
74
74
  tc_args = tc.respond_to?(:arguments) ? tc.arguments : (tc[:arguments] || tc["arguments"] || {})
75
+ tc_metadata = if tc.respond_to?(:provider_metadata)
76
+ tc.provider_metadata
77
+ else
78
+ tc[:provider_metadata] || tc["provider_metadata"] || {}
79
+ end
75
80
 
76
81
  last_tool_calls[tc_id] = tc_name
77
- parts << {
82
+ function_part = {
78
83
  functionCall: {
79
84
  name: tc_name,
80
85
  args: tc_args
81
86
  }
82
87
  }
88
+ thought_signature = tc_metadata[:thought_signature] || tc_metadata["thought_signature"]
89
+ function_part[:thoughtSignature] = thought_signature if thought_signature
90
+ parts << function_part
83
91
  end
84
92
  end
85
93
  result << { role: "model", parts: parts } if parts.any?
@@ -103,15 +111,19 @@ module PromptObjects
103
111
  end
104
112
 
105
113
  def parse_tool_response_content(content)
106
- # Try to parse as JSON, otherwise wrap in a result object
114
+ # Gemini's functionResponse.response must always be a Struct/object.
115
+ # Preserve JSON objects directly and wrap scalars, arrays, and text.
107
116
  if content.is_a?(String)
108
117
  begin
109
- JSON.parse(content)
118
+ parsed = JSON.parse(content)
119
+ parsed.is_a?(Hash) ? parsed : { result: parsed }
110
120
  rescue JSON::ParserError
111
121
  { result: content }
112
122
  end
113
- else
123
+ elsif content.is_a?(Hash)
114
124
  content
125
+ else
126
+ { result: content }
115
127
  end
116
128
  end
117
129
 
@@ -188,7 +200,7 @@ module PromptObjects
188
200
  if part["text"]
189
201
  text_content += part["text"]
190
202
  elsif part["functionCall"]
191
- tool_calls << parse_function_call(part["functionCall"])
203
+ tool_calls << parse_function_call(part)
192
204
  end
193
205
  end
194
206
 
@@ -207,13 +219,18 @@ module PromptObjects
207
219
  }
208
220
  end
209
221
 
210
- def parse_function_call(fc)
222
+ def parse_function_call(part)
223
+ fc = part["functionCall"]
224
+ provider_metadata = {}
225
+ provider_metadata[:thought_signature] = part["thoughtSignature"] if part["thoughtSignature"]
226
+
211
227
  # Gemini doesn't use tool_call_id like OpenAI, so we generate one
212
228
  # based on the function name and a random suffix
213
229
  ToolCall.new(
214
230
  id: "call_#{fc['name']}_#{SecureRandom.hex(8)}",
215
231
  name: fc["name"],
216
- arguments: fc["args"] || {}
232
+ arguments: fc["args"] || {},
233
+ provider_metadata: provider_metadata
217
234
  )
218
235
  end
219
236
  end
@@ -4,7 +4,7 @@ module PromptObjects
4
4
  module LLM
5
5
  # OpenAI API adapter for LLM calls.
6
6
  class OpenAIAdapter
7
- DEFAULT_MODEL = "gpt-5.2"
7
+ DEFAULT_MODEL = "gpt-5.6-luna"
8
8
 
9
9
  def initialize(api_key: nil, model: nil, base_url: nil, extra_headers: nil, provider_name: nil)
10
10
  @api_key = api_key || ENV.fetch("OPENAI_API_KEY") do
@@ -34,6 +34,10 @@ module PromptObjects
34
34
  if tools.any?
35
35
  params[:tools] = tools
36
36
  params[:tool_choice] = "auto"
37
+ # GPT-5.6 supports function tools through Chat Completions only when
38
+ # reasoning is disabled. Other OpenAI-compatible providers may not
39
+ # recognize this parameter, so keep the compatibility fix narrow.
40
+ params[:reasoning_effort] = "none" if openai_gpt_5_6?
37
41
  end
38
42
 
39
43
  raw_response = @client.chat(parameters: params)
@@ -64,6 +68,10 @@ module PromptObjects
64
68
 
65
69
  private
66
70
 
71
+ def openai_gpt_5_6?
72
+ @provider_name == "openai" && @model.start_with?("gpt-5.6")
73
+ end
74
+
67
75
  def build_messages(system, messages)
68
76
  result = [{ role: "system", content: system }]
69
77
 
@@ -8,6 +8,11 @@ module PromptObjects
8
8
  class Pricing
9
9
  RATES = {
10
10
  # OpenAI
11
+ "gpt-5.6-sol" => { input: 5.00, output: 30.00 },
12
+ "gpt-5.6-terra" => { input: 2.50, output: 15.00 },
13
+ "gpt-5.6-luna" => { input: 1.00, output: 6.00 },
14
+ "gpt-5.4-mini" => { input: 0.75, output: 4.50 },
15
+ "gpt-5.4-nano" => { input: 0.20, output: 1.25 },
11
16
  "gpt-5.2" => { input: 2.00, output: 8.00 },
12
17
  "gpt-4.1" => { input: 2.00, output: 8.00 },
13
18
  "gpt-4.1-mini" => { input: 0.40, output: 1.60 },
@@ -19,9 +24,11 @@ module PromptObjects
19
24
  "claude-sonnet-4-5" => { input: 3.00, output: 15.00 },
20
25
  "claude-haiku-4-5" => { input: 1.00, output: 5.00 },
21
26
  # Gemini
22
- "gemini-3-flash-preview" => { input: 0.15, output: 0.60 },
27
+ "gemini-3.5-flash" => { input: 1.50, output: 9.00 },
28
+ "gemini-3.1-flash-lite" => { input: 0.25, output: 1.50 },
29
+ "gemini-3-flash-preview" => { input: 0.50, output: 3.00 },
23
30
  "gemini-2.5-pro" => { input: 1.25, output: 10.00 },
24
- "gemini-2.5-flash" => { input: 0.15, output: 0.60 },
31
+ "gemini-2.5-flash" => { input: 0.30, output: 2.50 },
25
32
  }.freeze
26
33
 
27
34
  # Calculate cost in USD for a given usage.
@@ -25,12 +25,13 @@ module PromptObjects
25
25
  # Supports both method access (.id) and hash access ([:id]) for compatibility
26
26
  # with code that may receive either ToolCall objects or Hashes from the DB.
27
27
  class ToolCall
28
- attr_reader :id, :name, :arguments
28
+ attr_reader :id, :name, :arguments, :provider_metadata
29
29
 
30
- def initialize(id:, name:, arguments:)
30
+ def initialize(id:, name:, arguments:, provider_metadata: nil)
31
31
  @id = id
32
32
  @name = name
33
33
  @arguments = arguments
34
+ @provider_metadata = provider_metadata || {}
34
35
  end
35
36
 
36
37
  # Allow hash-style access for compatibility with code expecting Hashes
@@ -39,12 +40,15 @@ module PromptObjects
39
40
  when :id then @id
40
41
  when :name then @name
41
42
  when :arguments then @arguments
43
+ when :provider_metadata then @provider_metadata
42
44
  end
43
45
  end
44
46
 
45
47
  # Convert to a plain Hash (for serialization)
46
48
  def to_h
47
- { id: @id, name: @name, arguments: @arguments }
49
+ result = { id: @id, name: @name, arguments: @arguments }
50
+ result[:provider_metadata] = @provider_metadata unless @provider_metadata.empty?
51
+ result
48
52
  end
49
53
 
50
54
  # Create a ToolCall from a Hash (for deserialization)
@@ -54,7 +58,8 @@ module PromptObjects
54
58
  new(
55
59
  id: hash[:id] || hash["id"],
56
60
  name: hash[:name] || hash["name"],
57
- arguments: hash[:arguments] || hash["arguments"] || {}
61
+ arguments: hash[:arguments] || hash["arguments"] || {},
62
+ provider_metadata: hash[:provider_metadata] || hash["provider_metadata"] || {}
58
63
  )
59
64
  end
60
65
  end
@@ -11,15 +11,42 @@ module PromptObjects
11
11
  raise Error, "File not found: #{path}" unless File.exist?(path)
12
12
 
13
13
  content = File.read(path, encoding: "UTF-8")
14
+ parse(content, path: path)
15
+ end
16
+
17
+ # Parse prompt object markdown from a raw string (frontmatter + body).
18
+ # Raises if the YAML frontmatter is malformed, so callers can validate
19
+ # before persisting (see PromptObject#update_source).
20
+ # @param content [String] Raw .md content
21
+ # @param path [String, nil] Source path to record in the result
22
+ # @return [Hash] Parsed data with :config, :body, and :path
23
+ def self.parse(content, path: nil)
14
24
  parsed = FrontMatterParser::Parser.new(:md).call(content)
25
+ config = parsed.front_matter || {}
26
+
27
+ # Merge simplified frontmatter (delegates_to + tools) into capabilities.
28
+ # This lets PO authors distinguish PO references from primitive references
29
+ # while keeping the internal model uniform.
30
+ merge_capabilities(config)
15
31
 
16
32
  {
17
- config: parsed.front_matter || {},
33
+ config: config,
18
34
  body: parsed.content.strip,
19
35
  path: path
20
36
  }
21
37
  end
22
38
 
39
+ # Merge delegates_to + tools into the capabilities array.
40
+ # If both new-style and old-style keys are present, they are combined.
41
+ def self.merge_capabilities(config)
42
+ delegates = config.delete("delegates_to") || []
43
+ tools = config.delete("tools") || []
44
+ return if delegates.empty? && tools.empty?
45
+
46
+ existing = config["capabilities"] || []
47
+ config["capabilities"] = (existing + delegates + tools).uniq
48
+ end
49
+
23
50
  # Load all prompt objects from a directory.
24
51
  # @param dir [String] Directory path
25
52
  # @return [Array<Hash>] Array of parsed prompt objects
@@ -100,9 +100,12 @@ module PromptObjects
100
100
  po = @env.registry.get(po_name)
101
101
  return [{ uri: "po://#{po_name}/conversation", mimeType: "text/plain", text: "PO not found" }] unless po
102
102
 
103
- history = po.history.map do |msg|
104
- { role: msg[:role].to_s, content: msg[:content] }
105
- end
103
+ workspace_id = @env.active_session(initiated_by: "service:mcp")[:id]
104
+ thread = Repositories::ThreadRepository.new(@env.session_store)
105
+ .list(workspace_session_id: workspace_id, po_name: po_name).last
106
+ history = thread ? Repositories::MessageRepository.new(@env.session_store)
107
+ .list(thread_id: thread.id, limit: 10_000)
108
+ .map { |message| { role: message[:role].to_s, content: message[:content] } } : []
106
109
 
107
110
  [{
108
111
  uri: "po://#{po_name}/conversation",
@@ -18,12 +18,16 @@ module PromptObjects
18
18
  limit: {
19
19
  type: "integer",
20
20
  description: "Maximum number of messages to return (default: all)"
21
+ },
22
+ thread_id: {
23
+ type: "string",
24
+ description: "Optional runtime thread ID"
21
25
  }
22
26
  },
23
27
  required: %w[po_name]
24
28
  )
25
29
 
26
- def self.call(po_name:, limit: nil, server_context:)
30
+ def self.call(po_name:, limit: nil, thread_id: nil, server_context:)
27
31
  env = server_context[:env]
28
32
 
29
33
  po = env.registry.get(po_name)
@@ -34,21 +38,21 @@ module PromptObjects
34
38
  }])
35
39
  end
36
40
 
37
- history = po.history.map do |msg|
38
- {
39
- role: msg[:role].to_s,
40
- content: msg[:content],
41
- from: msg[:from],
42
- tool_calls: msg[:tool_calls]&.map { |tc| { name: tc.name, arguments: tc.arguments } }
43
- }.compact
44
- end
45
-
46
- history = history.last(limit) if limit
41
+ workspace_id = env.active_session(initiated_by: "service:mcp")[:id]
42
+ threads = Repositories::ThreadRepository.new(env.session_store)
43
+ .list(workspace_session_id: workspace_id, po_name: po_name)
44
+ thread = thread_id ? threads.find { |candidate| candidate.id == thread_id } : threads.last
45
+ history = thread ? Repositories::MessageRepository.new(env.session_store)
46
+ .list(thread_id: thread.id, limit: limit || 10_000)
47
+ .map do |message|
48
+ { role: message[:role].to_s, content: message[:content], from: message[:from_po] }.compact
49
+ end : []
47
50
 
48
51
  ::MCP::Tool::Response.new([{
49
52
  type: "text",
50
53
  text: JSON.pretty_generate({
51
54
  po_name: po_name,
55
+ thread_id: thread&.id,
52
56
  message_count: history.length,
53
57
  history: history
54
58
  })
@@ -3,10 +3,10 @@
3
3
  module PromptObjects
4
4
  module MCP
5
5
  module Tools
6
- # Get all pending human requests from the queue
6
+ # Get pending capability-approval requests from the queue.
7
7
  class GetPendingRequests < ::MCP::Tool
8
8
  tool_name "get_pending_requests"
9
- description "Get all pending human requests across all prompt objects. These are questions POs have asked that await human response."
9
+ description "Get pending capability-approval requests that await a response."
10
10
 
11
11
  input_schema(
12
12
  type: "object",
@@ -3,10 +3,10 @@
3
3
  module PromptObjects
4
4
  module MCP
5
5
  module Tools
6
- # Respond to a pending human request
6
+ # Respond to a pending capability-approval request.
7
7
  class RespondToRequest < ::MCP::Tool
8
8
  tool_name "respond_to_request"
9
- description "Respond to a pending human request from a prompt object. This unblocks the PO that asked the question."
9
+ description "Respond to a pending capability-approval request."
10
10
 
11
11
  input_schema(
12
12
  type: "object",
@@ -48,7 +48,7 @@ module PromptObjects
48
48
  env.bus.publish(
49
49
  from: "mcp_client",
50
50
  to: request.capability,
51
- message: "[response to ask_human] #{response}"
51
+ message: "[response to pending request] #{response}"
52
52
  )
53
53
 
54
54
  ::MCP::Tool::Response.new([{