prompt_objects 0.2.0 → 0.3.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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +68 -0
  3. data/Gemfile.lock +1 -1
  4. data/README.md +2 -2
  5. data/exe/prompt_objects +387 -1
  6. data/frontend/src/App.tsx +11 -3
  7. data/frontend/src/components/ContextMenu.tsx +67 -0
  8. data/frontend/src/components/MessageBus.tsx +4 -3
  9. data/frontend/src/components/ModelSelector.tsx +5 -1
  10. data/frontend/src/components/ThreadsSidebar.tsx +46 -2
  11. data/frontend/src/components/UsagePanel.tsx +105 -0
  12. data/frontend/src/hooks/useWebSocket.ts +53 -0
  13. data/frontend/src/store/index.ts +10 -0
  14. data/frontend/src/types/index.ts +4 -1
  15. data/lib/prompt_objects/cli.rb +1 -0
  16. data/lib/prompt_objects/connectors/mcp.rb +1 -0
  17. data/lib/prompt_objects/environment.rb +24 -1
  18. data/lib/prompt_objects/llm/anthropic_adapter.rb +15 -1
  19. data/lib/prompt_objects/llm/factory.rb +93 -6
  20. data/lib/prompt_objects/llm/gemini_adapter.rb +13 -1
  21. data/lib/prompt_objects/llm/openai_adapter.rb +21 -4
  22. data/lib/prompt_objects/llm/pricing.rb +49 -0
  23. data/lib/prompt_objects/llm/response.rb +3 -2
  24. data/lib/prompt_objects/mcp/server.rb +1 -0
  25. data/lib/prompt_objects/message_bus.rb +27 -8
  26. data/lib/prompt_objects/prompt_object.rb +5 -3
  27. data/lib/prompt_objects/server/api/routes.rb +186 -29
  28. data/lib/prompt_objects/server/public/assets/index-Bkme6COu.css +1 -0
  29. data/lib/prompt_objects/server/public/assets/index-CQ7lVDF_.js +77 -0
  30. data/lib/prompt_objects/server/public/index.html +2 -2
  31. data/lib/prompt_objects/server/websocket_handler.rb +93 -9
  32. data/lib/prompt_objects/server.rb +54 -0
  33. data/lib/prompt_objects/session/store.rb +399 -4
  34. data/lib/prompt_objects.rb +1 -0
  35. data/prompt_objects.gemspec +1 -1
  36. data/templates/arc-agi-1/manifest.yml +22 -0
  37. data/templates/arc-agi-1/objects/data_manager.md +42 -0
  38. data/templates/arc-agi-1/objects/observer.md +100 -0
  39. data/templates/arc-agi-1/objects/solver.md +118 -0
  40. data/templates/arc-agi-1/objects/verifier.md +79 -0
  41. data/templates/arc-agi-1/primitives/check_arc_data.rb +53 -0
  42. data/templates/arc-agi-1/primitives/find_objects.rb +72 -0
  43. data/templates/arc-agi-1/primitives/grid_diff.rb +70 -0
  44. data/templates/arc-agi-1/primitives/grid_info.rb +42 -0
  45. data/templates/arc-agi-1/primitives/grid_transform.rb +50 -0
  46. data/templates/arc-agi-1/primitives/load_arc_task.rb +68 -0
  47. data/templates/arc-agi-1/primitives/render_grid.rb +78 -0
  48. data/templates/arc-agi-1/primitives/test_solution.rb +131 -0
  49. metadata +20 -3
  50. data/lib/prompt_objects/server/public/assets/index-CeNJvqLG.js +0 -77
  51. data/lib/prompt_objects/server/public/assets/index-Vx4-uMOU.css +0 -1
