robot_lab 0.2.7 → 0.2.8

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/.envrc +4 -0
  3. data/.loki +5 -0
  4. data/Archspec.rb +44 -0
  5. data/CHANGELOG.md +17 -1
  6. data/Rakefile +6 -111
  7. data/_typos.toml +21 -0
  8. data/docs/api/index.md +2 -2
  9. data/docs/api/skills.md +53 -149
  10. data/docs/api/support.md +1 -1
  11. data/docs/architecture/index.md +5 -4
  12. data/docs/getting-started/configuration.md +4 -1
  13. data/docs/guides/hooks.md +122 -0
  14. data/docs/guides/using-tools.md +10 -1
  15. data/lib/robot_lab/agent_skill_catalog.rb +1 -0
  16. data/lib/robot_lab/ask_user.rb +2 -0
  17. data/lib/robot_lab/bus_poller.rb +2 -0
  18. data/lib/robot_lab/capabilities.rb +4 -0
  19. data/lib/robot_lab/config.rb +8 -0
  20. data/lib/robot_lab/doom_loop_detector.rb +6 -3
  21. data/lib/robot_lab/history_compressor.rb +5 -0
  22. data/lib/robot_lab/hook.rb +1 -0
  23. data/lib/robot_lab/hook_context.rb +4 -0
  24. data/lib/robot_lab/hook_registry.rb +1 -0
  25. data/lib/robot_lab/hooks.rb +6 -3
  26. data/lib/robot_lab/mcp/client.rb +2 -2
  27. data/lib/robot_lab/mcp/connection_poller.rb +16 -8
  28. data/lib/robot_lab/mcp/server_discovery.rb +1 -0
  29. data/lib/robot_lab/mcp/transports/sse.rb +3 -0
  30. data/lib/robot_lab/mcp/transports/stdio.rb +5 -0
  31. data/lib/robot_lab/mcp/transports/streamable_http.rb +4 -0
  32. data/lib/robot_lab/mcp/transports/websocket.rb +3 -0
  33. data/lib/robot_lab/memory.rb +23 -6
  34. data/lib/robot_lab/memory_change.rb +1 -0
  35. data/lib/robot_lab/message.rb +3 -0
  36. data/lib/robot_lab/names.rb +2 -4
  37. data/lib/robot_lab/network.rb +13 -6
  38. data/lib/robot_lab/robot/agent_skill_matching.rb +2 -0
  39. data/lib/robot_lab/robot/bus_messaging.rb +3 -0
  40. data/lib/robot_lab/robot/history_search.rb +2 -0
  41. data/lib/robot_lab/robot/hooking.rb +3 -0
  42. data/lib/robot_lab/robot/mcp_management.rb +11 -3
  43. data/lib/robot_lab/robot/template_rendering.rb +13 -4
  44. data/lib/robot_lab/robot.rb +58 -17
  45. data/lib/robot_lab/robot_result.rb +3 -0
  46. data/lib/robot_lab/run_config.rb +5 -0
  47. data/lib/robot_lab/script_tool.rb +20 -39
  48. data/lib/robot_lab/state_proxy.rb +1 -0
  49. data/lib/robot_lab/streaming/context.rb +1 -0
  50. data/lib/robot_lab/streaming/events.rb +1 -0
  51. data/lib/robot_lab/task.rb +2 -0
  52. data/lib/robot_lab/tool.rb +4 -0
  53. data/lib/robot_lab/user_message.rb +1 -0
  54. data/lib/robot_lab/utils.rb +2 -0
  55. data/lib/robot_lab/version.rb +1 -1
  56. data/lib/robot_lab/waiter.rb +3 -0
  57. data/lib/robot_lab.rb +6 -3
  58. metadata +7 -7
  59. data/lib/robot_lab/sandbox/null.rb +0 -13
  60. data/lib/robot_lab/sandbox/seatbelt.rb +0 -104
  61. data/lib/robot_lab/sandbox.rb +0 -52
