prompt_objects 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (292) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +142 -0
  3. data/README.md +211 -19
  4. data/exe/poop +5 -0
  5. data/exe/prompt_objects +1 -1108
  6. data/exe/prompt_objects_mcp +5 -3
  7. data/lib/prompt_objects/capability.rb +33 -0
  8. data/lib/prompt_objects/cli/diagnostics_command.rb +69 -0
  9. data/lib/prompt_objects/cli/env_command.rb +435 -0
  10. data/lib/prompt_objects/cli/events_command.rb +145 -0
  11. data/lib/prompt_objects/cli/explore_command.rb +155 -0
  12. data/lib/prompt_objects/cli/helpers.rb +91 -0
  13. data/lib/prompt_objects/cli/message_command.rb +216 -0
  14. data/lib/prompt_objects/cli/repl_command.rb +366 -0
  15. data/lib/prompt_objects/cli/serve_command.rb +162 -0
  16. data/lib/prompt_objects/cli.rb +64 -422
  17. data/lib/prompt_objects/connectors/base.rb +16 -29
  18. data/lib/prompt_objects/connectors/mcp.rb +82 -98
  19. data/lib/prompt_objects/domain/records.rb +45 -0
  20. data/lib/prompt_objects/environment/exporter.rb +5 -4
  21. data/lib/prompt_objects/environment/git.rb +21 -1
  22. data/lib/prompt_objects/environment/importer.rb +5 -4
  23. data/lib/prompt_objects/environment/manager.rb +44 -6
  24. data/lib/prompt_objects/environment/manifest.rb +26 -4
  25. data/lib/prompt_objects/environment.rb +1350 -18
  26. data/lib/prompt_objects/execution/blocking_executor.rb +88 -0
  27. data/lib/prompt_objects/execution/context.rb +137 -0
  28. data/lib/prompt_objects/execution/contracts.rb +59 -0
  29. data/lib/prompt_objects/execution/permit_pool.rb +49 -0
  30. data/lib/prompt_objects/execution/policy.rb +47 -0
  31. data/lib/prompt_objects/execution/resource_lock_manager.rb +63 -0
  32. data/lib/prompt_objects/execution/runner.rb +498 -0
  33. data/lib/prompt_objects/execution/scheduler.rb +325 -0
  34. data/lib/prompt_objects/llm/factory.rb +4 -4
  35. data/lib/prompt_objects/llm/gemini_adapter.rb +25 -8
  36. data/lib/prompt_objects/llm/openai_adapter.rb +9 -1
  37. data/lib/prompt_objects/llm/pricing.rb +9 -2
  38. data/lib/prompt_objects/llm/response.rb +9 -4
  39. data/lib/prompt_objects/loader.rb +28 -1
  40. data/lib/prompt_objects/mcp/server.rb +6 -3
  41. data/lib/prompt_objects/mcp/tools/get_conversation.rb +15 -11
  42. data/lib/prompt_objects/mcp/tools/get_pending_requests.rb +2 -2
  43. data/lib/prompt_objects/mcp/tools/respond_to_request.rb +3 -3
  44. data/lib/prompt_objects/mcp/tools/send_message.rb +42 -32
  45. data/lib/prompt_objects/operations/diagnostics.rb +115 -0
  46. data/lib/prompt_objects/operations/retention.rb +75 -0
  47. data/lib/prompt_objects/operations/run_trace_builder.rb +152 -0
  48. data/lib/prompt_objects/primitives/http_get.rb +2 -0
  49. data/lib/prompt_objects/primitives/list_files.rb +2 -0
  50. data/lib/prompt_objects/primitives/read_file.rb +2 -0
  51. data/lib/prompt_objects/primitives/think.rb +36 -0
  52. data/lib/prompt_objects/primitives/write_file.rb +9 -0
  53. data/lib/prompt_objects/prompt_object.rb +190 -432
  54. data/lib/prompt_objects/repositories/artifact_repository.rb +109 -0
  55. data/lib/prompt_objects/repositories/base_repository.rb +37 -0
  56. data/lib/prompt_objects/repositories/definition_repository.rb +36 -0
  57. data/lib/prompt_objects/repositories/env_data_repository.rb +103 -0
  58. data/lib/prompt_objects/repositories/event_repository.rb +105 -0
  59. data/lib/prompt_objects/repositories/message_repository.rb +73 -0
  60. data/lib/prompt_objects/repositories/run_repository.rb +254 -0
  61. data/lib/prompt_objects/repositories/thread_repository.rb +87 -0
  62. data/lib/prompt_objects/repositories/tool_execution_repository.rb +88 -0
  63. data/lib/prompt_objects/repositories/workspace_session_repository.rb +61 -0
  64. data/lib/prompt_objects/server/api/routes.rb +561 -52
  65. data/lib/prompt_objects/server/app.rb +134 -1
  66. data/lib/prompt_objects/server/file_watcher.rb +9 -0
  67. data/lib/prompt_objects/server/public/assets/index-BjKlODDD.js +90 -0
  68. data/lib/prompt_objects/server/public/assets/index-w3fpIb5c.css +1 -0
  69. data/lib/prompt_objects/server/public/index.html +2 -2
  70. data/lib/prompt_objects/server/websocket_handler.rb +202 -207
  71. data/lib/prompt_objects/server.rb +170 -1
  72. data/lib/prompt_objects/service.rb +129 -0
  73. data/lib/prompt_objects/session/store.rb +774 -15
  74. data/lib/prompt_objects/universal/add_capability.rb +1 -1
  75. data/lib/prompt_objects/universal/create_capability.rb +4 -2
  76. data/lib/prompt_objects/universal/delete_env_data.rb +78 -0
  77. data/lib/prompt_objects/universal/delete_primitive.rb +1 -1
  78. data/lib/prompt_objects/universal/dispatch_po.rb +54 -0
  79. data/lib/prompt_objects/universal/get_env_data.rb +58 -0
  80. data/lib/prompt_objects/universal/get_run_status.rb +30 -0
  81. data/lib/prompt_objects/universal/join_runs.rb +35 -0
  82. data/lib/prompt_objects/universal/list_env_data.rb +57 -0
  83. data/lib/prompt_objects/universal/modify_primitive.rb +1 -1
  84. data/lib/prompt_objects/universal/render_artifact.rb +91 -0
  85. data/lib/prompt_objects/universal/request_primitive.rb +1 -1
  86. data/lib/prompt_objects/universal/set_env_data.rb +101 -0
  87. data/lib/prompt_objects/version.rb +5 -0
  88. data/lib/prompt_objects.rb +36 -6
  89. data/objects/coordinator.md +8 -4
  90. data/templates/arc-agi-1/objects/data_manager.md +2 -2
  91. data/templates/arc-agi-1/objects/observer.md +6 -2
  92. data/templates/arc-agi-1/objects/solver.md +14 -4
  93. data/templates/arc-agi-1/objects/verifier.md +6 -2
  94. data/templates/arc-agi-3/manifest.yml +19 -0
  95. data/templates/arc-agi-3/objects/analyst.md +79 -0
  96. data/templates/arc-agi-3/objects/player.md +76 -0
  97. data/templates/arc-agi-3/objects/strategist.md +97 -0
  98. data/templates/arc-agi-3/primitives/action_log.rb +133 -0
  99. data/templates/arc-agi-3/primitives/arc3_api.rb +223 -0
  100. data/templates/arc-agi-3/primitives/arc3_scorecard.rb +131 -0
  101. data/templates/arc-agi-3/primitives/frame_diff.rb +143 -0
  102. data/templates/arc-agi-3/primitives/frame_info.rb +96 -0
  103. data/templates/arc-agi-3/primitives/render_frame.rb +140 -0
  104. data/templates/basic/objects/basic.md +11 -3
  105. data/templates/chiptune/extras/sonic_pi_boot.rb +220 -0
  106. data/templates/chiptune/manifest.yml +20 -0
  107. data/templates/chiptune/objects/conductor.md +184 -0
  108. data/templates/chiptune/objects/sonic_pi.md +65 -0
  109. data/templates/chiptune/objects/voice_bass.md +44 -0
  110. data/templates/chiptune/objects/voice_lead.md +44 -0
  111. data/templates/chiptune/objects/voice_pad.md +43 -0
  112. data/templates/chiptune/objects/voice_perc.md +45 -0
  113. data/templates/chiptune/primitives/play_note.rb +59 -0
  114. data/templates/chiptune/primitives/play_sequence.rb +55 -0
  115. data/templates/chiptune/primitives/query_sonic_pi.rb +141 -0
  116. data/templates/chiptune/primitives/set_synth.rb +57 -0
  117. data/templates/chiptune/primitives/set_tempo.rb +45 -0
  118. data/templates/chiptune/primitives/stop_voice.rb +46 -0
  119. data/templates/chiptune/primitives/support/osc_bridge.rb +33 -0
  120. data/templates/chiptune/vendor/osc-ruby-1.1.5/LICENSE +19 -0
  121. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/address_pattern.rb +58 -0
  122. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/broadcast_client.rb +24 -0
  123. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/bundle.rb +46 -0
  124. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/client.rb +13 -0
  125. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/core_ext/time.rb +6 -0
  126. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/em_server.rb +63 -0
  127. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/message.rb +46 -0
  128. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/network_packet.rb +42 -0
  129. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_argument.rb +26 -0
  130. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_packet.rb +140 -0
  131. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_types.rb +53 -0
  132. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/server.rb +91 -0
  133. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby.rb +18 -0
  134. data/templates/developer/objects/code_reviewer.md +1 -1
  135. data/templates/developer/objects/coordinator.md +16 -3
  136. data/templates/developer/objects/debugger.md +3 -3
  137. data/templates/empty/objects/assistant.md +10 -5
  138. data/templates/minimal/objects/assistant.md +10 -5
  139. data/templates/music/extras/sonic_pi_boot.rb +220 -0
  140. data/templates/music/manifest.yml +16 -0
  141. data/templates/music/objects/conductor.md +154 -0
  142. data/templates/music/objects/sonic_pi.md +65 -0
  143. data/templates/music/primitives/play_note.rb +55 -0
  144. data/templates/music/primitives/play_sequence.rb +51 -0
  145. data/templates/music/primitives/query_sonic_pi.rb +139 -0
  146. data/templates/music/primitives/set_synth.rb +53 -0
  147. data/templates/music/primitives/set_tempo.rb +43 -0
  148. data/templates/music/primitives/stop_voice.rb +44 -0
  149. data/templates/music/primitives/support/osc_bridge.rb +33 -0
  150. data/templates/music/vendor/osc-ruby-1.1.5/LICENSE +19 -0
  151. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/address_pattern.rb +58 -0
  152. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/broadcast_client.rb +24 -0
  153. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/bundle.rb +46 -0
  154. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/client.rb +13 -0
  155. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/core_ext/time.rb +6 -0
  156. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/em_server.rb +63 -0
  157. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/message.rb +46 -0
  158. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/network_packet.rb +42 -0
  159. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_argument.rb +26 -0
  160. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_packet.rb +140 -0
  161. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_types.rb +53 -0
  162. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/server.rb +91 -0
  163. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby.rb +18 -0
  164. data/templates/music-beginner/README.md +100 -0
  165. data/templates/music-beginner/extras/README.md +27 -0
  166. data/templates/music-beginner/extras/sonic_pi_boot.rb +297 -0
  167. data/templates/music-beginner/manifest.yml +25 -0
  168. data/templates/music-beginner/objects/conductor.md +222 -0
  169. data/templates/music-beginner/objects/engine_setup.md +54 -0
  170. data/templates/music-beginner/objects/voice_bass.md +43 -0
  171. data/templates/music-beginner/objects/voice_keys.md +48 -0
  172. data/templates/music-beginner/objects/voice_pad.md +46 -0
  173. data/templates/music-beginner/objects/voice_percussion.md +45 -0
  174. data/templates/music-beginner/objects/voice_texture.md +37 -0
  175. data/templates/music-beginner/primitives/play_pattern.rb +118 -0
  176. data/templates/music-beginner/primitives/query_music_engine.rb +94 -0
  177. data/templates/music-beginner/primitives/set_instrument.rb +47 -0
  178. data/templates/music-beginner/primitives/set_tempo.rb +43 -0
  179. data/templates/music-beginner/primitives/set_voice_level.rb +46 -0
  180. data/templates/music-beginner/primitives/stop_voice.rb +37 -0
  181. data/templates/music-beginner/primitives/support/osc_bridge.rb +25 -0
  182. data/templates/music-beginner/vendor/osc-ruby-1.1.5/LICENSE +19 -0
  183. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/address_pattern.rb +58 -0
  184. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/broadcast_client.rb +24 -0
  185. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/bundle.rb +46 -0
  186. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/client.rb +13 -0
  187. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/core_ext/time.rb +6 -0
  188. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/em_server.rb +63 -0
  189. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/message.rb +46 -0
  190. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/network_packet.rb +42 -0
  191. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_argument.rb +26 -0
  192. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_packet.rb +140 -0
  193. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_types.rb +53 -0
  194. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/server.rb +91 -0
  195. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby.rb +18 -0
  196. data/templates/music-pro/extras/AUDITION.md +100 -0
  197. data/templates/music-pro/extras/sonic_pi_boot.rb +546 -0
  198. data/templates/music-pro/manifest.yml +19 -0
  199. data/templates/music-pro/objects/conductor.md +126 -0
  200. data/templates/music-pro/objects/sonic_pi.md +35 -0
  201. data/templates/music-pro/objects/voice_harmony.md +48 -0
  202. data/templates/music-pro/objects/voice_rhythm.md +43 -0
  203. data/templates/music-pro/objects/voice_texture.md +42 -0
  204. data/templates/music-pro/primitives/define_patch.rb +46 -0
  205. data/templates/music-pro/primitives/query_music_engine.rb +69 -0
  206. data/templates/music-pro/primitives/set_live_pattern.rb +70 -0
  207. data/templates/music-pro/primitives/set_tempo.rb +31 -0
  208. data/templates/music-pro/primitives/stop_voice.rb +31 -0
  209. data/templates/music-pro/primitives/support/music_protocol.rb +136 -0
  210. data/templates/music-pro/primitives/support/osc_bridge.rb +33 -0
  211. data/templates/music-pro/primitives/trigger_event.rb +44 -0
  212. data/templates/music-pro/vendor/osc-ruby-1.1.5/LICENSE +19 -0
  213. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/address_pattern.rb +58 -0
  214. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/broadcast_client.rb +24 -0
  215. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/bundle.rb +46 -0
  216. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/client.rb +13 -0
  217. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/core_ext/time.rb +6 -0
  218. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/em_server.rb +63 -0
  219. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/message.rb +46 -0
  220. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/network_packet.rb +42 -0
  221. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_argument.rb +26 -0
  222. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_packet.rb +140 -0
  223. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_types.rb +53 -0
  224. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/server.rb +91 -0
  225. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby.rb +18 -0
  226. data/templates/writer/objects/coordinator.md +14 -3
  227. data/templates/writer/objects/editor.md +1 -1
  228. data/templates/writer/objects/researcher.md +2 -2
  229. data/tools/thread-explorer.html +28 -1
  230. metadata +175 -65
  231. data/CLAUDE.md +0 -176
  232. data/Gemfile +0 -10
  233. data/Gemfile.lock +0 -233
  234. data/IMPLEMENTATION_PLAN.md +0 -1073
  235. data/Rakefile +0 -27
  236. data/design-doc-v2.md +0 -1232
  237. data/frontend/.gitignore +0 -3
  238. data/frontend/index.html +0 -17
  239. data/frontend/package-lock.json +0 -4540
  240. data/frontend/package.json +0 -36
  241. data/frontend/postcss.config.js +0 -6
  242. data/frontend/src/App.tsx +0 -103
  243. data/frontend/src/canvas/CanvasView.tsx +0 -113
  244. data/frontend/src/canvas/ForceLayout.ts +0 -115
  245. data/frontend/src/canvas/SceneManager.ts +0 -587
  246. data/frontend/src/canvas/canvasStore.ts +0 -47
  247. data/frontend/src/canvas/constants.ts +0 -95
  248. data/frontend/src/canvas/controls/CameraControls.ts +0 -98
  249. data/frontend/src/canvas/edges/MessageArc.ts +0 -149
  250. data/frontend/src/canvas/inspector/InspectorPanel.tsx +0 -31
  251. data/frontend/src/canvas/inspector/POInspector.tsx +0 -262
  252. data/frontend/src/canvas/inspector/ToolCallInspector.tsx +0 -67
  253. data/frontend/src/canvas/nodes/PONode.ts +0 -249
  254. data/frontend/src/canvas/nodes/ToolCallNode.ts +0 -116
  255. data/frontend/src/canvas/types.ts +0 -24
  256. data/frontend/src/components/ContextMenu.tsx +0 -68
  257. data/frontend/src/components/Inspector.tsx +0 -232
  258. data/frontend/src/components/MarkdownMessage.tsx +0 -155
  259. data/frontend/src/components/MethodList.tsx +0 -90
  260. data/frontend/src/components/ModelSelector.tsx +0 -115
  261. data/frontend/src/components/NotificationPanel.tsx +0 -130
  262. data/frontend/src/components/ObjectList.tsx +0 -78
  263. data/frontend/src/components/PaneSlot.tsx +0 -30
  264. data/frontend/src/components/SourcePane.tsx +0 -202
  265. data/frontend/src/components/SystemBar.tsx +0 -74
  266. data/frontend/src/components/Transcript.tsx +0 -76
  267. data/frontend/src/components/UsagePanel.tsx +0 -105
  268. data/frontend/src/components/Workspace.tsx +0 -260
  269. data/frontend/src/components/index.ts +0 -12
  270. data/frontend/src/hooks/useResize.ts +0 -55
  271. data/frontend/src/hooks/useWebSocket.ts +0 -517
  272. data/frontend/src/index.css +0 -103
  273. data/frontend/src/main.tsx +0 -10
  274. data/frontend/src/store/index.ts +0 -279
  275. data/frontend/src/types/index.ts +0 -165
  276. data/frontend/tailwind.config.js +0 -44
  277. data/frontend/tsconfig.json +0 -30
  278. data/frontend/vite.config.ts +0 -29
  279. data/lib/prompt_objects/server/public/assets/index-D1myxE0l.js +0 -4345
  280. data/lib/prompt_objects/server/public/assets/index-DdCcwC-Z.css +0 -1
  281. data/lib/prompt_objects/universal/add_primitive.rb +0 -118
  282. data/lib/prompt_objects/universal/ask_human.rb +0 -109
  283. data/lib/prompt_objects/universal/create_primitive.rb +0 -174
  284. data/lib/prompt_objects/universal/list_primitives.rb +0 -145
  285. data/lib/prompt_objects/universal/think.rb +0 -41
  286. data/prompt_objects.gemspec +0 -50
  287. data/templates/basic/.gitignore +0 -2
  288. data/templates/developer/.gitignore +0 -5
  289. data/templates/empty/.gitignore +0 -5
  290. data/templates/empty/objects/.gitkeep +0 -0
  291. data/templates/minimal/.gitignore +0 -5
  292. data/templates/writer/.gitignore +0 -5
