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,249 +0,0 @@
1
- import * as THREE from 'three'
2
- import { CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js'
3
- import { COLORS, CSS_COLORS, NODE, ANIMATION } from '../constants'
4
-
5
- const vertexShader = /* glsl */ `
6
- varying vec2 vUv;
7
- void main() {
8
- vUv = uv;
9
- gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
10
- }
11
- `
12
-
13
- const fragmentShader = /* glsl */ `
14
- uniform vec3 uColor;
15
- uniform vec3 uGlowColor;
16
- uniform float uGlowIntensity;
17
- uniform float uTime;
18
- uniform float uHovered;
19
- uniform float uSelected;
20
-
21
- varying vec2 vUv;
22
-
23
- void main() {
24
- // Distance from center (0 at center, 1 at edge)
25
- float dist = length(vUv - 0.5) * 2.0;
26
-
27
- // Base dark fill
28
- vec3 base = uColor * 0.3;
29
-
30
- // Edge glow
31
- float edgeGlow = smoothstep(0.5, 1.0, dist) * uGlowIntensity;
32
- // Animated pulse
33
- float pulse = sin(uTime * ${ANIMATION.pulseSpeed.toFixed(1)}) * 0.15 + 0.85;
34
- edgeGlow *= pulse;
35
-
36
- vec3 color = mix(base, uGlowColor, edgeGlow);
37
-
38
- // Hover brightening
39
- color = mix(color, uGlowColor * 1.2, uHovered * 0.3);
40
-
41
- // Selection brightening
42
- color = mix(color, uGlowColor * 1.5, uSelected * 0.4);
43
-
44
- // Alpha: solid center, slight transparency at edges
45
- float alpha = smoothstep(1.0, 0.8, dist);
46
-
47
- gl_FragColor = vec4(color, alpha);
48
- }
49
- `
50
-
51
- type POStatus = 'idle' | 'thinking' | 'calling_tool'
52
-
53
- const STATUS_COLORS: Record<POStatus, number> = {
54
- idle: COLORS.statusIdle,
55
- thinking: COLORS.statusThinking,
56
- calling_tool: COLORS.statusCallingTool,
57
- }
58
-
59
- const STATUS_CSS_COLORS: Record<POStatus, string> = {
60
- idle: CSS_COLORS.statusIdle,
61
- thinking: CSS_COLORS.statusThinking,
62
- calling_tool: CSS_COLORS.statusCallingTool,
63
- }
64
-
65
- // Color for when this PO is being called by another PO (delegation)
66
- const DELEGATED_COLOR = 0x5a8fc2 // steel blue (matches po-status-delegated)
67
- const DELEGATED_CSS_COLOR = '#5a8fc2'
68
-
69
- export class PONode {
70
- readonly id: string
71
- readonly group: THREE.Group
72
- readonly mesh: THREE.Mesh
73
-
74
- private material: THREE.ShaderMaterial
75
- private statusRing: THREE.LineLoop
76
- private statusRingMaterial: THREE.LineBasicMaterial
77
- private label: CSS2DObject
78
- private labelEl: HTMLDivElement
79
- private nameEl: HTMLSpanElement
80
- private statusEl: HTMLSpanElement
81
- private badge: CSS2DObject
82
- private badgeEl: HTMLDivElement
83
- private badgeCountEl: HTMLSpanElement
84
-
85
- private targetPosition = new THREE.Vector3()
86
-
87
- constructor(id: string, name: string) {
88
- this.id = id
89
- this.group = new THREE.Group()
90
- this.group.userData = { type: 'po', id }
91
-
92
- // Hexagonal geometry
93
- const geometry = new THREE.CircleGeometry(NODE.poRadius, NODE.poSides)
94
-
95
- // Shader material
96
- this.material = new THREE.ShaderMaterial({
97
- vertexShader,
98
- fragmentShader,
99
- uniforms: {
100
- uColor: { value: new THREE.Color(COLORS.nodeFill) },
101
- uGlowColor: { value: new THREE.Color(COLORS.nodeGlow) },
102
- uGlowIntensity: { value: 0.5 },
103
- uTime: { value: 0 },
104
- uHovered: { value: 0 },
105
- uSelected: { value: 0 },
106
- },
107
- transparent: true,
108
- })
109
-
110
- this.mesh = new THREE.Mesh(geometry, this.material)
111
- this.mesh.userData = { type: 'po', id }
112
- this.group.add(this.mesh)
113
-
114
- // Status ring (hex outline)
115
- const ringGeometry = new THREE.BufferGeometry()
116
- const ringPoints: THREE.Vector3[] = []
117
- for (let i = 0; i <= NODE.poSides; i++) {
118
- const angle = (i / NODE.poSides) * Math.PI * 2 - Math.PI / 2
119
- ringPoints.push(
120
- new THREE.Vector3(
121
- Math.cos(angle) * (NODE.poRadius + 3),
122
- Math.sin(angle) * (NODE.poRadius + 3),
123
- 0
124
- )
125
- )
126
- }
127
- ringGeometry.setFromPoints(ringPoints)
128
-
129
- this.statusRingMaterial = new THREE.LineBasicMaterial({
130
- color: STATUS_COLORS.idle,
131
- transparent: true,
132
- opacity: 0.6,
133
- })
134
- this.statusRing = new THREE.LineLoop(ringGeometry, this.statusRingMaterial)
135
- this.group.add(this.statusRing)
136
-
137
- // CSS2D Label (name + status)
138
- this.labelEl = document.createElement('div')
139
- this.labelEl.className = 'canvas-node-label'
140
-
141
- this.nameEl = document.createElement('span')
142
- this.nameEl.className = 'canvas-node-name'
143
- this.nameEl.textContent = name
144
-
145
- this.statusEl = document.createElement('span')
146
- this.statusEl.className = 'canvas-node-status'
147
- this.statusEl.textContent = 'idle'
148
-
149
- this.labelEl.appendChild(this.nameEl)
150
- this.labelEl.appendChild(this.statusEl)
151
-
152
- this.label = new CSS2DObject(this.labelEl)
153
- this.label.position.set(0, -NODE.labelOffsetY, 0)
154
- this.group.add(this.label)
155
-
156
- // Notification badge
157
- this.badgeEl = document.createElement('div')
158
- this.badgeEl.className = 'canvas-node-badge'
159
- this.badgeEl.style.display = 'none'
160
-
161
- this.badgeCountEl = document.createElement('span')
162
- this.badgeCountEl.textContent = '0'
163
- this.badgeEl.appendChild(this.badgeCountEl)
164
-
165
- this.badge = new CSS2DObject(this.badgeEl)
166
- this.badge.position.set(NODE.badgeOffsetX, -NODE.badgeOffsetY, 0)
167
- this.group.add(this.badge)
168
- }
169
-
170
- setStatus(status: POStatus): void {
171
- this.statusRingMaterial.color.setHex(STATUS_COLORS[status])
172
-
173
- // Update glow intensity based on status
174
- const intensity = status === 'idle' ? 0.3 : status === 'thinking' ? 0.8 : 0.6
175
- this.material.uniforms.uGlowIntensity.value = intensity
176
-
177
- // Update glow color for calling_tool
178
- if (status === 'calling_tool') {
179
- this.material.uniforms.uGlowColor.value.setHex(COLORS.statusCallingTool)
180
- } else {
181
- this.material.uniforms.uGlowColor.value.setHex(COLORS.nodeGlow)
182
- }
183
-
184
- this.statusEl.textContent = status.replace('_', ' ')
185
- this.statusEl.style.color = STATUS_CSS_COLORS[status]
186
- }
187
-
188
- setDelegatedBy(callerName: string): void {
189
- // Override visual to show this PO is working on behalf of another PO.
190
- // The server doesn't send status updates for delegated POs, so we
191
- // infer this from the caller's tool_calls.
192
- this.statusRingMaterial.color.setHex(DELEGATED_COLOR)
193
- this.statusRingMaterial.opacity = 1.0
194
- this.material.uniforms.uGlowColor.value.setHex(DELEGATED_COLOR)
195
- this.material.uniforms.uGlowIntensity.value = 0.7
196
- this.statusEl.textContent = `called by ${callerName}`
197
- this.statusEl.style.color = DELEGATED_CSS_COLOR
198
- }
199
-
200
- clearDelegated(): void {
201
- // Restore to whatever the server status is — caller should
202
- // call setStatus() after this to re-apply server state.
203
- this.statusRingMaterial.opacity = 0.6
204
- }
205
-
206
- setNotificationCount(count: number): void {
207
- if (count > 0) {
208
- this.badgeEl.style.display = 'flex'
209
- this.badgeCountEl.textContent = String(count)
210
- } else {
211
- this.badgeEl.style.display = 'none'
212
- }
213
- }
214
-
215
- setHovered(hovered: boolean): void {
216
- this.material.uniforms.uHovered.value = hovered ? 1 : 0
217
- }
218
-
219
- setSelected(selected: boolean): void {
220
- this.material.uniforms.uSelected.value = selected ? 1 : 0
221
- this.statusRingMaterial.opacity = selected ? 1.0 : 0.6
222
- }
223
-
224
- setPosition(x: number, y: number): void {
225
- this.targetPosition.set(x, y, 0)
226
- }
227
-
228
- getPosition(): THREE.Vector3 {
229
- return this.group.position.clone()
230
- }
231
-
232
- update(_delta: number, elapsed: number): void {
233
- // Lerp toward target position
234
- this.group.position.lerp(this.targetPosition, ANIMATION.positionLerpFactor)
235
-
236
- // Update time uniform for pulse animation
237
- this.material.uniforms.uTime.value = elapsed
238
- }
239
-
240
- dispose(): void {
241
- this.mesh.geometry.dispose()
242
- this.material.dispose()
243
- this.statusRing.geometry.dispose()
244
- this.statusRingMaterial.dispose()
245
- this.labelEl.remove()
246
- this.badgeEl.remove()
247
- this.group.parent?.remove(this.group)
248
- }
249
- }
@@ -1,116 +0,0 @@
1
- import * as THREE from 'three'
2
- import { CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js'
3
- import { COLORS, NODE, ANIMATION } from '../constants'
4
-
5
- type Phase = 'fadein' | 'active' | 'fadeout' | 'expired'
6
-
7
- export class ToolCallNode {
8
- readonly id: string
9
- readonly callerPO: string
10
- readonly group: THREE.Group
11
- readonly mesh: THREE.Mesh
12
-
13
- private material: THREE.MeshBasicMaterial
14
- private label: CSS2DObject
15
- private labelEl: HTMLDivElement
16
- private phase: Phase = 'fadein'
17
- private phaseTime = 0
18
- private targetPosition = new THREE.Vector3()
19
-
20
- constructor(id: string, toolName: string, callerPO: string) {
21
- this.id = id
22
- this.callerPO = callerPO
23
- this.group = new THREE.Group()
24
- this.group.userData = { type: 'toolcall', id }
25
-
26
- // Diamond shape (rotated square)
27
- const r = NODE.toolCallRadius
28
- const shape = new THREE.Shape()
29
- shape.moveTo(0, r)
30
- shape.lineTo(r, 0)
31
- shape.lineTo(0, -r)
32
- shape.lineTo(-r, 0)
33
- shape.closePath()
34
-
35
- const geometry = new THREE.ShapeGeometry(shape)
36
-
37
- this.material = new THREE.MeshBasicMaterial({
38
- color: COLORS.toolCallFill,
39
- transparent: true,
40
- opacity: 0,
41
- })
42
-
43
- this.mesh = new THREE.Mesh(geometry, this.material)
44
- this.mesh.userData = { type: 'toolcall', id }
45
- this.group.add(this.mesh)
46
-
47
- // Label
48
- this.labelEl = document.createElement('div')
49
- this.labelEl.className = 'canvas-toolcall-label'
50
- this.labelEl.textContent = toolName
51
-
52
- this.label = new CSS2DObject(this.labelEl)
53
- this.label.position.set(0, -(NODE.toolCallRadius + 12), 0)
54
- this.group.add(this.label)
55
- }
56
-
57
- setPosition(x: number, y: number): void {
58
- this.targetPosition.set(x, y, 0)
59
- }
60
-
61
- triggerFadeOut(): void {
62
- if (this.phase !== 'expired') {
63
- this.phase = 'fadeout'
64
- this.phaseTime = 0
65
- }
66
- }
67
-
68
- isExpired(): boolean {
69
- return this.phase === 'expired'
70
- }
71
-
72
- update(delta: number): void {
73
- this.phaseTime += delta
74
- this.group.position.lerp(this.targetPosition, ANIMATION.positionLerpFactor)
75
-
76
- switch (this.phase) {
77
- case 'fadein':
78
- this.material.opacity = Math.min(this.phaseTime / ANIMATION.toolCallFadeInDuration, 1)
79
- if (this.phaseTime >= ANIMATION.toolCallFadeInDuration) {
80
- this.phase = 'active'
81
- this.phaseTime = 0
82
- }
83
- break
84
-
85
- case 'active':
86
- this.material.opacity = 1
87
- if (this.phaseTime >= ANIMATION.toolCallActiveDuration) {
88
- this.phase = 'fadeout'
89
- this.phaseTime = 0
90
- }
91
- break
92
-
93
- case 'fadeout':
94
- this.material.opacity = Math.max(
95
- 1 - this.phaseTime / ANIMATION.toolCallFadeOutDuration,
96
- 0
97
- )
98
- this.labelEl.style.opacity = String(this.material.opacity)
99
- if (this.phaseTime >= ANIMATION.toolCallFadeOutDuration) {
100
- this.phase = 'expired'
101
- }
102
- break
103
-
104
- case 'expired':
105
- this.material.opacity = 0
106
- break
107
- }
108
- }
109
-
110
- dispose(): void {
111
- this.mesh.geometry.dispose()
112
- this.material.dispose()
113
- this.labelEl.remove()
114
- this.group.parent?.remove(this.group)
115
- }
116
- }
@@ -1,24 +0,0 @@
1
- // Canvas-specific type definitions
2
-
3
- export interface CanvasNodeSelection {
4
- type: 'po' | 'toolcall'
5
- id: string
6
- }
7
-
8
- export interface ActiveToolCall {
9
- id: string
10
- toolName: string
11
- callerPO: string
12
- params: Record<string, unknown>
13
- status: 'active' | 'completed' | 'error'
14
- result?: string
15
- startedAt: number
16
- completedAt?: number
17
- }
18
-
19
- export interface ActiveMessageArc {
20
- id: string
21
- from: string
22
- to: string
23
- timestamp: number
24
- }
@@ -1,68 +0,0 @@
1
- import { useEffect, useRef } from 'react'
2
-
3
- interface ContextMenuItem {
4
- label: string
5
- onClick: () => void
6
- icon?: string
7
- danger?: boolean
8
- }
9
-
10
- interface ContextMenuProps {
11
- x: number
12
- y: number
13
- items: ContextMenuItem[]
14
- onClose: () => void
15
- }
16
-
17
- export function ContextMenu({ x, y, items, onClose }: ContextMenuProps) {
18
- const menuRef = useRef<HTMLDivElement>(null)
19
-
20
- useEffect(() => {
21
- const handleClickOutside = (e: MouseEvent) => {
22
- if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
23
- onClose()
24
- }
25
- }
26
- const handleEscape = (e: KeyboardEvent) => {
27
- if (e.key === 'Escape') onClose()
28
- }
29
-
30
- document.addEventListener('mousedown', handleClickOutside)
31
- document.addEventListener('keydown', handleEscape)
32
- return () => {
33
- document.removeEventListener('mousedown', handleClickOutside)
34
- document.removeEventListener('keydown', handleEscape)
35
- }
36
- }, [onClose])
37
-
38
- const adjustedStyle = {
39
- top: y,
40
- left: x,
41
- }
42
-
43
- return (
44
- <div
45
- ref={menuRef}
46
- className="fixed z-50 bg-po-surface-2 border border-po-border rounded shadow-xl py-0.5 min-w-[140px]"
47
- style={adjustedStyle}
48
- >
49
- {items.map((item, idx) => (
50
- <button
51
- key={idx}
52
- onClick={() => {
53
- item.onClick()
54
- onClose()
55
- }}
56
- className={`w-full text-left px-2.5 py-1.5 text-xs transition-colors duration-150 flex items-center gap-1.5 ${
57
- item.danger
58
- ? 'text-po-error hover:bg-po-surface-3'
59
- : 'text-po-text-secondary hover:bg-po-surface-3 hover:text-po-text-primary'
60
- }`}
61
- >
62
- {item.icon && <span>{item.icon}</span>}
63
- {item.label}
64
- </button>
65
- ))}
66
- </div>
67
- )
68
- }
@@ -1,232 +0,0 @@
1
- import { useState, useMemo } from 'react'
2
- import { useStore, usePONotifications } from '../store'
3
- import { useResize } from '../hooks/useResize'
4
- import { MethodList } from './MethodList'
5
- import { SourcePane } from './SourcePane'
6
- import { Workspace } from './Workspace'
7
- import { ContextMenu } from './ContextMenu'
8
- import { PaneSlot } from './PaneSlot'
9
- import type { PromptObject, CapabilityInfo } from '../types'
10
-
11
- interface InspectorProps {
12
- po: PromptObject
13
- sendMessage: (target: string, content: string, newThread?: boolean) => void
14
- createSession?: (target: string, name?: string) => void
15
- switchSession: (target: string, sessionId: string) => void
16
- createThread: (target: string) => void
17
- updatePrompt: (target: string, prompt: string) => void
18
- requestUsage?: (sessionId: string, includeTree?: boolean) => void
19
- exportThread?: (sessionId: string, format?: string) => void
20
- }
21
-
22
- export function Inspector({
23
- po,
24
- sendMessage,
25
- switchSession,
26
- createThread,
27
- updatePrompt,
28
- requestUsage,
29
- exportThread,
30
- }: InspectorProps) {
31
- const [selectedCapability, setSelectedCapability] = useState<CapabilityInfo | null>(null)
32
- const [threadMenuOpen, setThreadMenuOpen] = useState(false)
33
- const [contextMenu, setContextMenu] = useState<{ x: number; y: number; sessionId: string } | null>(null)
34
- const notifications = usePONotifications(po.name)
35
- const topPaneCollapsed = useStore((s) => s.topPaneCollapsed)
36
- const toggleTopPane = useStore((s) => s.toggleTopPane)
37
-
38
- const topPaneResize = useResize({
39
- direction: 'vertical',
40
- initialSize: 260,
41
- minSize: 120,
42
- maxSize: 600,
43
- })
44
-
45
- const methodListResize = useResize({
46
- direction: 'horizontal',
47
- initialSize: 192,
48
- minSize: 120,
49
- maxSize: 320,
50
- })
51
-
52
- const sessions = po.sessions || []
53
- const currentSessionId = po.current_session?.id
54
-
55
- // Sort sessions: current first, then by updated_at desc
56
- const sortedSessions = useMemo(() => {
57
- return [...sessions].sort((a, b) => {
58
- if (a.id === currentSessionId) return -1
59
- if (b.id === currentSessionId) return 1
60
- return (b.updated_at || '').localeCompare(a.updated_at || '')
61
- })
62
- }, [sessions, currentSessionId])
63
-
64
- const isActive = po.status !== 'idle'
65
-
66
- const statusDot = {
67
- idle: 'bg-po-status-idle',
68
- thinking: 'bg-po-status-active',
69
- calling_tool: 'bg-po-status-calling',
70
- }[po.status] || 'bg-po-status-idle'
71
-
72
- const statusGlow = {
73
- idle: '',
74
- thinking: 'shadow-[0_0_6px_rgba(212,149,42,0.7)]',
75
- calling_tool: 'shadow-[0_0_6px_rgba(59,154,110,0.7)]',
76
- }[po.status] || ''
77
-
78
- const statusLabelColor = {
79
- idle: 'text-po-text-ghost',
80
- thinking: 'text-po-status-active',
81
- calling_tool: 'text-po-status-calling',
82
- }[po.status] || 'text-po-text-ghost'
83
-
84
- const statusLabel = {
85
- idle: 'idle',
86
- thinking: 'thinking...',
87
- calling_tool: 'calling tool...',
88
- }[po.status] || po.status
89
-
90
- const handleThreadContextMenu = (e: React.MouseEvent, sessionId: string) => {
91
- e.preventDefault()
92
- setContextMenu({ x: e.clientX, y: e.clientY, sessionId })
93
- }
94
-
95
- return (
96
- <div className="h-full flex flex-col">
97
- {/* Inspector Header */}
98
- <div className="h-8 bg-po-surface-2 border-b border-po-border flex items-center px-3 gap-2 flex-shrink-0">
99
- <div className="relative flex-shrink-0">
100
- <div className={`w-2 h-2 rounded-full ${statusDot} ${statusGlow} ${isActive ? 'animate-pulse' : ''}`} />
101
- </div>
102
- <span className="font-mono text-xs text-po-text-primary font-medium">{po.name}</span>
103
- <span className={`text-2xs font-medium truncate ${statusLabelColor} ${isActive ? 'animate-pulse' : ''}`}>{statusLabel}</span>
104
- {po.description && (
105
- <span className="text-2xs text-po-text-ghost truncate hidden sm:inline">{po.description}</span>
106
- )}
107
- {notifications.length > 0 && (
108
- <span className="text-2xs font-mono bg-po-warning text-po-bg px-1 rounded font-bold">
109
- {notifications.length}
110
- </span>
111
- )}
112
-
113
- <div className="flex-1" />
114
-
115
- {/* Thread picker */}
116
- <div className="relative">
117
- <button
118
- onClick={() => setThreadMenuOpen(!threadMenuOpen)}
119
- className="flex items-center gap-1 text-2xs text-po-text-secondary hover:text-po-text-primary transition-colors duration-150"
120
- >
121
- <span className="font-mono">
122
- {currentSessionId
123
- ? sessions.find(s => s.id === currentSessionId)?.name || `Thread ${currentSessionId.slice(0, 6)}`
124
- : 'No thread'}
125
- </span>
126
- <svg className={`w-3 h-3 transition-transform ${threadMenuOpen ? 'rotate-180' : ''}`} fill="none" stroke="currentColor" viewBox="0 0 24 24">
127
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
128
- </svg>
129
- </button>
130
-
131
- {threadMenuOpen && (
132
- <div className="absolute right-0 top-full mt-1 w-56 bg-po-surface-2 border border-po-border rounded shadow-xl z-50 overflow-hidden">
133
- <button
134
- onClick={() => { createThread(po.name); setThreadMenuOpen(false) }}
135
- className="w-full text-left px-2.5 py-1.5 text-xs text-po-accent hover:bg-po-surface-3 transition-colors duration-150 border-b border-po-border"
136
- >
137
- + New Thread
138
- </button>
139
- <div className="max-h-48 overflow-auto">
140
- {sortedSessions.map((session) => (
141
- <button
142
- key={session.id}
143
- onClick={() => { switchSession(po.name, session.id); setThreadMenuOpen(false) }}
144
- onContextMenu={(e) => handleThreadContextMenu(e, session.id)}
145
- className={`w-full text-left px-2.5 py-1.5 text-xs transition-colors duration-150 ${
146
- session.id === currentSessionId
147
- ? 'bg-po-accent-wash text-po-accent'
148
- : 'text-po-text-secondary hover:bg-po-surface-3'
149
- }`}
150
- >
151
- <div className="flex items-center gap-1.5">
152
- {session.thread_type === 'delegation' && <span className="text-po-status-delegated">&#8627;</span>}
153
- <span className="font-mono truncate flex-1">
154
- {session.name || `Thread ${session.id.slice(0, 6)}`}
155
- </span>
156
- <span className="text-2xs text-po-text-ghost">{session.message_count}m</span>
157
- </div>
158
- {session.parent_po && (
159
- <div className="text-2xs text-po-status-delegated mt-0.5">from {session.parent_po}</div>
160
- )}
161
- </button>
162
- ))}
163
- </div>
164
- </div>
165
- )}
166
- </div>
167
- </div>
168
-
169
- {/* Top: Methods + Source (collapsible, resizable height) */}
170
- <PaneSlot
171
- label="Methods | Source"
172
- collapsed={topPaneCollapsed}
173
- onToggle={toggleTopPane}
174
- height={topPaneResize.size}
175
- resizeHandle={
176
- <div
177
- className="resize-handle-h"
178
- onMouseDown={topPaneResize.onMouseDown}
179
- />
180
- }
181
- >
182
- <div className="flex h-full">
183
- {/* Method List (resizable width) */}
184
- <div style={{ width: methodListResize.size }} className="flex-shrink-0">
185
- <MethodList
186
- po={po}
187
- selectedCapability={selectedCapability}
188
- onSelectCapability={setSelectedCapability}
189
- />
190
- </div>
191
-
192
- {/* Resize handle */}
193
- <div
194
- className="resize-handle"
195
- onMouseDown={methodListResize.onMouseDown}
196
- />
197
-
198
- {/* Source Pane */}
199
- <SourcePane
200
- po={po}
201
- selectedCapability={selectedCapability}
202
- onSave={(prompt) => updatePrompt(po.name, prompt)}
203
- />
204
- </div>
205
- </PaneSlot>
206
-
207
- {/* Bottom: Workspace */}
208
- <div className="flex-1 overflow-hidden">
209
- <Workspace po={po} sendMessage={sendMessage} />
210
- </div>
211
-
212
- {/* Context menu for thread right-click */}
213
- {contextMenu && (
214
- <ContextMenu
215
- x={contextMenu.x}
216
- y={contextMenu.y}
217
- onClose={() => setContextMenu(null)}
218
- items={[
219
- ...(requestUsage ? [
220
- { label: 'View Usage', onClick: () => requestUsage(contextMenu.sessionId) },
221
- { label: 'View Tree Usage', onClick: () => requestUsage(contextMenu.sessionId, true) },
222
- ] : []),
223
- ...(exportThread ? [
224
- { label: 'Export Markdown', onClick: () => exportThread(contextMenu.sessionId, 'markdown') },
225
- { label: 'Export JSON', onClick: () => exportThread(contextMenu.sessionId, 'json') },
226
- ] : []),
227
- ]}
228
- />
229
- )}
230
- </div>
231
- )
232
- }