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 @@
1
+ *,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:Geist Mono,IBM Plex Mono,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.container{width:100%}@media(min-width:640px){.container{max-width:640px}}@media(min-width:768px){.container{max-width:768px}}@media(min-width:1024px){.container{max-width:1024px}}@media(min-width:1280px){.container{max-width:1280px}}@media(min-width:1536px){.container{max-width:1536px}}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.collapse{visibility:collapse}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{top:0;right:0;bottom:0;left:0}.bottom-0{bottom:0}.bottom-3{bottom:.75rem}.bottom-4{bottom:1rem}.left-0{left:0}.left-2{left:.5rem}.right-0{right:0}.right-3{right:.75rem}.right-4{right:1rem}.top-0{top:0}.top-3{top:.75rem}.top-full{top:100%}.z-10{z-index:10}.z-20{z-index:20}.z-50{z-index:50}.mx-2{margin-left:.5rem;margin-right:.5rem}.my-0\.5{margin-top:.125rem;margin-bottom:.125rem}.my-1{margin-top:.25rem;margin-bottom:.25rem}.my-2{margin-top:.5rem;margin-bottom:.5rem}.my-3{margin-top:.75rem;margin-bottom:.75rem}.mb-0\.5{margin-bottom:.125rem}.mb-1{margin-bottom:.25rem}.mb-1\.5{margin-bottom:.375rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.ml-0\.5{margin-left:.125rem}.ml-1{margin-left:.25rem}.ml-2{margin-left:.5rem}.ml-\[4\.5rem\]{margin-left:4.5rem}.ml-auto{margin-left:auto}.mr-2{margin-right:.5rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-1\.5{margin-top:.375rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-5{margin-top:1.25rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.line-clamp-4{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:4}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.table{display:table}.grid{display:grid}.contents{display:contents}.hidden{display:none}.h-1\.5{height:.375rem}.h-10{height:2.5rem}.h-12{height:3rem}.h-2{height:.5rem}.h-2\.5{height:.625rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-9{height:2.25rem}.h-full{height:100%}.h-screen{height:100vh}.max-h-48{max-height:12rem}.max-h-52{max-height:13rem}.max-h-96{max-height:24rem}.max-h-\[60vh\]{max-height:60vh}.max-h-\[80vh\]{max-height:80vh}.min-h-0{min-height:0px}.min-h-12{min-height:3rem}.min-h-9{min-height:2.25rem}.min-h-\[22rem\]{min-height:22rem}.min-h-\[28rem\]{min-height:28rem}.w-1\.5{width:.375rem}.w-2{width:.5rem}.w-2\.5{width:.625rem}.w-3{width:.75rem}.w-56{width:14rem}.w-6{width:1.5rem}.w-60{width:15rem}.w-64{width:16rem}.w-96{width:24rem}.w-\[400px\]{width:400px}.w-\[420px\]{width:420px}.w-full{width:100%}.min-w-0{min-width:0px}.min-w-10{min-width:2.5rem}.min-w-20{min-width:5rem}.min-w-\[140px\]{min-width:140px}.min-w-\[68rem\]{min-width:68rem}.min-w-\[72rem\]{min-width:72rem}.min-w-full{min-width:100%}.max-w-36{max-width:9rem}.max-w-3xl{max-width:48rem}.max-w-\[60px\]{max-width:60px}.max-w-md{max-width:28rem}.max-w-sm{max-width:24rem}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.rotate-180{--tw-rotate: 180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.cursor-grab{cursor:grab}.cursor-not-allowed{cursor:not-allowed}.cursor-nwse-resize{cursor:nwse-resize}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize-none{resize:none}.\!resize{resize:both!important}.resize{resize:both}.list-inside{list-style-position:inside}.list-decimal{list-style-type:decimal}.list-disc{list-style-type:disc}.auto-rows-\[minmax\(22rem\,auto\)\]{grid-auto-rows:minmax(22rem,auto)}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-\[14rem_minmax\(30rem\,1fr\)_22rem\]{grid-template-columns:14rem minmax(30rem,1fr) 22rem}.grid-cols-\[16rem_minmax\(34rem\,1fr\)_22rem\]{grid-template-columns:16rem minmax(34rem,1fr) 22rem}.grid-cols-\[17rem_minmax\(25rem\,1fr\)_minmax\(20rem\,0\.8fr\)\]{grid-template-columns:17rem minmax(25rem,1fr) minmax(20rem,.8fr)}.grid-cols-\[6rem_1fr\]{grid-template-columns:6rem 1fr}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-center{align-items:center}.items-baseline{align-items:baseline}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-px{gap:1px}.gap-x-2{-moz-column-gap:.5rem;column-gap:.5rem}.gap-y-1\.5{row-gap:.375rem}.space-y-0\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.125rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.125rem * var(--tw-space-y-reverse))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-1\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.375rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.375rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-visible{overflow:visible}.overflow-x-auto{overflow-x:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-b{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.rounded-r{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.rounded-t{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.border{border-width:1px}.border-0{border-width:0px}.border-b{border-bottom-width:1px}.border-b-0{border-bottom-width:0px}.border-l{border-left-width:1px}.border-l-2{border-left-width:2px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-po-accent{--tw-border-opacity: 1;border-color:rgb(212 149 42 / var(--tw-border-opacity, 1))}.border-po-accent-muted{--tw-border-opacity: 1;border-color:rgb(154 109 32 / var(--tw-border-opacity, 1))}.border-po-accent\/40{border-color:#d4952a66}.border-po-border{--tw-border-opacity: 1;border-color:rgb(61 58 55 / var(--tw-border-opacity, 1))}.border-po-border-focus{--tw-border-opacity: 1;border-color:rgb(92 87 82 / var(--tw-border-opacity, 1))}.border-po-error{--tw-border-opacity: 1;border-color:rgb(196 92 74 / var(--tw-border-opacity, 1))}.border-po-error\/40{border-color:#c45c4a66}.border-po-error\/60{border-color:#c45c4a99}.border-po-status-active{--tw-border-opacity: 1;border-color:rgb(212 149 42 / var(--tw-border-opacity, 1))}.border-po-status-calling,.border-po-success{--tw-border-opacity: 1;border-color:rgb(59 154 110 / var(--tw-border-opacity, 1))}.border-po-success\/40{border-color:#3b9a6e66}.border-po-success\/50{border-color:#3b9a6e80}.border-po-warning{--tw-border-opacity: 1;border-color:rgb(212 149 42 / var(--tw-border-opacity, 1))}.border-po-warning\/30{border-color:#d4952a4d}.border-po-warning\/40{border-color:#d4952a66}.border-po-warning\/50{border-color:#d4952a80}.border-transparent{border-color:transparent}.border-violet-400\/50{border-color:#a78bfa80}.bg-\[linear-gradient\(180deg\,rgba\(74\,167\,200\,0\.06\)\,transparent_35\%\)\,\#171513\]{background-color:linear-gradient(180deg,rgba(74,167,200,.06),transparent 35%),#171513}.bg-black\/50{background-color:#00000080}.bg-po-accent{--tw-bg-opacity: 1;background-color:rgb(212 149 42 / var(--tw-bg-opacity, 1))}.bg-po-accent-wash{background-color:#d4952a14}.bg-po-bg{--tw-bg-opacity: 1;background-color:rgb(26 25 24 / var(--tw-bg-opacity, 1))}.bg-po-bg\/50{background-color:#1a191880}.bg-po-bg\/60{background-color:#1a191899}.bg-po-bg\/70{background-color:#1a1918b3}.bg-po-bg\/80{background-color:#1a1918cc}.bg-po-bg\/95{background-color:#1a1918f2}.bg-po-error{--tw-bg-opacity: 1;background-color:rgb(196 92 74 / var(--tw-bg-opacity, 1))}.bg-po-error\/10{background-color:#c45c4a1a}.bg-po-status-active{--tw-bg-opacity: 1;background-color:rgb(212 149 42 / var(--tw-bg-opacity, 1))}.bg-po-status-calling{--tw-bg-opacity: 1;background-color:rgb(59 154 110 / var(--tw-bg-opacity, 1))}.bg-po-status-idle{--tw-bg-opacity: 1;background-color:rgb(120 114 106 / var(--tw-bg-opacity, 1))}.bg-po-success{--tw-bg-opacity: 1;background-color:rgb(59 154 110 / var(--tw-bg-opacity, 1))}.bg-po-surface{--tw-bg-opacity: 1;background-color:rgb(34 33 32 / var(--tw-bg-opacity, 1))}.bg-po-surface-2{--tw-bg-opacity: 1;background-color:rgb(44 42 40 / var(--tw-bg-opacity, 1))}.bg-po-surface-2\/90{background-color:#2c2a28e6}.bg-po-surface\/55{background-color:#2221208c}.bg-po-surface\/60{background-color:#22212099}.bg-po-surface\/70{background-color:#222120b3}.bg-po-surface\/80{background-color:#222120cc}.bg-po-surface\/85{background-color:#222120d9}.bg-po-surface\/90{background-color:#222120e6}.bg-po-surface\/95{background-color:#222120f2}.bg-po-text-ghost{--tw-bg-opacity: 1;background-color:rgb(82 78 72 / var(--tw-bg-opacity, 1))}.bg-po-warning{--tw-bg-opacity: 1;background-color:rgb(212 149 42 / var(--tw-bg-opacity, 1))}.bg-po-warning\/5{background-color:#d4952a0d}.bg-transparent{background-color:transparent}.bg-\[linear-gradient\(135deg\,transparent_46\%\,\#5b5752_47\%\,\#5b5752_54\%\,transparent_55\%\,transparent_66\%\,\#5b5752_67\%\,\#5b5752_74\%\,transparent_75\%\)\]{background-image:linear-gradient(135deg,transparent 46%,#5b5752 47%,#5b5752 54%,transparent 55%,transparent 66%,#5b5752 67%,#5b5752 74%,transparent 75%)}.bg-\[radial-gradient\(circle\,\#2c2a28_1px\,transparent_1px\)\]{background-image:radial-gradient(circle,#2c2a28 1px,transparent 1px)}.bg-gradient-to-b{background-image:linear-gradient(to bottom,var(--tw-gradient-stops))}.from-amber-900{--tw-gradient-from: #78350f var(--tw-gradient-from-position);--tw-gradient-to: rgb(120 53 15 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.to-slate-900{--tw-gradient-to: #0f172a var(--tw-gradient-to-position)}.p-2{padding:.5rem}.p-2\.5{padding:.625rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-3\.5{padding-left:.875rem;padding-right:.875rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.pb-1\.5{padding-bottom:.375rem}.pb-2{padding-bottom:.5rem}.pl-2{padding-left:.5rem}.pl-3{padding-left:.75rem}.pl-4{padding-left:1rem}.pl-8{padding-left:2rem}.pl-\[4\.5rem\]{padding-left:4.5rem}.pt-2{padding-top:.5rem}.text-left{text-align:left}.text-center{text-align:center}.align-text-bottom{vertical-align:text-bottom}.font-mono{font-family:Geist Mono,IBM Plex Mono,monospace}.font-ui{font-family:Geist,system-ui,sans-serif}.text-2xs{font-size:11px;line-height:15px}.text-\[0\.9em\]{font-size:.9em}.text-base{font-size:1rem;line-height:1.5rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.uppercase{text-transform:uppercase}.capitalize{text-transform:capitalize}.normal-case{text-transform:none}.italic{font-style:italic}.leading-relaxed{line-height:1.625}.tracking-wide{letter-spacing:.025em}.tracking-wider{letter-spacing:.05em}.text-po-accent{--tw-text-opacity: 1;color:rgb(212 149 42 / var(--tw-text-opacity, 1))}.text-po-bg{--tw-text-opacity: 1;color:rgb(26 25 24 / var(--tw-text-opacity, 1))}.text-po-error{--tw-text-opacity: 1;color:rgb(196 92 74 / var(--tw-text-opacity, 1))}.text-po-status-active{--tw-text-opacity: 1;color:rgb(212 149 42 / var(--tw-text-opacity, 1))}.text-po-status-calling{--tw-text-opacity: 1;color:rgb(59 154 110 / var(--tw-text-opacity, 1))}.text-po-status-delegated{--tw-text-opacity: 1;color:rgb(90 143 194 / var(--tw-text-opacity, 1))}.text-po-success{--tw-text-opacity: 1;color:rgb(59 154 110 / var(--tw-text-opacity, 1))}.text-po-text-ghost{--tw-text-opacity: 1;color:rgb(82 78 72 / var(--tw-text-opacity, 1))}.text-po-text-primary{--tw-text-opacity: 1;color:rgb(232 226 218 / var(--tw-text-opacity, 1))}.text-po-text-secondary{--tw-text-opacity: 1;color:rgb(168 162 154 / var(--tw-text-opacity, 1))}.text-po-text-tertiary{--tw-text-opacity: 1;color:rgb(120 114 106 / var(--tw-text-opacity, 1))}.text-po-warning{--tw-text-opacity: 1;color:rgb(212 149 42 / var(--tw-text-opacity, 1))}.text-violet-300{--tw-text-opacity: 1;color:rgb(196 181 253 / var(--tw-text-opacity, 1))}.placeholder-po-text-ghost::-moz-placeholder{--tw-placeholder-opacity: 1;color:rgb(82 78 72 / var(--tw-placeholder-opacity, 1))}.placeholder-po-text-ghost::placeholder{--tw-placeholder-opacity: 1;color:rgb(82 78 72 / var(--tw-placeholder-opacity, 1))}.opacity-0{opacity:0}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.opacity-70{opacity:.7}.opacity-80{opacity:.8}.shadow-2xl{--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / .25);--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_0_5px_rgba\(212\,149\,42\,0\.6\)\]{--tw-shadow: 0 0 5px rgba(212,149,42,.6);--tw-shadow-colored: 0 0 5px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_0_5px_rgba\(59\,154\,110\,0\.6\)\]{--tw-shadow: 0 0 5px rgba(59,154,110,.6);--tw-shadow-colored: 0 0 5px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_0_6px_rgba\(212\,149\,42\,0\.7\)\]{--tw-shadow: 0 0 6px rgba(212,149,42,.7);--tw-shadow-colored: 0 0 6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_0_6px_rgba\(59\,154\,110\,0\.7\)\]{--tw-shadow: 0 0 6px rgba(59,154,110,.7);--tw-shadow-colored: 0 0 6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.ring{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-1{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-4{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-po-accent{--tw-ring-opacity: 1;--tw-ring-color: rgb(212 149 42 / var(--tw-ring-opacity, 1))}.ring-po-bg{--tw-ring-opacity: 1;--tw-ring-color: rgb(26 25 24 / var(--tw-ring-opacity, 1))}.ring-po-border{--tw-ring-opacity: 1;--tw-ring-color: rgb(61 58 55 / var(--tw-ring-opacity, 1))}.blur{--tw-blur: blur(8px);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-150{transition-duration:.15s}.\[background-size\:28px_28px\]{background-size:28px 28px}html{color-scheme:dark}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{--tw-bg-opacity: 1;background-color:rgb(26 25 24 / var(--tw-bg-opacity, 1))}::-webkit-scrollbar-thumb{border-radius:9999px;--tw-bg-opacity: 1;background-color:rgb(61 58 55 / var(--tw-bg-opacity, 1))}::-webkit-scrollbar-thumb:hover{--tw-bg-opacity: 1;background-color:rgb(92 87 82 / var(--tw-bg-opacity, 1))}::-moz-selection{background:#d4952a4d}::selection{background:#d4952a4d}.resize-handle{width:.25rem;cursor:col-resize;border-left-width:1px;--tw-border-opacity: 1;border-color:rgb(61 58 55 / var(--tw-border-opacity, 1));transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.resize-handle:hover{background-color:#d4952a4d}.resize-handle:active{background-color:#d4952a80}.resize-handle-h{height:.25rem;cursor:row-resize;border-top-width:1px;--tw-border-opacity: 1;border-color:rgb(61 58 55 / var(--tw-border-opacity, 1));transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.resize-handle-h:hover{background-color:#d4952a4d}.resize-handle-h:active{background-color:#d4952a80}.floor-surface{background-color:#1a1918;background-image:radial-gradient(circle,#2c2a28 1px,transparent 1px);background-size:28px 28px}@keyframes station-enter{0%{opacity:0;scale:.96}to{opacity:1;scale:1}}.station-enter{animation:station-enter .24s cubic-bezier(.16,1,.3,1)}@keyframes drawer-enter{0%{transform:translate(24px);opacity:0}to{transform:translate(0);opacity:1}}.drawer-enter{animation:drawer-enter .18s cubic-bezier(.16,1,.3,1)}@keyframes delegation-dash{to{stroke-dashoffset:-24}}.delegation-edge{animation:delegation-dash .9s linear infinite}@media(prefers-reduced-motion:reduce){.station-enter,.drawer-enter,.delegation-edge{animation:none}}.before\:absolute:before{content:var(--tw-content);position:absolute}.before\:bottom-3:before{content:var(--tw-content);bottom:.75rem}.before\:left-\[0\.72rem\]:before{content:var(--tw-content);left:.72rem}.before\:top-3:before{content:var(--tw-content);top:.75rem}.before\:w-px:before{content:var(--tw-content);width:1px}.before\:bg-po-border:before{content:var(--tw-content);--tw-bg-opacity: 1;background-color:rgb(61 58 55 / var(--tw-bg-opacity, 1))}.first\:mt-0:first-child{margin-top:0}.last\:mb-0:last-child{margin-bottom:0}.last\:border-b-0:last-child{border-bottom-width:0px}.hover\:border-po-accent:hover{--tw-border-opacity: 1;border-color:rgb(212 149 42 / var(--tw-border-opacity, 1))}.hover\:border-po-border-focus:hover{--tw-border-opacity: 1;border-color:rgb(92 87 82 / var(--tw-border-opacity, 1))}.hover\:bg-po-accent-muted:hover{--tw-bg-opacity: 1;background-color:rgb(154 109 32 / var(--tw-bg-opacity, 1))}.hover\:bg-po-surface-2:hover{--tw-bg-opacity: 1;background-color:rgb(44 42 40 / var(--tw-bg-opacity, 1))}.hover\:bg-po-surface-3:hover{--tw-bg-opacity: 1;background-color:rgb(54 52 50 / var(--tw-bg-opacity, 1))}.hover\:text-po-accent:hover{--tw-text-opacity: 1;color:rgb(212 149 42 / var(--tw-text-opacity, 1))}.hover\:text-po-error:hover{--tw-text-opacity: 1;color:rgb(196 92 74 / var(--tw-text-opacity, 1))}.hover\:text-po-text-primary:hover{--tw-text-opacity: 1;color:rgb(232 226 218 / var(--tw-text-opacity, 1))}.hover\:text-po-text-secondary:hover{--tw-text-opacity: 1;color:rgb(168 162 154 / var(--tw-text-opacity, 1))}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-100:hover{opacity:1}.focus\:border-po-accent:focus{--tw-border-opacity: 1;border-color:rgb(212 149 42 / var(--tw-border-opacity, 1))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.active\:cursor-grabbing:active{cursor:grabbing}.disabled\:opacity-30:disabled{opacity:.3}.disabled\:opacity-40:disabled{opacity:.4}.disabled\:opacity-50:disabled{opacity:.5}.group:hover .group-hover\:opacity-100{opacity:1}@media(min-width:640px){.sm\:inline{display:inline}}@media(min-width:1280px){.xl\:col-span-2{grid-column:span 2 / span 2}.xl\:min-h-\[30rem\]{min-height:30rem}.xl\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(min-width:1536px){.\32xl\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}
@@ -9,8 +9,8 @@
9
9
  <link href="https://cdn.jsdelivr.net/npm/geist@1/dist/fonts/geist-sans/style.css" rel="stylesheet" />
10
10
  <link href="https://cdn.jsdelivr.net/npm/geist@1/dist/fonts/geist-mono/style.css" rel="stylesheet" />
11
11
  <title>PromptObjects</title>
12
- <script type="module" crossorigin src="/assets/index-D1myxE0l.js"></script>
13
- <link rel="stylesheet" crossorigin href="/assets/index-DdCcwC-Z.css">
12
+ <script type="module" crossorigin src="/assets/index-BjKlODDD.js"></script>
13
+ <link rel="stylesheet" crossorigin href="/assets/index-w3fpIb5c.css">
14
14
  </head>
15
15
  <body class="bg-po-bg text-po-text-primary font-ui">
16
16
  <div id="root"></div>
@@ -8,51 +8,14 @@ module PromptObjects
8
8
  # Handles WebSocket connections for real-time communication with the frontend.
9
9
  # Subscribes to MessageBus for state updates and handles client messages.
10
10
  #
11
- # == Real-time UI Feedback Pattern ==
12
- #
13
- # IMPORTANT: All user actions must provide immediate visual feedback BEFORE
14
- # async operations complete. This makes the UI feel "alive" and responsive.
15
- #
16
- # When implementing new features, follow this pattern:
17
- #
18
- # 1. SWITCH CONTEXT FIRST
19
- # When creating/switching threads, sessions, etc., send the context switch
20
- # message FIRST so the frontend displays the new context immediately.
21
- # Example: thread_created → frontend switches to new thread
22
- #
23
- # 2. UPDATE NAVIGATION
24
- # Send updated lists (sessions, threads) immediately so sidebars reflect
25
- # the change without waiting for async work to complete.
26
- # Example: po_state with sessions list → ThreadsSidebar shows new thread
27
- #
28
- # 3. SHOW USER INPUT
29
- # Send the user's input back immediately so they see it in the UI.
30
- # Don't wait for the AI to respond before showing what the user typed.
31
- # Example: session_updated with user message → chat shows "You: ..."
32
- #
33
- # 4. SHOW PROGRESS
34
- # Update status indicators during work (thinking, calling_tool, etc.).
35
- # Use streaming for incremental content when supported.
36
- # Example: po_state status "thinking" → spinner/animation shown
37
- #
38
- # 5. CONFIRM COMPLETION
39
- # Send final authoritative state after async work completes.
40
- # Example: session_updated with full messages → final chat state
41
- #
42
- # Message flow for "send message with new thread":
43
- # thread_created → switch to new thread
44
- # po_state (sessions) → update sidebar
45
- # session_updated → show user message
46
- # po_state (thinking) → show progress indicator
47
- # [stream chunks] → incremental AI response
48
- # po_response → complete AI response
49
- # session_updated → final messages
50
- # po_state (idle) → clear progress indicator
11
+ # Runtime snapshots establish authoritative state; versioned events then
12
+ # incrementally update runs, messages, artifacts, and environment data.
51
13
  class WebSocketHandler
52
14
  def initialize(runtime:, connection:, app: nil)
53
15
  @runtime = runtime
54
16
  @connection = connection
55
17
  @app = app
18
+ @write_mutex = Mutex.new
56
19
  @subscribed = false
57
20
  @bus_subscription = nil
58
21
  end
@@ -68,8 +31,10 @@ module PromptObjects
68
31
  # Send a message to this client (public for broadcasting).
69
32
  def send_message(data)
70
33
  json = JSON.generate(data)
71
- @connection.write(json)
72
- @connection.flush
34
+ @write_mutex.synchronize do
35
+ @connection.write(json)
36
+ @connection.flush
37
+ end
73
38
  rescue => e
74
39
  puts "WebSocket write error: #{e.message}" if ENV["DEBUG"]
75
40
  end
@@ -141,7 +106,8 @@ module PromptObjects
141
106
  name: @runtime.name,
142
107
  path: @runtime.env_path,
143
108
  po_count: @runtime.registry.prompt_objects.size,
144
- primitive_count: @runtime.registry.primitives.size
109
+ primitive_count: @runtime.registry.primitives.size,
110
+ runtime_model: @runtime.runtime_model
145
111
  }
146
112
  )
147
113
 
@@ -161,6 +127,13 @@ module PromptObjects
161
127
  )
162
128
  end
163
129
 
130
+ # Autonomous scheduler tasks may have started before this client
131
+ # connected. Replay their latest state so countdowns are correct after
132
+ # page load or reconnect.
133
+ @runtime.autonomous_schedule_states.each_value do |schedule|
134
+ send_message(type: "autonomous_schedule", payload: schedule)
135
+ end
136
+
164
137
  # Send pending human requests (notifications)
165
138
  @runtime.human_queue.all_pending.each do |request|
166
139
  send_message(
@@ -182,6 +155,8 @@ module PromptObjects
182
155
  }
183
156
  )
184
157
  end
158
+
159
+ @runtime.runtime_snapshot_envelopes.each { |envelope| send_message(envelope) }
185
160
  end
186
161
 
187
162
  # === Client Message Handling ===
@@ -203,12 +178,20 @@ module PromptObjects
203
178
  message = JSON.parse(data)
204
179
 
205
180
  case message["type"]
181
+ when "enqueue_run"
182
+ handle_enqueue_run(message["payload"])
183
+ when "cancel_run"
184
+ handle_cancel_run(message["payload"])
185
+ when "create_runtime_thread"
186
+ handle_create_runtime_thread(message["payload"])
187
+ when "rename_runtime_thread"
188
+ handle_rename_runtime_thread(message["payload"])
189
+ when "archive_runtime_thread"
190
+ handle_archive_runtime_thread(message["payload"])
206
191
  when "send_message"
207
192
  handle_send_message(message["payload"])
208
193
  when "respond_to_notification"
209
194
  handle_notification_response(message["payload"])
210
- when "update_po"
211
- handle_update_po(message["payload"])
212
195
  when "create_session"
213
196
  handle_create_session(message["payload"])
214
197
  when "switch_session"
@@ -223,6 +206,8 @@ module PromptObjects
223
206
  handle_switch_llm(message["payload"])
224
207
  when "update_prompt"
225
208
  handle_update_prompt(message["payload"])
209
+ when "update_po_source"
210
+ handle_update_po_source(message["payload"])
226
211
  when "get_session_usage"
227
212
  handle_get_session_usage(message["payload"])
228
213
  when "export_thread"
@@ -240,155 +225,150 @@ module PromptObjects
240
225
  puts e.backtrace.first(5).join("\n") if ENV["DEBUG"]
241
226
  end
242
227
 
243
- def handle_send_message(payload)
244
- po_name = payload["target"]
228
+ def handle_enqueue_run(payload)
229
+ target_po = payload["target_po"]
245
230
  content = payload["content"]
246
- new_thread = payload["new_thread"] # If true, create a new thread first
247
-
248
- po = @runtime.registry.get(po_name)
249
-
250
- unless po.is_a?(PromptObject)
251
- send_error("Unknown prompt object: #{po_name}")
252
- return
253
- end
254
-
255
- # If new_thread requested, create one first
256
- if new_thread
257
- thread_id = po.new_thread
258
- # Notify client of new thread immediately so they see it
259
- send_message(
260
- type: "thread_created",
261
- payload: {
262
- target: po_name,
263
- thread_id: thread_id,
264
- thread_type: "root"
265
- }
266
- )
267
- # Also send updated sessions list so ThreadsSidebar shows it immediately
268
- send_message(
269
- type: "po_state",
270
- payload: {
271
- name: po_name,
272
- state: { sessions: po.list_sessions.map { |s| PromptObject.serialize_session(s) } }
273
- }
274
- )
275
- end
276
-
277
- # Capture the session_id at request start so response goes to correct session
278
- request_session_id = po.session_id
279
-
280
- # Update PO state to working AND send the user message for immediate UI feedback
281
- send_message(
282
- type: "po_state",
283
- payload: { name: po_name, state: { status: "thinking" } }
231
+ client_request_id = payload["client_request_id"]
232
+ raise Error, "client_request_id is required" if client_request_id.to_s.empty?
233
+ raise Error, "content is required" if content.to_s.empty?
234
+
235
+ source = payload["source"] || { "type" => "human" }
236
+ from_source = protocol_source(source)
237
+ run = @runtime.enqueue_run(
238
+ target_po: target_po,
239
+ content: content,
240
+ from_source: from_source,
241
+ workspace_session_id: payload["workspace_session_id"],
242
+ thread_id: payload["thread_id"],
243
+ client_request_id: client_request_id,
244
+ deadline: payload["deadline"]
284
245
  )
285
-
286
- # Send immediate session update showing the user's message
287
- # This gives instant feedback before the AI responds
288
- # Include existing messages so we don't clear the chat when continuing a thread
289
- existing_messages = session_messages(po, request_session_id)
290
- new_user_message = { role: "user", content: content, from: "human" }
291
246
  send_message(
292
- type: "session_updated",
293
- payload: {
294
- target: po_name,
295
- session_id: request_session_id,
296
- messages: existing_messages + [new_user_message]
297
- }
247
+ type: "run_acknowledged",
248
+ payload: serialize_runtime_value(run.to_h)
298
249
  )
299
250
 
300
- # Run in async context
301
251
  Async do
302
- # Use tui_mode: true so ask_human uses HumanQueue (blocking) instead of REPL mode
303
- context = @runtime.context(tui_mode: true)
304
- context.current_capability = "human"
305
-
306
- # Set up callback for real-time tool call updates
307
- # This fires after each tool call iteration in the receive loop
308
- po.on_history_updated = ->(po_obj, session_id, history) {
309
- send_message(
310
- type: "session_updated",
311
- payload: {
312
- target: po_obj.name,
313
- session_id: session_id,
314
- messages: history.map { |m| PromptObject.serialize_message(m) }
315
- }
316
- )
317
- }
318
-
319
252
  begin
320
- response = po.receive(content, context: context)
321
-
322
- # Auto-name the thread if it doesn't have a name yet
323
- auto_name_thread_if_needed(po, request_session_id, content)
253
+ response = execute_blocking { @runtime.execute_run(run.id, tui_mode: false) }
254
+ next if response == :queued
324
255
 
325
- # Send the complete response with session_id for correct routing
326
256
  send_message(
327
257
  type: "po_response",
328
- payload: {
329
- target: po_name,
330
- session_id: request_session_id,
331
- content: response
332
- }
333
- )
334
-
335
- # Send session update for the session where messages were added
336
- # This ensures the correct session is updated even if user switched
337
- if request_session_id
338
- send_message(
339
- type: "session_updated",
340
- payload: {
341
- target: po_name,
342
- session_id: request_session_id,
343
- messages: session_messages(po, request_session_id)
344
- }
345
- )
346
- end
347
-
348
- # Also send updated sessions list (for thread panel)
349
- send_message(
350
- type: "po_state",
351
- payload: {
352
- name: po_name,
353
- state: { sessions: po.list_sessions.map { |s| PromptObject.serialize_session(s) } }
354
- }
258
+ payload: { target: target_po, thread_id: run.thread_id, content: response }
355
259
  )
356
260
  rescue => e
357
- send_error("Error from #{po_name}: #{e.message}")
358
-
359
- # Broadcast rich error context for diagnostics
360
- llm_config = @runtime.llm_config
261
+ send_error("Error from #{target_po}: #{e.message}")
361
262
  send_message(
362
263
  type: "llm_error",
363
264
  payload: {
364
- po_name: po_name,
365
- provider: llm_config[:provider],
366
- model: llm_config[:model],
265
+ po_name: target_po,
266
+ provider: run.provider,
267
+ model: run.model,
367
268
  error: e.message,
368
269
  error_class: e.class.name
369
270
  }
370
271
  )
371
- ensure
372
- # Clean up the callback
373
- po.on_history_updated = nil
374
-
375
- # Update PO state back to idle (status only, not session data)
376
- send_message(
377
- type: "po_state",
378
- payload: { name: po_name, state: { status: "idle" } }
379
- )
380
272
  end
381
273
  end
382
274
  end
383
275
 
384
- # Auto-name a thread based on the user's first message if it doesn't have a name
385
- def auto_name_thread_if_needed(po, session_id, user_message)
386
- return unless @runtime.session_store && session_id
276
+ def handle_cancel_run(payload)
277
+ run_id = payload["run_id"]
278
+ raise Error, "run_id is required" if run_id.to_s.empty?
279
+
280
+ cancelled = @runtime.cancel_run(run_id, reason: payload["reason"] || "client request")
281
+ send_message(
282
+ type: "run_cancellation_acknowledged",
283
+ payload: { run_id: run_id, cancelled: cancelled }
284
+ )
285
+ end
286
+
287
+ def handle_create_runtime_thread(payload)
288
+ thread = @runtime.create_runtime_thread(
289
+ workspace_session_id: payload["workspace_session_id"],
290
+ po_name: payload["po_name"],
291
+ thread_type: payload["thread_type"] || "human",
292
+ name: payload["name"]
293
+ )
294
+ send_message(
295
+ type: "runtime_thread_created",
296
+ payload: serialize_runtime_value(thread.to_h)
297
+ )
298
+ end
299
+
300
+ def handle_rename_runtime_thread(payload)
301
+ thread = @runtime.rename_runtime_thread(payload["thread_id"], name: payload["name"])
302
+ send_message(
303
+ type: "runtime_thread_renamed",
304
+ payload: serialize_runtime_value(thread.to_h)
305
+ )
306
+ end
307
+
308
+ def handle_archive_runtime_thread(payload)
309
+ thread = @runtime.archive_runtime_thread(payload["thread_id"])
310
+ send_message(
311
+ type: "runtime_thread_archived",
312
+ payload: serialize_runtime_value(thread.to_h)
313
+ )
314
+ end
315
+
316
+ def handle_send_message(payload)
317
+ po_name = payload["target"]
318
+ content = payload["content"]
319
+ new_thread = payload["new_thread"] # If true, create a new thread first
320
+
321
+ po = @runtime.registry.get(po_name)
322
+
323
+ unless po.is_a?(PromptObject)
324
+ send_error("Unknown prompt object: #{po_name}")
325
+ return
326
+ end
327
+
328
+ workspace_id = payload["workspace_session_id"] || @runtime.active_session(initiated_by: "human")[:id]
329
+ thread_id = payload["thread_id"] || payload["session_id"]
330
+ if new_thread
331
+ thread = @runtime.create_runtime_thread(
332
+ workspace_session_id: workspace_id,
333
+ po_name: po_name,
334
+ thread_type: "human",
335
+ name: payload["name"]
336
+ )
337
+ thread_id = thread.id
338
+ send_message(type: "runtime_thread_created", payload: serialize_runtime_value(thread.to_h))
339
+ end
340
+ handle_enqueue_run(
341
+ "target_po" => po_name,
342
+ "content" => content,
343
+ "client_request_id" => payload["client_request_id"] || SecureRandom.uuid,
344
+ "workspace_session_id" => workspace_id,
345
+ "thread_id" => thread_id,
346
+ "source" => { "type" => "human" }
347
+ )
348
+ end
349
+
350
+ def execute_blocking(&block)
351
+ return @app.blocking_executor.call(&block) if @app&.respond_to?(:blocking_executor)
352
+
353
+ block.call
354
+ end
355
+
356
+ def protocol_source(source)
357
+ type = source.fetch("type", "human")
358
+ name = source["name"]
359
+ return type if %w[human api].include?(type)
360
+ return "#{type}:#{name}" if %w[timer artifact service].include?(type) && !name.to_s.empty?
387
361
 
388
- session = @runtime.session_store.get_session(session_id)
389
- return if session.nil? || session[:name] # Already has a name
362
+ raise Error, "Unsupported run source: #{type}"
363
+ end
390
364
 
391
- @runtime.session_store.auto_name_thread(session_id, user_message)
365
+ def serialize_runtime_value(value)
366
+ case value
367
+ when Time then value.iso8601
368
+ when Hash then value.to_h { |key, nested| [key, serialize_runtime_value(nested)] }
369
+ when Array then value.map { |nested| serialize_runtime_value(nested) }
370
+ else value
371
+ end
392
372
  end
393
373
 
394
374
  def handle_notification_response(payload)
@@ -403,18 +383,39 @@ module PromptObjects
403
383
  )
404
384
  end
405
385
 
406
- def handle_update_po(payload)
407
- po_name = payload["name"]
408
- updates = payload["updates"]
386
+ # Replace a PO's entire .md source (frontmatter + body) from the editor.
387
+ # Re-parses in place so autonomous/delegates_to/tools changes take effect,
388
+ # and re-keys the registry if the frontmatter name changed.
389
+ def handle_update_po_source(payload)
390
+ po_name = payload["target"]
391
+ source = payload["source"]
409
392
 
410
393
  po = @runtime.registry.get(po_name)
411
394
  return send_error("Unknown prompt object: #{po_name}") unless po.is_a?(PromptObject)
395
+ return send_error("#{po_name} has no source file to edit") unless po.path
396
+
397
+ old_name = po.name
398
+ if po.update_source(source)
399
+ if po.name != old_name
400
+ @runtime.registry.unregister(old_name)
401
+ @runtime.registry.register(po)
402
+
403
+ # Keep the filename in sync with the new name so the file and the
404
+ # registered PO don't diverge (update_source wrote to the old path).
405
+ new_path = File.join(File.dirname(po.path), "#{po.name}.md")
406
+ po.relocate(new_path)
407
+ end
412
408
 
413
- # TODO: Implement PO updates (capabilities, etc.)
414
- send_message(
415
- type: "po_updated",
416
- payload: { name: po_name }
417
- )
409
+ # Broadcasts full PO state (incl. the re-read source) to all clients.
410
+ @runtime.notify_po_modified(po)
411
+
412
+ send_message(
413
+ type: "po_source_updated",
414
+ payload: { target: po.name, previous_name: old_name, success: true }
415
+ )
416
+ else
417
+ send_error("Failed to update #{po_name} — check the YAML frontmatter")
418
+ end
418
419
  end
419
420
 
420
421
  def handle_create_session(payload)
@@ -424,22 +425,25 @@ module PromptObjects
424
425
  po = @runtime.registry.get(po_name)
425
426
  return send_error("Unknown prompt object: #{po_name}") unless po.is_a?(PromptObject)
426
427
 
427
- session_id = po.new_session(name: session_name)
428
+ workspace_id = payload["workspace_session_id"] || @runtime.active_session(initiated_by: "human")[:id]
429
+ thread = @runtime.create_runtime_thread(
430
+ workspace_session_id: workspace_id,
431
+ po_name: po_name,
432
+ thread_type: "human",
433
+ name: session_name
434
+ )
428
435
 
429
436
  send_message(
430
437
  type: "session_created",
431
438
  payload: {
432
439
  target: po_name,
433
- session_id: session_id,
440
+ session_id: thread.id,
441
+ thread_id: thread.id,
442
+ workspace_session_id: thread.workspace_session_id,
434
443
  name: session_name
435
444
  }
436
445
  )
437
-
438
- # Also send updated PO state
439
- send_message(
440
- type: "po_state",
441
- payload: { name: po_name, state: po.to_state_hash(registry: @runtime.registry) }
442
- )
446
+ send_message(type: "runtime_thread_created", payload: serialize_runtime_value(thread.to_h))
443
447
  end
444
448
 
445
449
  def handle_switch_session(payload)
@@ -449,17 +453,13 @@ module PromptObjects
449
453
  po = @runtime.registry.get(po_name)
450
454
  return send_error("Unknown prompt object: #{po_name}") unless po.is_a?(PromptObject)
451
455
 
452
- if po.switch_session(session_id)
456
+ thread = Repositories::ThreadRepository.new(@runtime.session_store).get(session_id)
457
+ if thread && thread.po_name == po_name
453
458
  send_message(
454
459
  type: "session_switched",
455
460
  payload: { target: po_name, session_id: session_id }
456
461
  )
457
462
 
458
- # Send updated PO state with new session's messages
459
- send_message(
460
- type: "po_state",
461
- payload: { name: po_name, state: po.to_state_hash(registry: @runtime.registry) }
462
- )
463
463
  else
464
464
  send_error("Could not switch to session: #{session_id}")
465
465
  end
@@ -473,22 +473,17 @@ module PromptObjects
473
473
  po = @runtime.registry.get(po_name)
474
474
  return send_error("Unknown prompt object: #{po_name}") unless po.is_a?(PromptObject)
475
475
 
476
- thread_id = po.new_thread(name: thread_name)
477
-
478
- send_message(
479
- type: "thread_created",
480
- payload: {
481
- target: po_name,
482
- thread_id: thread_id,
483
- name: thread_name,
484
- thread_type: thread_type
485
- }
476
+ workspace_id = payload["workspace_session_id"] || @runtime.active_session(initiated_by: "human")[:id]
477
+ thread = @runtime.create_runtime_thread(
478
+ workspace_session_id: workspace_id,
479
+ po_name: po_name,
480
+ thread_type: thread_type == "root" ? "human" : thread_type,
481
+ name: thread_name
486
482
  )
487
483
 
488
- # Also send updated PO state
489
484
  send_message(
490
- type: "po_state",
491
- payload: { name: po_name, state: po.to_state_hash(registry: @runtime.registry) }
485
+ type: "runtime_thread_created",
486
+ payload: serialize_runtime_value(thread.to_h)
492
487
  )
493
488
  end
494
489
 
@@ -666,9 +661,9 @@ module PromptObjects
666
661
  def request_to_hash(request)
667
662
  {
668
663
  id: request.id,
669
- po_name: request.capability, # capability is the PO name
670
- type: "ask_human", # hardcode type since it's always ask_human
671
- message: request.question, # question is the message
664
+ po_name: request.capability,
665
+ type: request.capability,
666
+ message: request.question,
672
667
  options: request.options || []
673
668
  }
674
669
  end