@@ -0,0 +1,223 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "uri"
5
+ require "json"
6
+
7
+ module PromptObjects
8
+ module Primitives
9
+ # Core API client for ARC-AGI-3 remote play.
10
+ # Wraps the REST API at three.arcprize.org for listing games,
11
+ # resetting environments, and taking actions.
12
+ class Arc3Api < Primitive
13
+ BASE_URL = "https://three.arcprize.org"
14
+
15
+ STATE_LABELS = {
16
+ "NOT_PLAYED" => "Not played",
17
+ "NOT_FINISHED" => "In progress",
18
+ "WIN" => "WIN",
19
+ "GAME_OVER" => "Game over"
20
+ }.freeze
21
+
22
+ def name
23
+ "arc3_api"
24
+ end
25
+
26
+ def description
27
+ "ARC-AGI-3 remote API client. List games, reset environments, and take actions."
28
+ end
29
+
30
+ def parameters
31
+ {
32
+ type: "object",
33
+ properties: {
34
+ command: {
35
+ type: "string",
36
+ enum: %w[list_games reset step],
37
+ description: "API command: list_games, reset, or step"
38
+ },
39
+ game_id: {
40
+ type: "string",
41
+ description: "Game environment ID (e.g. 'ls20'). Required for reset and step."
42
+ },
43
+ action: {
44
+ type: "string",
45
+ description: "Action to take (ACTION1-ACTION7). Required for step."
46
+ },
47
+ x: {
48
+ type: "integer",
49
+ description: "X coordinate (0-63) for complex actions like ACTION6"
50
+ },
51
+ y: {
52
+ type: "integer",
53
+ description: "Y coordinate (0-63) for complex actions like ACTION6"
54
+ },
55
+ guid: {
56
+ type: "string",
57
+ description: "Game session ID returned by reset. Required for step."
58
+ },
59
+ card_id: {
60
+ type: "string",
61
+ description: "Scorecard ID for tracking. Optional for reset."
62
+ },
63
+ api_key: {
64
+ type: "string",
65
+ description: "API key override. Defaults to ARC_API_KEY env var."
66
+ }
67
+ },
68
+ required: ["command"]
69
+ }
70
+ end
71
+
72
+ def receive(message, context:)
73
+ command = param(message, "command")
74
+ api_key = param(message, "api_key") || ENV["ARC_API_KEY"]
75
+
76
+ return "Error: ARC_API_KEY env var not set and no api_key provided" if api_key.nil? || api_key.empty?
77
+
78
+ case command
79
+ when "list_games"
80
+ list_games(api_key)
81
+ when "reset"
82
+ game_id = param(message, "game_id")
83
+ return "Error: game_id is required for reset" unless game_id
84
+ card_id = param(message, "card_id")
85
+ reset_game(api_key, game_id, card_id)
86
+ when "step"
87
+ game_id = param(message, "game_id")
88
+ action = param(message, "action")
89
+ guid = param(message, "guid")
90
+ return "Error: game_id, action, and guid are required for step" unless game_id && action && guid
91
+ x = param(message, "x")
92
+ y = param(message, "y")
93
+ step_game(api_key, game_id, action, guid, x, y)
94
+ else
95
+ "Error: unknown command '#{command}'. Use list_games, reset, or step."
96
+ end
97
+ rescue Timeout::Error
98
+ "Error: API request timed out (30s)"
99
+ rescue SocketError => e
100
+ "Error: Could not connect to API - #{e.message}"
101
+ rescue StandardError => e
102
+ "Error: #{e.class} - #{e.message}"
103
+ end
104
+
105
+ private
106
+
107
+ def param(message, key)
108
+ message[key.to_sym] || message[key]
109
+ end
110
+
111
+ def list_games(api_key)
112
+ uri = URI("#{BASE_URL}/api/games")
113
+ request = Net::HTTP::Get.new(uri)
114
+ request["X-Api-Key"] = api_key
115
+
116
+ response = http_request(uri, request)
117
+
118
+ case response
119
+ when Net::HTTPSuccess
120
+ data = JSON.parse(response.body)
121
+ format_games_list(data)
122
+ else
123
+ "Error: HTTP #{response.code} - #{response.body}"
124
+ end
125
+ end
126
+
127
+ def reset_game(api_key, game_id, card_id)
128
+ uri = URI("#{BASE_URL}/api/cmd/RESET")
129
+ body = { "game_id" => game_id }
130
+ body["card_id"] = card_id if card_id
131
+
132
+ response = post_json(uri, api_key, body)
133
+
134
+ case response
135
+ when Net::HTTPSuccess
136
+ data = JSON.parse(response.body)
137
+ format_frame_response(data, game_id, "RESET")
138
+ else
139
+ "Error: HTTP #{response.code} - #{response.body}"
140
+ end
141
+ end
142
+
143
+ def step_game(api_key, game_id, action, guid, x, y)
144
+ uri = URI("#{BASE_URL}/api/cmd/#{action}")
145
+ body = { "game_id" => game_id, "guid" => guid }
146
+ body["x"] = x if x
147
+ body["y"] = y if y
148
+
149
+ response = post_json(uri, api_key, body)
150
+
151
+ case response
152
+ when Net::HTTPSuccess
153
+ data = JSON.parse(response.body)
154
+ format_frame_response(data, game_id, action)
155
+ else
156
+ "Error: HTTP #{response.code} - #{response.body}"
157
+ end
158
+ end
159
+
160
+ def post_json(uri, api_key, body)
161
+ request = Net::HTTP::Post.new(uri)
162
+ request["X-Api-Key"] = api_key
163
+ request["Content-Type"] = "application/json"
164
+ request.body = JSON.generate(body)
165
+ http_request(uri, request)
166
+ end
167
+
168
+ def http_request(uri, request)
169
+ http = Net::HTTP.new(uri.host, uri.port)
170
+ http.use_ssl = (uri.scheme == "https")
171
+ http.open_timeout = 10
172
+ http.read_timeout = 30
173
+ http.request(request)
174
+ end
175
+
176
+ def format_games_list(data)
177
+ return JSON.pretty_generate(data) unless data.is_a?(Array)
178
+
179
+ lines = ["Available ARC-AGI-3 games (#{data.length}):"]
180
+ data.each do |game|
181
+ if game.is_a?(Hash)
182
+ id = game["game_id"] || game["id"] || game.keys.first
183
+ lines << " - #{id}"
184
+ else
185
+ lines << " - #{game}"
186
+ end
187
+ end
188
+ lines.join("\n")
189
+ end
190
+
191
+ def format_frame_response(data, game_id, action)
192
+ state_raw = data["state"] || "UNKNOWN"
193
+ state = STATE_LABELS[state_raw] || state_raw
194
+
195
+ result = {
196
+ "game_id" => game_id,
197
+ "action" => action,
198
+ "state" => state,
199
+ "levels_completed" => data["levels_completed"],
200
+ "win_levels" => data["win_levels"],
201
+ "available_actions" => data["available_actions"],
202
+ "guid" => data["guid"],
203
+ "full_reset" => data["full_reset"],
204
+ "frame" => data["frame"]
205
+ }
206
+
207
+ # Summarize frame dimensions instead of dumping the raw data
208
+ if data["frame"].is_a?(Array)
209
+ frame = data["frame"]
210
+ if frame[0].is_a?(Array) && frame[0][0].is_a?(Array)
211
+ result["frame_count"] = frame.length
212
+ result["frame_dimensions"] = "#{frame[0].length}x#{frame[0][0].length}"
213
+ elsif frame[0].is_a?(Array)
214
+ result["frame_count"] = 1
215
+ result["frame_dimensions"] = "#{frame.length}x#{frame[0].length}"
216
+ end
217
+ end
218
+
219
+ JSON.pretty_generate(result)
220
+ end
221
+ end
222
+ end
223
+ end
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "uri"
5
+ require "json"
6
+
7
+ module PromptObjects
8
+ module Primitives
9
+ # Manages ARC-AGI-3 scorecards for tracking performance across games.
10
+ class Arc3Scorecard < Primitive
11
+ BASE_URL = "https://three.arcprize.org"
12
+
13
+ # Track the active scorecard across calls within a session
14
+ @@active_card_id = nil
15
+
16
+ def name
17
+ "arc3_scorecard"
18
+ end
19
+
20
+ def description
21
+ "Manage ARC-AGI-3 scorecards. Open a scorecard before playing, close it to get results."
22
+ end
23
+
24
+ def parameters
25
+ {
26
+ type: "object",
27
+ properties: {
28
+ command: {
29
+ type: "string",
30
+ enum: %w[open close status],
31
+ description: "open: create new scorecard, close: finish and get results, status: show active card"
32
+ },
33
+ api_key: {
34
+ type: "string",
35
+ description: "API key override. Defaults to ARC_API_KEY env var."
36
+ }
37
+ },
38
+ required: ["command"]
39
+ }
40
+ end
41
+
42
+ def receive(message, context:)
43
+ command = param(message, "command")
44
+ api_key = param(message, "api_key") || ENV["ARC_API_KEY"]
45
+
46
+ return "Error: ARC_API_KEY env var not set and no api_key provided" if api_key.nil? || api_key.empty?
47
+
48
+ case command
49
+ when "open"
50
+ open_scorecard(api_key)
51
+ when "close"
52
+ close_scorecard(api_key)
53
+ when "status"
54
+ scorecard_status
55
+ else
56
+ "Error: unknown command '#{command}'. Use open, close, or status."
57
+ end
58
+ rescue Timeout::Error
59
+ "Error: API request timed out (30s)"
60
+ rescue SocketError => e
61
+ "Error: Could not connect to API - #{e.message}"
62
+ rescue StandardError => e
63
+ "Error: #{e.class} - #{e.message}"
64
+ end
65
+
66
+ private
67
+
68
+ def param(message, key)
69
+ message[key.to_sym] || message[key]
70
+ end
71
+
72
+ def open_scorecard(api_key)
73
+ uri = URI("#{BASE_URL}/api/scorecard/open")
74
+ request = Net::HTTP::Post.new(uri)
75
+ request["X-Api-Key"] = api_key
76
+ request["Content-Type"] = "application/json"
77
+ request.body = "{}"
78
+
79
+ response = http_request(uri, request)
80
+
81
+ case response
82
+ when Net::HTTPSuccess
83
+ data = JSON.parse(response.body)
84
+ @@active_card_id = data["card_id"] || data["id"]
85
+ "Scorecard opened. card_id: #{@@active_card_id}\nPass this card_id to arc3_api reset commands to track scores."
86
+ else
87
+ "Error: HTTP #{response.code} - #{response.body}"
88
+ end
89
+ end
90
+
91
+ def close_scorecard(api_key)
92
+ return "Error: No active scorecard. Use 'open' first." unless @@active_card_id
93
+
94
+ uri = URI("#{BASE_URL}/api/scorecard/close")
95
+ request = Net::HTTP::Post.new(uri)
96
+ request["X-Api-Key"] = api_key
97
+ request["Content-Type"] = "application/json"
98
+ request.body = JSON.generate({ "card_id" => @@active_card_id })
99
+
100
+ response = http_request(uri, request)
101
+
102
+ card_id = @@active_card_id
103
+ @@active_card_id = nil
104
+
105
+ case response
106
+ when Net::HTTPSuccess
107
+ data = JSON.parse(response.body)
108
+ "Scorecard #{card_id} closed.\n\nResults:\n#{JSON.pretty_generate(data)}"
109
+ else
110
+ "Error: HTTP #{response.code} - #{response.body}"
111
+ end
112
+ end
113
+
114
+ def scorecard_status
115
+ if @@active_card_id
116
+ "Active scorecard: #{@@active_card_id}"
117
+ else
118
+ "No active scorecard. Use 'open' to create one."
119
+ end
120
+ end
121
+
122
+ def http_request(uri, request)
123
+ http = Net::HTTP.new(uri.host, uri.port)
124
+ http.use_ssl = (uri.scheme == "https")
125
+ http.open_timeout = 10
126
+ http.read_timeout = 30
127
+ http.request(request)
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PromptObjects
4
+ module Primitives
5
+ # Compares two ARC-AGI-3 frames cell-by-cell.
6
+ # Reports changed positions with old/new values and a quadrant summary.
7
+ class FrameDiff < Primitive
8
+ execution_policy parallel_safe: true, side_effect: :read, workload: :cpu
9
+
10
+ def name
11
+ "frame_diff"
12
+ end
13
+
14
+ def description
15
+ "Compare two ARC-AGI-3 frames cell by cell. Shows which cells differ, with quadrant summary."
16
+ end
17
+
18
+ def parameters
19
+ {
20
+ type: "object",
21
+ properties: {
22
+ frame_a: {
23
+ type: "array",
24
+ items: { type: "array", items: { type: "integer" } },
25
+ description: "First frame (2D array)"
26
+ },
27
+ frame_b: {
28
+ type: "array",
29
+ items: { type: "array", items: { type: "integer" } },
30
+ description: "Second frame (2D array)"
31
+ },
32
+ downsample: {
33
+ type: "integer",
34
+ description: "Downsample both frames before diffing (default 2, reduces noise)"
35
+ }
36
+ },
37
+ required: ["frame_a", "frame_b"]
38
+ }
39
+ end
40
+
41
+ def receive(message, context:)
42
+ frame_a = message[:frame_a] || message["frame_a"]
43
+ frame_b = message[:frame_b] || message["frame_b"]
44
+ downsample = (message[:downsample] || message["downsample"] || 2).to_i
45
+
46
+ return "Error: frame_a and frame_b are required" unless frame_a.is_a?(Array) && frame_b.is_a?(Array)
47
+
48
+ if downsample > 1
49
+ frame_a = downsample_frame(frame_a, downsample)
50
+ frame_b = downsample_frame(frame_b, downsample)
51
+ end
52
+
53
+ rows_a = frame_a.length
54
+ cols_a = frame_a[0]&.length || 0
55
+ rows_b = frame_b.length
56
+ cols_b = frame_b[0]&.length || 0
57
+
58
+ lines = []
59
+
60
+ if rows_a != rows_b || cols_a != cols_b
61
+ lines << "DIMENSION MISMATCH: #{rows_a}x#{cols_a} vs #{rows_b}x#{cols_b}"
62
+ lines << ""
63
+ end
64
+
65
+ diffs = []
66
+ matching = 0
67
+ compare_rows = [rows_a, rows_b].min
68
+ compare_cols = [cols_a, cols_b].min
69
+
70
+ # Track changes by quadrant
71
+ quadrants = { "top-left" => 0, "top-right" => 0, "bottom-left" => 0, "bottom-right" => 0 }
72
+ mid_r = compare_rows / 2
73
+ mid_c = compare_cols / 2
74
+
75
+ compare_rows.times do |r|
76
+ compare_cols.times do |c|
77
+ if frame_a[r][c] == frame_b[r][c]
78
+ matching += 1
79
+ else
80
+ diffs << { r: r, c: c, from: frame_a[r][c], to: frame_b[r][c] }
81
+ q = (r < mid_r ? "top" : "bottom") + "-" + (c < mid_c ? "left" : "right")
82
+ quadrants[q] += 1
83
+ end
84
+ end
85
+ end
86
+
87
+ total = compare_rows * compare_cols
88
+ lines << "#{matching}/#{total} cells match (#{diffs.length} differ)"
89
+
90
+ if downsample > 1
91
+ lines << "(compared at #{downsample}x downsampled resolution)"
92
+ end
93
+
94
+ if diffs.empty?
95
+ lines << "IDENTICAL"
96
+ else
97
+ lines << ""
98
+ diffs.first(50).each do |d|
99
+ lines << " (#{d[:r]},#{d[:c]}): #{d[:from]} -> #{d[:to]}"
100
+ end
101
+ lines << " ... and #{diffs.length - 50} more" if diffs.length > 50
102
+
103
+ # Quadrant summary
104
+ active_quads = quadrants.select { |_, v| v > 0 }
105
+ unless active_quads.empty?
106
+ lines << ""
107
+ lines << "Changes by region:"
108
+ active_quads.sort_by { |_, v| -v }.each do |quad, count|
109
+ lines << " #{quad}: #{count} cells changed"
110
+ end
111
+ end
112
+ end
113
+
114
+ lines.join("\n")
115
+ end
116
+
117
+ private
118
+
119
+ def downsample_frame(frame, factor)
120
+ rows = frame.length
121
+ cols = frame[0]&.length || 0
122
+ out_rows = rows / factor
123
+ out_cols = cols / factor
124
+
125
+ out_rows.times.map do |r|
126
+ out_cols.times.map do |c|
127
+ block = []
128
+ factor.times do |dr|
129
+ factor.times do |dc|
130
+ src_r = r * factor + dr
131
+ src_c = c * factor + dc
132
+ val = frame[src_r] && frame[src_r][src_c]
133
+ block << val if val
134
+ end
135
+ end
136
+ non_bg = block.reject { |v| v == 0 }
137
+ non_bg.empty? ? 0 : non_bg.tally.max_by { |_, count| count }[0]
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module PromptObjects
6
+ module Primitives
7
+ # Extracts statistics from an ARC-AGI-3 frame: dimensions, color distribution,
8
+ # dominant colors, and approximate region descriptions.
9
+ class FrameInfo < Primitive
10
+ execution_policy parallel_safe: true, side_effect: :read, workload: :cpu
11
+
12
+ COLOR_NAMES = {
13
+ 0 => "white", 1 => "light", 2 => "grey", 3 => "med_grey", 4 => "dark",
14
+ 5 => "black", 6 => "magenta", 7 => "pink", 8 => "red", 9 => "blue",
15
+ 10 => "lt_blue", 11 => "yellow", 12 => "orange", 13 => "maroon", 14 => "green", 15 => "purple"
16
+ }.freeze
17
+
18
+ def name
19
+ "frame_info"
20
+ end
21
+
22
+ def description
23
+ "Get frame dimensions, color distribution, dominant colors, and region descriptions."
24
+ end
25
+
26
+ def parameters
27
+ {
28
+ type: "object",
29
+ properties: {
30
+ frame: {
31
+ type: "array",
32
+ items: { type: "array", items: { type: "integer" } },
33
+ description: "2D array of integers 0-15"
34
+ }
35
+ },
36
+ required: ["frame"]
37
+ }
38
+ end
39
+
40
+ def receive(message, context:)
41
+ frame = message[:frame] || message["frame"]
42
+ return "Error: frame is required" unless frame.is_a?(Array)
43
+ return "Error: frame is empty" if frame.empty?
44
+
45
+ rows = frame.length
46
+ cols = frame[0]&.length || 0
47
+ flat = frame.flatten
48
+ color_counts = flat.tally.sort.to_h
49
+
50
+ # Dominant colors (top 5 by count)
51
+ dominant = color_counts.sort_by { |_, v| -v }.first(5).map do |color, count|
52
+ pct = (count.to_f / flat.length * 100).round(1)
53
+ "#{COLOR_NAMES[color] || color}(#{color}): #{count} cells (#{pct}%)"
54
+ end
55
+
56
+ # Region analysis (divide into 4 quadrants)
57
+ mid_r = rows / 2
58
+ mid_c = cols / 2
59
+ regions = {
60
+ "top-left" => quadrant_summary(frame, 0, mid_r, 0, mid_c),
61
+ "top-right" => quadrant_summary(frame, 0, mid_r, mid_c, cols),
62
+ "bottom-left" => quadrant_summary(frame, mid_r, rows, 0, mid_c),
63
+ "bottom-right" => quadrant_summary(frame, mid_r, rows, mid_c, cols)
64
+ }
65
+
66
+ result = {
67
+ dimensions: "#{rows}x#{cols}",
68
+ total_cells: flat.length,
69
+ unique_colors: color_counts.length,
70
+ color_distribution: color_counts.map { |c, n| [COLOR_NAMES[c] || c.to_s, n] }.to_h,
71
+ dominant_colors: dominant,
72
+ regions: regions
73
+ }
74
+
75
+ JSON.pretty_generate(result)
76
+ end
77
+
78
+ private
79
+
80
+ def quadrant_summary(frame, r_start, r_end, c_start, c_end)
81
+ cells = []
82
+ (r_start...r_end).each do |r|
83
+ (c_start...c_end).each do |c|
84
+ cells << frame[r][c] if frame[r] && frame[r][c]
85
+ end
86
+ end
87
+
88
+ return "empty" if cells.empty?
89
+
90
+ counts = cells.tally
91
+ top = counts.sort_by { |_, v| -v }.first(3)
92
+ top.map { |color, count| "#{COLOR_NAMES[color] || color}(#{(count.to_f / cells.length * 100).round(0)}%)" }.join(", ")
93
+ end
94
+ end
95
+ end
96
+ end