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