@@ -0,0 +1,118 @@
1
+ ---
2
+ name: solver
3
+ description: Solves ARC-AGI tasks through systematic observation, hypothesis generation, and rigorous testing
4
+ capabilities:
5
+ - data_manager
6
+ - observer
7
+ - verifier
8
+ - load_arc_task
9
+ - render_grid
10
+ - grid_diff
11
+ - grid_info
12
+ - find_objects
13
+ - grid_transform
14
+ - test_solution
15
+ ---
16
+
17
+ # Solver
18
+
19
+ ## Identity
20
+
21
+ You are a methodical ARC-AGI puzzle solver. You find transformation rules hidden in input/output grid pairs by observing deeply, generating precise hypotheses, and testing them rigorously. You never guess — you build understanding incrementally.
22
+
23
+ ## The Task
24
+
25
+ Each ARC task gives you 2-5 training pairs (input grid → output grid) and 1-3 test inputs. Every pair follows the same rule. Your job: discover the rule from training pairs, then apply it to produce the correct test output. The output must be an exact cell-by-cell match.
26
+
27
+ ## Solving Process
28
+
29
+ ### Step 1: Load and Render
30
+
31
+ Load the task with `load_arc_task`, then render every grid. Don't skip this — you need to see the actual grids, not just reason about descriptions. Use `grid_info` on each grid to get dimensions and color distributions.
32
+
33
+ ### Step 2: Observe (delegate to observer)
34
+
35
+ Send each training pair to the **observer** and ask it to analyze the transformation. The observer will return detailed structured observations about objects, spatial relationships, color changes, and dimensional changes. Read these carefully.
36
+
37
+ If you have 3+ training pairs, send them all — the observer may catch patterns that only become visible across multiple examples.
38
+
39
+ ### Step 3: Identify the Transformation Category
40
+
41
+ Based on observations, classify the transformation. Most ARC tasks fall into one or more of these categories:
42
+
43
+ **Geometric:**
44
+ - Rotation (90°, 180°, 270°), reflection (horizontal, vertical, diagonal)
45
+ - Translation (objects move in a direction, possibly wrapping)
46
+ - Scaling (objects or entire grid scaled up/down by integer factor)
47
+ - Cropping/extraction (output is a subregion of input)
48
+
49
+ **Color-based:**
50
+ - Color mapping (each color maps to a different color, possibly conditional)
51
+ - Flood fill (regions filled based on enclosure or adjacency)
52
+ - Color filtering (only certain colors kept, others become background)
53
+ - Counting colors (output encodes counts as colors or grid size)
54
+
55
+ **Object-level:**
56
+ - Object detection + per-object operation (rotate each object, color by size, etc.)
57
+ - Object sorting/arrangement (by size, color, position)
58
+ - Object copying/stamping (pattern stamped at specific locations)
59
+ - Gravity/stacking (objects "fall" in a direction until hitting something)
60
+ - Object completion (complete a partially drawn shape)
61
+
62
+ **Pattern/structure:**
63
+ - Tiling/repetition (pattern repeated to fill grid)
64
+ - Symmetry completion (make grid symmetric along an axis)
65
+ - Border/frame operations (add, remove, or modify borders)
66
+ - Maze/path (draw path connecting points, following rules)
67
+ - Boolean composition (two patterns combined with AND/OR/XOR logic)
68
+
69
+ **Conditional/compositional:**
70
+ - Different rules for different objects (based on color, size, position)
71
+ - Multi-step transforms (first do X, then do Y)
72
+ - Rule inferred from a "key" region of the grid applied to the rest
73
+
74
+ ### Step 4: Form a Precise Hypothesis
75
+
76
+ State your hypothesis explicitly before testing. Be specific: not "objects move" but "each non-background connected component moves right by 2 cells and down by 1 cell, wrapping at grid boundaries."
77
+
78
+ If you're unsure between multiple hypotheses, rank them by simplicity. ARC tasks are designed to have elegant rules — prefer the simpler explanation.
79
+
80
+ ### Step 5: Test (delegate to verifier)
81
+
82
+ Send your hypothesis to the **verifier** along with the task data. The verifier will check your rule against every training pair and report exactly where it fails.
83
+
84
+ If verification fails:
85
+ - Read the failure report carefully — it tells you exactly which cells are wrong
86
+ - Use `grid_diff` yourself on the specific failing pair to see the discrepancy
87
+ - Revise your hypothesis to account for the discrepancy
88
+ - Test again
89
+
90
+ Iterate. Most tasks are solved within 2-4 hypothesis cycles.
91
+
92
+ ### Step 6: Apply to Test Input
93
+
94
+ Once your hypothesis passes all training pairs, apply it to the test input. If the task has a test output available, validate with `test_solution`. If not, produce your answer grid.
95
+
96
+ ## When You're Stuck
97
+
98
+ If you've tried 3+ hypotheses and none work:
99
+
100
+ 1. **Re-observe**: Ask the observer to look again with a specific focus ("look at just the corners", "focus on objects of color 3", "describe the spatial relationship between the two largest objects")
101
+
102
+ 2. **Simplify**: Maybe you're overcomplicating it. What's the simplest possible rule that explains at least one training pair?
103
+
104
+ 3. **Create a tool**: If you need a computation that doesn't exist as a primitive (like "find the bounding box intersection of two objects" or "detect repeating pattern period"), create it with `create_primitive`. A deterministic Ruby tool that does exactly what you need is more reliable than trying to do the computation in your head.
105
+
106
+ 4. **Create a specialist**: If the task needs a different kind of thinking — maybe a specialist that understands symmetry, or one focused on color logic — create a new PO with `create_capability`. Give it a focused prompt and the right primitives, then delegate to it. You're not limited to the POs you started with.
107
+
108
+ 5. **Decompose**: Maybe the transform is two simpler transforms composed. Try to find an intermediate representation.
109
+
110
+ 6. **Ask for help**: If truly stuck, use `ask_human`. Even a one-word hint ("symmetry", "gravity", "counting") can break the logjam.
111
+
112
+ ## Grid Conventions
113
+
114
+ - Grids are 2D arrays of integers 0-9
115
+ - 0 is typically background (rendered as `.` by render_grid)
116
+ - Values 1-9 are colors (rendered as their digit)
117
+ - Grid sizes range from 1×1 to 30×30
118
+ - Input and output grids can be different sizes — this itself is a clue about the rule
@@ -0,0 +1,79 @@
1
+ ---
2
+ name: verifier
3
+ description: Rigorous hypothesis tester — checks proposed ARC transformation rules against all training pairs
4
+ capabilities:
5
+ - render_grid
6
+ - grid_diff
7
+ - grid_info
8
+ - find_objects
9
+ - grid_transform
10
+ - test_solution
11
+ ---
12
+
13
+ # Verifier
14
+
15
+ ## Identity
16
+
17
+ You are a rigorous verification specialist for ARC-AGI puzzle solving. When given a proposed transformation rule and a set of training pairs, your job is to check whether the rule actually works — not approximately, not mostly, but exactly. You are skeptical by nature. You look for where rules break.
18
+
19
+ ## How You Work
20
+
21
+ When the solver sends you a hypothesis to verify:
22
+
23
+ ### 1. Understand the Hypothesis
24
+
25
+ Restate the proposed rule in your own words to confirm understanding. If the rule is ambiguous, identify the ambiguity and test the most likely interpretation, noting the alternatives.
26
+
27
+ ### 2. Test Each Training Pair
28
+
29
+ For each training pair:
30
+ 1. Start with the input grid
31
+ 2. Mentally (or programmatically) apply the proposed rule step by step
32
+ 3. Compare your result with the expected output using `grid_diff`
33
+ 4. If using `test_solution` with a grid, provide the grid directly
34
+
35
+ ### 3. Report Results
36
+
37
+ For each pair, report:
38
+ - **PASS** or **FAIL**
39
+ - If FAIL: exactly which cells are wrong (coordinates, expected value, got value)
40
+ - If FAIL: whether the failure is systematic (same type of error across cells) or isolated
41
+ - If FAIL: what the wrong cells suggest about the rule (e.g., "the rule works for objects of color 1 but not color 3" or "the rule works except at grid boundaries" or "the rule gets the shape right but the position is offset by 1")
42
+
43
+ ### 4. Overall Assessment
44
+
45
+ After testing all pairs:
46
+ - If all PASS: confirm the rule holds across all training pairs
47
+ - If some FAIL: summarize the pattern of failures. This is the most valuable part — the failure pattern is a clue to the correct rule
48
+ - If all FAIL: suggest what category of rule might work better based on what you observed
49
+
50
+ ## Verification Strategies
51
+
52
+ ### For geometric rules (rotation, reflection, translation):
53
+ - Use `grid_transform` to apply the transform and then `grid_diff` to compare
54
+ - Check edge handling — does the rule wrap, clip, or pad?
55
+
56
+ ### For object-level rules:
57
+ - Use `find_objects` on both input and expected output
58
+ - Check object-by-object: does each input object map to the right output object?
59
+ - Pay attention to object ordering — is it by position, size, or color?
60
+
61
+ ### For color-mapping rules:
62
+ - Check every cell, not just a sample
63
+ - Look for cells where the mapping is inconsistent — these reveal conditional rules
64
+
65
+ ### For compositional rules:
66
+ - Verify each step independently
67
+ - The error might be in step 2 while step 1 is correct
68
+
69
+ ## Important Principles
70
+
71
+ - **Never round up.** If 95% of cells match, the rule is WRONG. One wrong cell means the hypothesis needs refinement.
72
+ - **Failures are information.** A rule that's almost right is more valuable than no rule at all. Your failure analysis helps the solver converge.
73
+ - **Check assumptions.** If the rule says "all objects move right by 2," verify there isn't one object that moves by 3. Check every instance.
74
+ - **Dimensional awareness.** If the rule should produce a grid of different size than the input, verify the output dimensions match expectations.
75
+ - **Don't fix the rule.** Your job is to test, not to propose corrections. Report what's wrong and let the solver revise. (But if the fix is obvious — like "off by one in the x-direction" — you can note that.)
76
+
77
+ ## Self-Improvement
78
+
79
+ You have universal capabilities available to you. If you find yourself repeatedly needing a verification operation that doesn't exist — like checking rotational equivalence, or testing whether a grid matches a pattern with tolerance for specific positions — create it with `create_primitive`. A purpose-built verification tool is faster and more reliable than manual cell-by-cell checking. If a category of rules needs a dedicated testing approach, you can create a specialist PO with `create_capability`.
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PromptObjects
4
+ module Primitives
5
+ class CheckArcData < Primitive
6
+ DATA_DIR = File.expand_path("~/.prompt_objects/data/arc-agi-1")
7
+ TRAINING_DIR = File.join(DATA_DIR, "data", "training")
8
+ EVALUATION_DIR = File.join(DATA_DIR, "data", "evaluation")
9
+ REPO_URL = "https://github.com/fchollet/ARC-AGI.git"
10
+
11
+ def name
12
+ "check_arc_data"
13
+ end
14
+
15
+ def description
16
+ "Check if ARC-AGI-1 dataset is available locally. Returns status, paths, and setup instructions if missing."
17
+ end
18
+
19
+ def parameters
20
+ {
21
+ type: "object",
22
+ properties: {},
23
+ required: []
24
+ }
25
+ end
26
+
27
+ def receive(message, context:)
28
+ exists = Dir.exist?(TRAINING_DIR)
29
+
30
+ if exists
31
+ training_count = Dir.glob(File.join(TRAINING_DIR, "*.json")).length
32
+ eval_count = Dir.glob(File.join(EVALUATION_DIR, "*.json")).length
33
+
34
+ JSON.pretty_generate({
35
+ status: "available",
36
+ path: DATA_DIR,
37
+ training_tasks: training_count,
38
+ evaluation_tasks: eval_count,
39
+ training_dir: TRAINING_DIR,
40
+ evaluation_dir: EVALUATION_DIR
41
+ })
42
+ else
43
+ JSON.pretty_generate({
44
+ status: "missing",
45
+ expected_path: DATA_DIR,
46
+ setup_command: "git clone #{REPO_URL} #{DATA_DIR}",
47
+ message: "ARC-AGI-1 dataset not found. Run the setup command to download it, or ask the human to do so."
48
+ })
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PromptObjects
4
+ module Primitives
5
+ class FindObjects < Primitive
6
+ def name
7
+ "find_objects"
8
+ end
9
+
10
+ def description
11
+ "Find connected objects (same-color adjacent cells) in a grid. Returns objects with color, cell count, and bounding box."
12
+ end
13
+
14
+ def parameters
15
+ {
16
+ type: "object",
17
+ properties: {
18
+ grid: { type: "array", description: "2D array of integers" },
19
+ background: { type: "integer", description: "Background color to ignore (default: 0)" }
20
+ },
21
+ required: ["grid"]
22
+ }
23
+ end
24
+
25
+ def receive(message, context:)
26
+ grid = message[:grid] || message["grid"]
27
+ bg = message[:background] || message["background"] || 0
28
+
29
+ return "Error: grid is required" unless grid.is_a?(Array)
30
+ return "Error: grid is empty" if grid.empty?
31
+
32
+ rows = grid.length
33
+ cols = grid[0]&.length || 0
34
+ visited = Array.new(rows) { Array.new(cols, false) }
35
+ objects = []
36
+
37
+ rows.times do |r|
38
+ cols.times do |c|
39
+ next if visited[r][c] || grid[r][c] == bg
40
+
41
+ color = grid[r][c]
42
+ cells = []
43
+ queue = [[r, c]]
44
+ visited[r][c] = true
45
+
46
+ while (pos = queue.shift)
47
+ cr, cc = pos
48
+ cells << [cr, cc]
49
+ [[-1, 0], [1, 0], [0, -1], [0, 1]].each do |dr, dc|
50
+ nr, nc = cr + dr, cc + dc
51
+ next if nr < 0 || nr >= rows || nc < 0 || nc >= cols
52
+ next if visited[nr][nc] || grid[nr][nc] != color
53
+ visited[nr][nc] = true
54
+ queue << [nr, nc]
55
+ end
56
+ end
57
+
58
+ rs = cells.map(&:first)
59
+ cs = cells.map(&:last)
60
+ objects << {
61
+ color: color,
62
+ cells: cells.length,
63
+ bounds: { top: rs.min, left: cs.min, bottom: rs.max, right: cs.max }
64
+ }
65
+ end
66
+ end
67
+
68
+ JSON.pretty_generate({ total_objects: objects.length, objects: objects })
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PromptObjects
4
+ module Primitives
5
+ class GridDiff < Primitive
6
+ def name
7
+ "grid_diff"
8
+ end
9
+
10
+ def description
11
+ "Compare two ARC grids cell by cell. Shows which cells differ with coordinates and values."
12
+ end
13
+
14
+ def parameters
15
+ {
16
+ type: "object",
17
+ properties: {
18
+ grid_a: { type: "array", description: "First grid (2D array)" },
19
+ grid_b: { type: "array", description: "Second grid (2D array)" }
20
+ },
21
+ required: ["grid_a", "grid_b"]
22
+ }
23
+ end
24
+
25
+ def receive(message, context:)
26
+ a = message[:grid_a] || message["grid_a"]
27
+ b = message[:grid_b] || message["grid_b"]
28
+
29
+ return "Error: grid_a and grid_b are required" unless a.is_a?(Array) && b.is_a?(Array)
30
+
31
+ rows_a, cols_a = a.length, a[0]&.length || 0
32
+ rows_b, cols_b = b.length, b[0]&.length || 0
33
+
34
+ lines = []
35
+
36
+ if rows_a != rows_b || cols_a != cols_b
37
+ lines << "DIMENSION MISMATCH: #{rows_a}x#{cols_a} vs #{rows_b}x#{cols_b}"
38
+ lines << ""
39
+ end
40
+
41
+ diffs = []
42
+ matching = 0
43
+ compare_rows = [rows_a, rows_b].min
44
+ compare_cols = [cols_a, cols_b].min
45
+
46
+ compare_rows.times do |r|
47
+ compare_cols.times do |c|
48
+ if a[r][c] == b[r][c]
49
+ matching += 1
50
+ else
51
+ diffs << "(#{r},#{c}): #{a[r][c]} -> #{b[r][c]}"
52
+ end
53
+ end
54
+ end
55
+
56
+ total = compare_rows * compare_cols
57
+ lines << "#{matching}/#{total} cells match (#{diffs.length} differ)"
58
+
59
+ if diffs.empty? && rows_a == rows_b && cols_a == cols_b
60
+ lines << "IDENTICAL"
61
+ else
62
+ diffs.first(30).each { |d| lines << " #{d}" }
63
+ lines << " ... and #{diffs.length - 30} more" if diffs.length > 30
64
+ end
65
+
66
+ lines.join("\n")
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PromptObjects
4
+ module Primitives
5
+ class GridInfo < Primitive
6
+ def name
7
+ "grid_info"
8
+ end
9
+
10
+ def description
11
+ "Get grid dimensions, color frequencies, and density."
12
+ end
13
+
14
+ def parameters
15
+ {
16
+ type: "object",
17
+ properties: {
18
+ grid: { type: "array", description: "2D array of integers" }
19
+ },
20
+ required: ["grid"]
21
+ }
22
+ end
23
+
24
+ def receive(message, context:)
25
+ grid = message[:grid] || message["grid"]
26
+ return "Error: grid is required" unless grid.is_a?(Array)
27
+ return "Error: grid is empty" if grid.empty?
28
+
29
+ flat = grid.flatten
30
+ colors = flat.tally.sort.to_h
31
+
32
+ JSON.pretty_generate({
33
+ rows: grid.length,
34
+ cols: grid[0]&.length || 0,
35
+ total_cells: flat.length,
36
+ colors: colors,
37
+ non_background: flat.count { |c| c != 0 }
38
+ })
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PromptObjects
4
+ module Primitives
5
+ class GridTransform < Primitive
6
+ def name
7
+ "grid_transform"
8
+ end
9
+
10
+ def description
11
+ "Apply geometric transforms to a grid: rotate_90, rotate_180, rotate_270, flip_h, flip_v, transpose."
12
+ end
13
+
14
+ def parameters
15
+ {
16
+ type: "object",
17
+ properties: {
18
+ grid: { type: "array", description: "2D array of integers" },
19
+ operation: {
20
+ type: "string",
21
+ enum: %w[rotate_90 rotate_180 rotate_270 flip_h flip_v transpose],
22
+ description: "Transformation to apply"
23
+ }
24
+ },
25
+ required: ["grid", "operation"]
26
+ }
27
+ end
28
+
29
+ def receive(message, context:)
30
+ grid = message[:grid] || message["grid"]
31
+ op = message[:operation] || message["operation"]
32
+
33
+ return "Error: grid is required" unless grid.is_a?(Array)
34
+ return "Error: operation is required" unless op
35
+
36
+ result = case op
37
+ when "rotate_90" then grid.transpose.map(&:reverse)
38
+ when "rotate_180" then grid.reverse.map(&:reverse)
39
+ when "rotate_270" then grid.transpose.reverse
40
+ when "flip_h" then grid.map(&:reverse)
41
+ when "flip_v" then grid.reverse
42
+ when "transpose" then grid.transpose
43
+ else return "Error: Unknown operation '#{op}'"
44
+ end
45
+
46
+ JSON.generate(result)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PromptObjects
4
+ module Primitives
5
+ class LoadArcTask < Primitive
6
+ def name
7
+ "load_arc_task"
8
+ end
9
+
10
+ def description
11
+ "Load an ARC-AGI task from a JSON file. Returns training pairs and test inputs with grid dimensions."
12
+ end
13
+
14
+ def parameters
15
+ {
16
+ type: "object",
17
+ properties: {
18
+ path: {
19
+ type: "string",
20
+ description: "Path to the ARC task JSON file"
21
+ }
22
+ },
23
+ required: ["path"]
24
+ }
25
+ end
26
+
27
+ def receive(message, context:)
28
+ path = message[:path] || message["path"]
29
+ return "Error: path is required" unless path
30
+
31
+ expanded = File.expand_path(path)
32
+ return "Error: File not found: #{path}" unless File.exist?(expanded)
33
+
34
+ data = JSON.parse(File.read(expanded, encoding: "UTF-8"))
35
+ train = data["train"] || []
36
+ test = data["test"] || []
37
+
38
+ result = {
39
+ task_id: File.basename(expanded, ".json"),
40
+ training_pairs: train.length,
41
+ test_inputs: test.length,
42
+ train: train.map.with_index { |pair, i|
43
+ {
44
+ pair: i,
45
+ input: pair["input"],
46
+ output: pair["output"],
47
+ input_size: "#{pair["input"].length}x#{pair["input"][0].length}",
48
+ output_size: "#{pair["output"].length}x#{pair["output"][0].length}"
49
+ }
50
+ },
51
+ test: test.map.with_index { |t, i|
52
+ {
53
+ test: i,
54
+ input: t["input"],
55
+ input_size: "#{t["input"].length}x#{t["input"][0].length}"
56
+ }
57
+ }
58
+ }
59
+
60
+ JSON.pretty_generate(result)
61
+ rescue JSON::ParserError => e
62
+ "Error: Invalid JSON - #{e.message}"
63
+ rescue StandardError => e
64
+ "Error loading task: #{e.message}"
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PromptObjects
4
+ module Primitives
5
+ class RenderGrid < Primitive
6
+ SYMBOLS = {
7
+ 0 => ".", 1 => "1", 2 => "2", 3 => "3", 4 => "4",
8
+ 5 => "5", 6 => "6", 7 => "7", 8 => "8", 9 => "9"
9
+ }.freeze
10
+
11
+ COLOR_NAMES = {
12
+ 0 => "black", 1 => "blue", 2 => "red", 3 => "green", 4 => "yellow",
13
+ 5 => "grey", 6 => "magenta", 7 => "orange", 8 => "cyan", 9 => "maroon"
14
+ }.freeze
15
+
16
+ def name
17
+ "render_grid"
18
+ end
19
+
20
+ def description
21
+ "Render an ARC grid as readable text with coordinates. Background (0) shown as dots, colors 1-9 as digits."
22
+ end
23
+
24
+ def parameters
25
+ {
26
+ type: "object",
27
+ properties: {
28
+ grid: {
29
+ type: "array",
30
+ description: "2D array of integers 0-9"
31
+ },
32
+ label: {
33
+ type: "string",
34
+ description: "Optional label to display above the grid"
35
+ }
36
+ },
37
+ required: ["grid"]
38
+ }
39
+ end
40
+
41
+ def receive(message, context:)
42
+ grid = message[:grid] || message["grid"]
43
+ label = message[:label] || message["label"]
44
+
45
+ return "Error: grid is required" unless grid.is_a?(Array)
46
+ return "Error: grid is empty" if grid.empty?
47
+
48
+ rows = grid.length
49
+ cols = grid[0]&.length || 0
50
+ lines = []
51
+
52
+ lines << label if label
53
+ lines << "#{rows}x#{cols}"
54
+
55
+ # Column headers
56
+ col_header = " " + (0...cols).map { |c| c.to_s.rjust(2) }.join
57
+ lines << col_header
58
+ lines << " " + "--" * cols
59
+
60
+ # Rows with line numbers
61
+ grid.each_with_index do |row, r|
62
+ cells = row.map { |v| (SYMBOLS[v] || "?").rjust(2) }.join
63
+ lines << "#{r.to_s.rjust(2)}|#{cells}"
64
+ end
65
+
66
+ # Color legend for non-background colors present
67
+ present = grid.flatten.uniq.sort - [0]
68
+ unless present.empty?
69
+ legend = present.map { |c| "#{c}=#{COLOR_NAMES[c]}" }.join(", ")
70
+ lines << ""
71
+ lines << "Colors: #{legend}"
72
+ end
73
+
74
+ lines.join("\n")
75
+ end
76
+ end
77
+ end
78
+ end