@@ -1,104 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "tempfile"
4
-
5
- module RobotLab
6
- module Sandbox
7
- # macOS strategy: generates a deny-by-default sandbox-exec profile from the
8
- # effective grant and wraps the command as
9
- # sandbox-exec -f <profile> <cmd...>
10
- #
11
- # The profile allows process exec plus reads of system locations (so the
12
- # interpreter can load), reads of the skill bundle and granted paths, writes
13
- # only to granted paths (and the standard /dev sinks), and network only when
14
- # granted. Everything else -- notably $HOME, so SSH/cloud credentials --
15
- # is denied. Interpreters installed under $HOME (e.g. rbenv) are not visible;
16
- # declare them in fs_read or mark the skill trust: core.
17
- class Seatbelt
18
- # System locations a typical interpreter needs to read to start up.
19
- SYSTEM_READ = %w[/usr /bin /sbin /System /Library /opt /private/etc /dev /var/select].freeze
20
- DEV_WRITE = %w[/dev/null /dev/stdout /dev/stderr /dev/dtracehelper /dev/tty].freeze
21
-
22
- def initialize(grant, skill_dir:)
23
- @grant = grant
24
- @skill_dir = File.expand_path(skill_dir.to_s)
25
- @profile = nil
26
- end
27
-
28
- def wrap(cmd)
29
- @profile = write_profile
30
- ["sandbox-exec", "-f", @profile, *cmd]
31
- end
32
-
33
- def cleanup
34
- File.unlink(@profile) if @profile && File.exist?(@profile)
35
- rescue StandardError
36
- nil
37
- end
38
-
39
- # The generated Seatbelt profile text (public for testing).
40
- def profile_text
41
- reads = canonicalize(SYSTEM_READ + [@skill_dir] + @grant.fs_read)
42
- writes = canonicalize(@grant.fs_write)
43
- lines = [
44
- "(version 1)",
45
- # bsd.sb supplies the base rules a process needs to start (dyld, mach
46
- # bootstrap, etc.); without it a deny-default profile aborts the binary.
47
- '(import "bsd.sb")',
48
- "(deny default)",
49
- "(allow process-fork)",
50
- "(allow process-exec)",
51
- "(allow sysctl-read)",
52
- "(allow mach-lookup)",
53
- # Metadata (stat/lookup) on any path so the interpreter can traverse to
54
- # granted files; reading file *contents* stays restricted below.
55
- "(allow file-read-metadata)",
56
- read_rule(reads),
57
- write_rule(DEV_WRITE.map { |p| [:literal, p] } + writes.map { |p| [:subpath, p] })
58
- ]
59
- lines << "(allow network*)" if @grant.network
60
- "#{lines.compact.join("\n")}\n"
61
- end
62
-
63
- private
64
-
65
- def write_profile
66
- file = Tempfile.create(["robot_lab-sandbox-", ".sb"])
67
- file.write(profile_text)
68
- file.close
69
- file.path
70
- end
71
-
72
- # Resolve to the real (symlink-free) path the kernel matches against. macOS
73
- # symlinks /tmp -> /private/tmp, /var -> /private/var, etc., so logical
74
- # paths would never match. For not-yet-existing write targets, resolve the
75
- # nearest existing ancestor and re-append the remainder.
76
- def canonicalize(paths)
77
- Array(paths).map { |p| real_path(File.expand_path(p.to_s)) }.compact.uniq
78
- end
79
-
80
- def real_path(expanded)
81
- existing = expanded
82
- rest = []
83
- until File.exist?(existing) || existing == "/"
84
- rest.unshift(File.basename(existing))
85
- existing = File.dirname(existing)
86
- end
87
- real = File.realpath(existing)
88
- rest.empty? ? real : File.join(real, *rest)
89
- rescue StandardError
90
- expanded
91
- end
92
-
93
- def read_rule(paths)
94
- subpaths = paths.map { |p| "(subpath #{p.inspect})" }.join(" ")
95
- "(allow file-read* #{subpaths})"
96
- end
97
-
98
- def write_rule(entries)
99
- clauses = entries.map { |kind, p| "(#{kind} #{p.inspect})" }.join(" ")
100
- "(allow file-write* #{clauses})"
101
- end
102
- end
103
- end
104
- end
@@ -1,52 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RobotLab
4
- # Confines skill-script execution to a granted set of capabilities.
5
- #
6
- # Sandboxing is opt-in (config.sandbox.enabled). When off, every script runs
7
- # unconfined exactly as before. When on, each script runs under a strategy:
8
- #
9
- # - {Sandbox::Seatbelt} on macOS — a generated deny-by-default sandbox-exec
10
- # profile derived from the effective {Capabilities} grant.
11
- # - {Sandbox::Null} elsewhere (or for +trust: core+ skills) — a passthrough.
12
- #
13
- # OS-level confinement is therefore best-effort and platform-specific; it is
14
- # never a hard dependency.
15
- module Sandbox
16
- module_function
17
-
18
- # @return [Boolean] whether sandboxing is turned on in config
19
- def enabled?(config = RobotLab.config)
20
- config.respond_to?(:sandbox) && config.sandbox && config.sandbox.enabled == true
21
- end
22
-
23
- def macos?
24
- RUBY_PLATFORM.include?("darwin")
25
- end
26
-
27
- # Pick a strategy for the given (already-intersected) grant.
28
- #
29
- # @param grant [Capabilities] effective grant
30
- # @param skill_dir [String] skill bundle root, always granted read access
31
- # @param macos [Boolean] whether to use the macOS strategy; defaults to the
32
- # real platform check and is injectable so both branches are testable on
33
- # any host without stubbing.
34
- # @return [#wrap, #cleanup]
35
- def for(grant, skill_dir:, macos: macos?)
36
- return Null.new if grant.core?
37
- return Seatbelt.new(grant, skill_dir: skill_dir) if macos
38
-
39
- warn_once_non_macos
40
- Null.new
41
- end
42
-
43
- def warn_once_non_macos
44
- return if @warned_non_macos
45
-
46
- @warned_non_macos = true
47
- RobotLab.config.logger.warn(
48
- "Sandbox: OS-level confinement is only available on macOS; scripts run unconfined here"
49
- )
50
- end
51
- end
52
- end