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
@@ -1,559 +0,0 @@
1
- import { useEffect, useRef, useCallback } from 'react'
2
- import { useStore } from '../store'
3
- import type {
4
- WSMessage,
5
- PromptObject,
6
- BusMessage,
7
- Notification,
8
- Environment,
9
- Message,
10
- LLMConfig,
11
- EnvDataEntry,
12
- SendMessagePayload,
13
- RespondToNotificationPayload,
14
- CreateSessionPayload,
15
- SwitchSessionPayload,
16
- CreateThreadPayload,
17
- ThreadType,
18
- } from '../types'
19
-
20
- export function useWebSocket() {
21
- const ws = useRef<WebSocket | null>(null)
22
- const reconnectTimeout = useRef<number | null>(null)
23
- const disposed = useRef(false)
24
-
25
- // Use a ref for the message handler so the WS always calls the latest version,
26
- // regardless of when `connect()` was created or how many reconnections happened.
27
- const handleMessageRef = useRef<(message: WSMessage) => void>(() => {})
28
-
29
- const {
30
- setConnected,
31
- resetOnDisconnect,
32
- setEnvironment,
33
- setPromptObject,
34
- removePromptObject,
35
- updateSessionMessages,
36
- switchPOSession,
37
- addBusMessage,
38
- addNotification,
39
- removeNotification,
40
- appendStreamChunk,
41
- clearStream,
42
- setPendingResponse,
43
- clearPendingResponse,
44
- setLLMConfig,
45
- updateCurrentLLM,
46
- setUsageData,
47
- setEnvData,
48
- setSessionRoot,
49
- } = useStore()
50
-
51
- // Keep the handler ref up to date every render
52
- handleMessageRef.current = (message: WSMessage) => {
53
- switch (message.type) {
54
- case 'environment':
55
- setEnvironment(message.payload as Environment)
56
- break
57
-
58
- case 'po_state': {
59
- const { name, state } = message.payload as {
60
- name: string
61
- state: Partial<PromptObject>
62
- }
63
- setPromptObject(name, state)
64
- break
65
- }
66
-
67
- case 'po_response': {
68
- const { target, content } = message.payload as {
69
- target: string
70
- content: string
71
- }
72
- setPendingResponse(target, content)
73
- // Clear after a short delay to allow UI to update
74
- setTimeout(() => clearPendingResponse(target), 100)
75
- break
76
- }
77
-
78
- case 'stream': {
79
- const { target, chunk } = message.payload as {
80
- target: string
81
- chunk: string
82
- }
83
- appendStreamChunk(target, chunk)
84
- break
85
- }
86
-
87
- case 'stream_end': {
88
- const { target } = message.payload as { target: string }
89
- clearStream(target)
90
- break
91
- }
92
-
93
- case 'bus_message':
94
- addBusMessage(message.payload as BusMessage)
95
- break
96
-
97
- case 'notification':
98
- addNotification(message.payload as Notification)
99
- break
100
-
101
- case 'notification_resolved': {
102
- const { id } = message.payload as { id: string }
103
- removeNotification(id)
104
- break
105
- }
106
-
107
- // Live file updates
108
- case 'po_added': {
109
- const { name, state } = message.payload as {
110
- name: string
111
- state: Partial<PromptObject>
112
- }
113
- console.log('PO added:', name)
114
- setPromptObject(name, state)
115
- break
116
- }
117
-
118
- case 'po_modified': {
119
- const { name, state } = message.payload as {
120
- name: string
121
- state: Partial<PromptObject>
122
- }
123
- console.log('PO modified:', name)
124
- setPromptObject(name, state)
125
- break
126
- }
127
-
128
- case 'po_removed': {
129
- const { name } = message.payload as { name: string }
130
- console.log('PO removed:', name)
131
- removePromptObject(name)
132
- break
133
- }
134
-
135
- case 'po_delegation_started': {
136
- const { target, caller } = message.payload as {
137
- target: string
138
- caller: string
139
- thread_id: string
140
- tool_call_id: string
141
- }
142
- // Mark the target PO as delegated — it's now working on behalf of caller
143
- setPromptObject(target, { status: 'thinking', delegated_by: caller })
144
- break
145
- }
146
-
147
- case 'po_delegation_completed': {
148
- const { target } = message.payload as {
149
- target: string
150
- caller: string
151
- thread_id: string
152
- tool_call_id: string
153
- }
154
- // Delegation finished — target PO returns to idle
155
- setPromptObject(target, { status: 'idle', delegated_by: null })
156
- break
157
- }
158
-
159
- case 'session_updated': {
160
- const { target, session_id, messages } = message.payload as {
161
- target: string
162
- session_id: string
163
- messages: Message[]
164
- }
165
- updateSessionMessages(target, session_id, messages)
166
- break
167
- }
168
-
169
- case 'thread_created': {
170
- const { target, thread_id, thread_type } = message.payload as {
171
- target: string
172
- thread_id: string
173
- name: string | null
174
- thread_type: ThreadType
175
- }
176
- console.log('Thread created:', target, thread_id, thread_type)
177
- // IMMEDIATELY switch to the new thread so user sees their message
178
- // This ensures session_updated messages for this thread are displayed
179
- switchPOSession(target, thread_id)
180
- break
181
- }
182
-
183
- case 'thread_tree': {
184
- // Thread tree response - could be used for navigation
185
- console.log('Thread tree received:', message.payload)
186
- break
187
- }
188
-
189
- case 'llm_config':
190
- setLLMConfig(message.payload as LLMConfig)
191
- break
192
-
193
- case 'llm_switched': {
194
- const { provider, model } = message.payload as {
195
- provider: string
196
- model: string
197
- }
198
- updateCurrentLLM(provider, model)
199
- break
200
- }
201
-
202
- case 'session_usage': {
203
- setUsageData(message.payload as Record<string, unknown>)
204
- break
205
- }
206
-
207
- case 'thread_export': {
208
- const { content, format, session_id } = message.payload as { content: string; format: string; session_id: string }
209
- const mimeType = format === 'json' ? 'application/json' : 'text/markdown'
210
- const ext = format === 'json' ? 'json' : 'md'
211
- const blob = new Blob([content], { type: mimeType })
212
- const url = URL.createObjectURL(blob)
213
- const a = document.createElement('a')
214
- a.href = url
215
- a.download = `${session_id}.${ext}`
216
- a.click()
217
- URL.revokeObjectURL(url)
218
- break
219
- }
220
-
221
- case 'prompt_updated': {
222
- const { target, success } = message.payload as { target: string; success: boolean }
223
- if (!success) console.warn('Prompt update failed for:', target)
224
- break
225
- }
226
-
227
- case 'llm_error': {
228
- const { po_name, error, provider, model } = message.payload as {
229
- po_name: string
230
- provider: string
231
- model: string
232
- error: string
233
- error_class: string
234
- }
235
- console.error(`LLM error for ${po_name} (${provider}/${model}):`, error)
236
- // Reset PO to idle so UI isn't stuck in "thinking" state
237
- setPromptObject(po_name, { status: 'idle' })
238
- break
239
- }
240
-
241
- case 'session_created':
242
- // Session creation confirmed — po_state update follows with full state
243
- break
244
-
245
- case 'session_switched':
246
- // Session switch confirmed — po_state update follows with full state
247
- break
248
-
249
- case 'env_data_changed': {
250
- const { root_thread_id, entries } = message.payload as {
251
- action: string
252
- root_thread_id: string
253
- key: string
254
- stored_by: string
255
- entries: EnvDataEntry[]
256
- }
257
- setEnvData(root_thread_id, entries)
258
- break
259
- }
260
-
261
- case 'env_data_list': {
262
- const { session_id, root_thread_id, entries } = message.payload as {
263
- session_id: string
264
- root_thread_id: string
265
- entries: EnvDataEntry[]
266
- }
267
- setEnvData(root_thread_id, entries)
268
- setSessionRoot(session_id, root_thread_id)
269
- break
270
- }
271
-
272
- case 'error': {
273
- const { message: errorMsg } = message.payload as { message: string }
274
- console.error('Server error:', errorMsg)
275
- break
276
- }
277
-
278
- case 'pong':
279
- // Heartbeat response, ignore
280
- break
281
-
282
- default:
283
- console.log('Unknown message type:', message.type)
284
- }
285
- }
286
-
287
- const connect = useCallback(() => {
288
- // Don't reconnect if we've been disposed (component unmounted)
289
- if (disposed.current) return
290
-
291
- // Close any existing connection before creating a new one
292
- // to prevent duplicate connections racing each other
293
- if (ws.current) {
294
- // Remove handlers first so the close doesn't trigger another reconnect
295
- ws.current.onclose = null
296
- ws.current.onerror = null
297
- ws.current.onmessage = null
298
- ws.current.onopen = null
299
- if (ws.current.readyState === WebSocket.OPEN || ws.current.readyState === WebSocket.CONNECTING) {
300
- ws.current.close()
301
- }
302
- ws.current = null
303
- }
304
-
305
- // Determine WebSocket URL
306
- // In dev mode (Vite on 5173), connect directly to Ruby server on 3000
307
- // In production, connect to same host
308
- const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
309
- const isDev = window.location.port === '5173'
310
- const host = isDev ? 'localhost:3000' : window.location.host
311
- const wsUrl = `${protocol}//${host}`
312
-
313
- console.log('Connecting to WebSocket:', wsUrl)
314
- const socket = new WebSocket(wsUrl)
315
- ws.current = socket
316
-
317
- socket.onopen = () => {
318
- // Guard against stale handler from a replaced socket
319
- if (ws.current !== socket) return
320
- console.log('WebSocket connected')
321
- setConnected(true)
322
-
323
- // Clear any pending reconnect
324
- if (reconnectTimeout.current) {
325
- clearTimeout(reconnectTimeout.current)
326
- reconnectTimeout.current = null
327
- }
328
- }
329
-
330
- socket.onclose = () => {
331
- // Guard: if this socket was already replaced, ignore its close event.
332
- // This prevents zombie onclose handlers from triggering reconnects
333
- // after a fresh connection has already been established.
334
- if (ws.current !== socket) return
335
- if (disposed.current) return
336
-
337
- console.log('WebSocket disconnected')
338
- // Reset PO statuses to idle and clear broken streams —
339
- // prevents locked chat input and stale streaming indicators
340
- resetOnDisconnect()
341
-
342
- // Attempt to reconnect after 2 seconds
343
- if (reconnectTimeout.current) {
344
- clearTimeout(reconnectTimeout.current)
345
- }
346
- reconnectTimeout.current = window.setTimeout(() => {
347
- console.log('Attempting to reconnect...')
348
- connect()
349
- }, 2000)
350
- }
351
-
352
- socket.onerror = (error) => {
353
- if (ws.current !== socket) return
354
- console.error('WebSocket error:', error)
355
- }
356
-
357
- socket.onmessage = (event) => {
358
- if (ws.current !== socket) return
359
- try {
360
- const message: WSMessage = JSON.parse(event.data)
361
- // Always call the latest handler via ref — no stale closures
362
- handleMessageRef.current(message)
363
- } catch (error) {
364
- console.error('Failed to parse WebSocket message:', error)
365
- }
366
- }
367
- }, [setConnected, resetOnDisconnect])
368
-
369
- // Connect on mount, clean up on unmount
370
- useEffect(() => {
371
- disposed.current = false
372
- connect()
373
-
374
- return () => {
375
- disposed.current = true
376
- if (reconnectTimeout.current) {
377
- clearTimeout(reconnectTimeout.current)
378
- reconnectTimeout.current = null
379
- }
380
- if (ws.current) {
381
- // Remove handlers before closing to prevent zombie events
382
- ws.current.onclose = null
383
- ws.current.onerror = null
384
- ws.current.onmessage = null
385
- ws.current.onopen = null
386
- ws.current.close()
387
- ws.current = null
388
- }
389
- }
390
- }, [connect])
391
-
392
- // Send message to a PO
393
- const sendMessage = useCallback((target: string, content: string, newThread?: boolean) => {
394
- if (!ws.current || ws.current.readyState !== WebSocket.OPEN) {
395
- console.error('WebSocket not connected')
396
- return
397
- }
398
-
399
- const payload: SendMessagePayload = { target, content, new_thread: newThread }
400
- ws.current.send(
401
- JSON.stringify({
402
- type: 'send_message',
403
- payload,
404
- })
405
- )
406
- }, [])
407
-
408
- // Respond to a notification
409
- const respondToNotification = useCallback((id: string, response: string) => {
410
- if (!ws.current || ws.current.readyState !== WebSocket.OPEN) {
411
- console.error('WebSocket not connected')
412
- return
413
- }
414
-
415
- const payload: RespondToNotificationPayload = { id, response }
416
- ws.current.send(
417
- JSON.stringify({
418
- type: 'respond_to_notification',
419
- payload,
420
- })
421
- )
422
- }, [])
423
-
424
- // Create a new session
425
- const createSession = useCallback((target: string, name?: string) => {
426
- if (!ws.current || ws.current.readyState !== WebSocket.OPEN) {
427
- console.error('WebSocket not connected')
428
- return
429
- }
430
-
431
- const payload: CreateSessionPayload = { target, name }
432
- ws.current.send(
433
- JSON.stringify({
434
- type: 'create_session',
435
- payload,
436
- })
437
- )
438
- }, [])
439
-
440
- // Switch to a different session
441
- const switchSession = useCallback((target: string, sessionId: string) => {
442
- if (!ws.current || ws.current.readyState !== WebSocket.OPEN) {
443
- console.error('WebSocket not connected')
444
- return
445
- }
446
-
447
- const payload: SwitchSessionPayload = { target, session_id: sessionId }
448
- ws.current.send(
449
- JSON.stringify({
450
- type: 'switch_session',
451
- payload,
452
- })
453
- )
454
- }, [])
455
-
456
- // Switch LLM provider/model
457
- const switchLLM = useCallback((provider: string, model?: string) => {
458
- if (!ws.current || ws.current.readyState !== WebSocket.OPEN) {
459
- console.error('WebSocket not connected')
460
- return
461
- }
462
-
463
- ws.current.send(
464
- JSON.stringify({
465
- type: 'switch_llm',
466
- payload: { provider, model },
467
- })
468
- )
469
- }, [])
470
-
471
- // Create a new thread (defaults to root thread)
472
- const createThread = useCallback((target: string, name?: string, threadType?: ThreadType) => {
473
- if (!ws.current || ws.current.readyState !== WebSocket.OPEN) {
474
- console.error('WebSocket not connected')
475
- return
476
- }
477
-
478
- const payload: CreateThreadPayload = { target, name, thread_type: threadType }
479
- ws.current.send(
480
- JSON.stringify({
481
- type: 'create_thread',
482
- payload,
483
- })
484
- )
485
- }, [])
486
-
487
- // Request usage data for a session
488
- const requestUsage = useCallback((sessionId: string, includeTree?: boolean) => {
489
- if (!ws.current || ws.current.readyState !== WebSocket.OPEN) {
490
- console.error('WebSocket not connected')
491
- return
492
- }
493
-
494
- ws.current.send(
495
- JSON.stringify({
496
- type: 'get_session_usage',
497
- payload: { session_id: sessionId, include_tree: includeTree || false },
498
- })
499
- )
500
- }, [])
501
-
502
- // Export a thread as markdown or JSON
503
- const exportThread = useCallback((sessionId: string, format?: string) => {
504
- if (!ws.current || ws.current.readyState !== WebSocket.OPEN) {
505
- console.error('WebSocket not connected')
506
- return
507
- }
508
-
509
- ws.current.send(
510
- JSON.stringify({
511
- type: 'export_thread',
512
- payload: { session_id: sessionId, format: format || 'markdown' },
513
- })
514
- )
515
- }, [])
516
-
517
- // Request env data for a session
518
- const requestEnvData = useCallback((sessionId: string) => {
519
- if (!ws.current || ws.current.readyState !== WebSocket.OPEN) {
520
- console.error('WebSocket not connected')
521
- return
522
- }
523
-
524
- ws.current.send(
525
- JSON.stringify({
526
- type: 'get_env_data_list',
527
- payload: { session_id: sessionId },
528
- })
529
- )
530
- }, [])
531
-
532
- // Update a PO's prompt (markdown body)
533
- const updatePrompt = useCallback((target: string, prompt: string) => {
534
- if (!ws.current || ws.current.readyState !== WebSocket.OPEN) {
535
- console.error('WebSocket not connected')
536
- return
537
- }
538
-
539
- ws.current.send(
540
- JSON.stringify({
541
- type: 'update_prompt',
542
- payload: { target, prompt },
543
- })
544
- )
545
- }, [])
546
-
547
- return {
548
- sendMessage,
549
- respondToNotification,
550
- createSession,
551
- switchSession,
552
- switchLLM,
553
- createThread,
554
- updatePrompt,
555
- requestUsage,
556
- exportThread,
557
- requestEnvData,
558
- }
559
- }
@@ -1,102 +0,0 @@
1
- @tailwind base;
2
- @tailwind components;
3
- @tailwind utilities;
4
-
5
- /* Base styles */
6
- html {
7
- color-scheme: dark;
8
- }
9
-
10
- body {
11
- @apply antialiased;
12
- }
13
-
14
- /* Custom scrollbar */
15
- ::-webkit-scrollbar {
16
- width: 6px;
17
- height: 6px;
18
- }
19
-
20
- ::-webkit-scrollbar-track {
21
- @apply bg-po-bg;
22
- }
23
-
24
- ::-webkit-scrollbar-thumb {
25
- @apply bg-po-border rounded-full;
26
- }
27
-
28
- ::-webkit-scrollbar-thumb:hover {
29
- @apply bg-po-border-focus;
30
- }
31
-
32
- /* Selection */
33
- ::selection {
34
- background: rgba(212, 149, 42, 0.3);
35
- }
36
-
37
- /* Custom utilities */
38
- @layer utilities {
39
- .scrollbar-thin {
40
- scrollbar-width: thin;
41
- scrollbar-color: #3d3a37 #1a1918;
42
- }
43
- }
44
-
45
- /* Resize handles */
46
- .resize-handle {
47
- @apply w-1 cursor-col-resize hover:bg-po-accent/30 active:bg-po-accent/50 transition-colors border-l border-po-border;
48
- }
49
-
50
- .resize-handle-h {
51
- @apply h-1 cursor-row-resize hover:bg-po-accent/30 active:bg-po-accent/50 transition-colors border-t border-po-border;
52
- }
53
-
54
- /* Canvas CSS2DRenderer labels */
55
- .canvas-node-label {
56
- text-align: center;
57
- pointer-events: none;
58
- user-select: none;
59
- }
60
-
61
- .canvas-node-name {
62
- display: block;
63
- color: #e8e2da;
64
- font-size: 12px;
65
- font-weight: 500;
66
- font-family: 'Geist Mono', 'IBM Plex Mono', monospace;
67
- text-shadow: 0 1px 4px rgba(0, 0, 0, 0.8);
68
- }
69
-
70
- .canvas-node-status {
71
- display: block;
72
- color: #78726a;
73
- font-size: 10px;
74
- font-family: 'Geist Mono', 'IBM Plex Mono', monospace;
75
- text-shadow: 0 1px 4px rgba(0, 0, 0, 0.8);
76
- }
77
-
78
- .canvas-node-badge {
79
- display: flex;
80
- align-items: center;
81
- justify-content: center;
82
- width: 20px;
83
- height: 20px;
84
- background: #d4952a;
85
- color: #1a1918;
86
- font-size: 10px;
87
- font-weight: 700;
88
- border-radius: 50%;
89
- pointer-events: none;
90
- user-select: none;
91
- box-shadow: 0 0 8px rgba(212, 149, 42, 0.6);
92
- }
93
-
94
- .canvas-toolcall-label {
95
- color: #3b9a6e;
96
- font-size: 10px;
97
- font-family: 'Geist Mono', 'IBM Plex Mono', monospace;
98
- text-align: center;
99
- pointer-events: none;
100
- user-select: none;
101
- text-shadow: 0 1px 4px rgba(0, 0, 0, 0.8);
102
- }
@@ -1,10 +0,0 @@
1
- import { StrictMode } from 'react'
2
- import { createRoot } from 'react-dom/client'
3
- import App from './App'
4
- import './index.css'
5
-
6
- createRoot(document.getElementById('root')!).render(
7
- <StrictMode>
8
- <App />
9
- </StrictMode>
10
- )