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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +142 -0
- data/README.md +211 -19
- data/exe/poop +5 -0
- data/exe/prompt_objects +1 -1108
- data/exe/prompt_objects_mcp +5 -3
- data/lib/prompt_objects/capability.rb +33 -0
- data/lib/prompt_objects/cli/diagnostics_command.rb +69 -0
- data/lib/prompt_objects/cli/env_command.rb +435 -0
- data/lib/prompt_objects/cli/events_command.rb +145 -0
- data/lib/prompt_objects/cli/explore_command.rb +155 -0
- data/lib/prompt_objects/cli/helpers.rb +91 -0
- data/lib/prompt_objects/cli/message_command.rb +216 -0
- data/lib/prompt_objects/cli/repl_command.rb +366 -0
- data/lib/prompt_objects/cli/serve_command.rb +162 -0
- data/lib/prompt_objects/cli.rb +64 -422
- data/lib/prompt_objects/connectors/base.rb +16 -29
- data/lib/prompt_objects/connectors/mcp.rb +82 -98
- data/lib/prompt_objects/domain/records.rb +45 -0
- data/lib/prompt_objects/environment/exporter.rb +5 -4
- data/lib/prompt_objects/environment/git.rb +21 -1
- data/lib/prompt_objects/environment/importer.rb +5 -4
- data/lib/prompt_objects/environment/manager.rb +44 -6
- data/lib/prompt_objects/environment/manifest.rb +26 -4
- data/lib/prompt_objects/environment.rb +1350 -18
- data/lib/prompt_objects/execution/blocking_executor.rb +88 -0
- data/lib/prompt_objects/execution/context.rb +137 -0
- data/lib/prompt_objects/execution/contracts.rb +59 -0
- data/lib/prompt_objects/execution/permit_pool.rb +49 -0
- data/lib/prompt_objects/execution/policy.rb +47 -0
- data/lib/prompt_objects/execution/resource_lock_manager.rb +63 -0
- data/lib/prompt_objects/execution/runner.rb +498 -0
- data/lib/prompt_objects/execution/scheduler.rb +325 -0
- data/lib/prompt_objects/llm/factory.rb +4 -4
- data/lib/prompt_objects/llm/gemini_adapter.rb +25 -8
- data/lib/prompt_objects/llm/openai_adapter.rb +9 -1
- data/lib/prompt_objects/llm/pricing.rb +9 -2
- data/lib/prompt_objects/llm/response.rb +9 -4
- data/lib/prompt_objects/loader.rb +28 -1
- data/lib/prompt_objects/mcp/server.rb +6 -3
- data/lib/prompt_objects/mcp/tools/get_conversation.rb +15 -11
- data/lib/prompt_objects/mcp/tools/get_pending_requests.rb +2 -2
- data/lib/prompt_objects/mcp/tools/respond_to_request.rb +3 -3
- data/lib/prompt_objects/mcp/tools/send_message.rb +42 -32
- data/lib/prompt_objects/operations/diagnostics.rb +115 -0
- data/lib/prompt_objects/operations/retention.rb +75 -0
- data/lib/prompt_objects/operations/run_trace_builder.rb +152 -0
- data/lib/prompt_objects/primitives/http_get.rb +2 -0
- data/lib/prompt_objects/primitives/list_files.rb +2 -0
- data/lib/prompt_objects/primitives/read_file.rb +2 -0
- data/lib/prompt_objects/primitives/think.rb +36 -0
- data/lib/prompt_objects/primitives/write_file.rb +9 -0
- data/lib/prompt_objects/prompt_object.rb +190 -432
- data/lib/prompt_objects/repositories/artifact_repository.rb +109 -0
- data/lib/prompt_objects/repositories/base_repository.rb +37 -0
- data/lib/prompt_objects/repositories/definition_repository.rb +36 -0
- data/lib/prompt_objects/repositories/env_data_repository.rb +103 -0
- data/lib/prompt_objects/repositories/event_repository.rb +105 -0
- data/lib/prompt_objects/repositories/message_repository.rb +73 -0
- data/lib/prompt_objects/repositories/run_repository.rb +254 -0
- data/lib/prompt_objects/repositories/thread_repository.rb +87 -0
- data/lib/prompt_objects/repositories/tool_execution_repository.rb +88 -0
- data/lib/prompt_objects/repositories/workspace_session_repository.rb +61 -0
- data/lib/prompt_objects/server/api/routes.rb +561 -52
- data/lib/prompt_objects/server/app.rb +134 -1
- data/lib/prompt_objects/server/file_watcher.rb +9 -0
- data/lib/prompt_objects/server/public/assets/index-BjKlODDD.js +90 -0
- data/lib/prompt_objects/server/public/assets/index-w3fpIb5c.css +1 -0
- data/lib/prompt_objects/server/public/index.html +2 -2
- data/lib/prompt_objects/server/websocket_handler.rb +202 -207
- data/lib/prompt_objects/server.rb +170 -1
- data/lib/prompt_objects/service.rb +129 -0
- data/lib/prompt_objects/session/store.rb +774 -15
- data/lib/prompt_objects/universal/add_capability.rb +1 -1
- data/lib/prompt_objects/universal/create_capability.rb +4 -2
- data/lib/prompt_objects/universal/delete_env_data.rb +78 -0
- data/lib/prompt_objects/universal/delete_primitive.rb +1 -1
- data/lib/prompt_objects/universal/dispatch_po.rb +54 -0
- data/lib/prompt_objects/universal/get_env_data.rb +58 -0
- data/lib/prompt_objects/universal/get_run_status.rb +30 -0
- data/lib/prompt_objects/universal/join_runs.rb +35 -0
- data/lib/prompt_objects/universal/list_env_data.rb +57 -0
- data/lib/prompt_objects/universal/modify_primitive.rb +1 -1
- data/lib/prompt_objects/universal/render_artifact.rb +91 -0
- data/lib/prompt_objects/universal/request_primitive.rb +1 -1
- data/lib/prompt_objects/universal/set_env_data.rb +101 -0
- data/lib/prompt_objects/version.rb +5 -0
- data/lib/prompt_objects.rb +36 -6
- data/objects/coordinator.md +8 -4
- data/templates/arc-agi-1/objects/data_manager.md +2 -2
- data/templates/arc-agi-1/objects/observer.md +6 -2
- data/templates/arc-agi-1/objects/solver.md +14 -4
- data/templates/arc-agi-1/objects/verifier.md +6 -2
- data/templates/arc-agi-3/manifest.yml +19 -0
- data/templates/arc-agi-3/objects/analyst.md +79 -0
- data/templates/arc-agi-3/objects/player.md +76 -0
- data/templates/arc-agi-3/objects/strategist.md +97 -0
- data/templates/arc-agi-3/primitives/action_log.rb +133 -0
- data/templates/arc-agi-3/primitives/arc3_api.rb +223 -0
- data/templates/arc-agi-3/primitives/arc3_scorecard.rb +131 -0
- data/templates/arc-agi-3/primitives/frame_diff.rb +143 -0
- data/templates/arc-agi-3/primitives/frame_info.rb +96 -0
- data/templates/arc-agi-3/primitives/render_frame.rb +140 -0
- data/templates/basic/objects/basic.md +11 -3
- data/templates/chiptune/extras/sonic_pi_boot.rb +220 -0
- data/templates/chiptune/manifest.yml +20 -0
- data/templates/chiptune/objects/conductor.md +184 -0
- data/templates/chiptune/objects/sonic_pi.md +65 -0
- data/templates/chiptune/objects/voice_bass.md +44 -0
- data/templates/chiptune/objects/voice_lead.md +44 -0
- data/templates/chiptune/objects/voice_pad.md +43 -0
- data/templates/chiptune/objects/voice_perc.md +45 -0
- data/templates/chiptune/primitives/play_note.rb +59 -0
- data/templates/chiptune/primitives/play_sequence.rb +55 -0
- data/templates/chiptune/primitives/query_sonic_pi.rb +141 -0
- data/templates/chiptune/primitives/set_synth.rb +57 -0
- data/templates/chiptune/primitives/set_tempo.rb +45 -0
- data/templates/chiptune/primitives/stop_voice.rb +46 -0
- data/templates/chiptune/primitives/support/osc_bridge.rb +33 -0
- data/templates/chiptune/vendor/osc-ruby-1.1.5/LICENSE +19 -0
- data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/address_pattern.rb +58 -0
- data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/broadcast_client.rb +24 -0
- data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/bundle.rb +46 -0
- data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/client.rb +13 -0
- data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/core_ext/time.rb +6 -0
- data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/em_server.rb +63 -0
- data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/message.rb +46 -0
- data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/network_packet.rb +42 -0
- data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_argument.rb +26 -0
- data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_packet.rb +140 -0
- data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_types.rb +53 -0
- data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/server.rb +91 -0
- data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby.rb +18 -0
- data/templates/developer/objects/code_reviewer.md +1 -1
- data/templates/developer/objects/coordinator.md +16 -3
- data/templates/developer/objects/debugger.md +3 -3
- data/templates/empty/objects/assistant.md +10 -5
- data/templates/minimal/objects/assistant.md +10 -5
- data/templates/music/extras/sonic_pi_boot.rb +220 -0
- data/templates/music/manifest.yml +16 -0
- data/templates/music/objects/conductor.md +154 -0
- data/templates/music/objects/sonic_pi.md +65 -0
- data/templates/music/primitives/play_note.rb +55 -0
- data/templates/music/primitives/play_sequence.rb +51 -0
- data/templates/music/primitives/query_sonic_pi.rb +139 -0
- data/templates/music/primitives/set_synth.rb +53 -0
- data/templates/music/primitives/set_tempo.rb +43 -0
- data/templates/music/primitives/stop_voice.rb +44 -0
- data/templates/music/primitives/support/osc_bridge.rb +33 -0
- data/templates/music/vendor/osc-ruby-1.1.5/LICENSE +19 -0
- data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/address_pattern.rb +58 -0
- data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/broadcast_client.rb +24 -0
- data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/bundle.rb +46 -0
- data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/client.rb +13 -0
- data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/core_ext/time.rb +6 -0
- data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/em_server.rb +63 -0
- data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/message.rb +46 -0
- data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/network_packet.rb +42 -0
- data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_argument.rb +26 -0
- data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_packet.rb +140 -0
- data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_types.rb +53 -0
- data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/server.rb +91 -0
- data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby.rb +18 -0
- data/templates/music-beginner/README.md +100 -0
- data/templates/music-beginner/extras/README.md +27 -0
- data/templates/music-beginner/extras/sonic_pi_boot.rb +297 -0
- data/templates/music-beginner/manifest.yml +25 -0
- data/templates/music-beginner/objects/conductor.md +222 -0
- data/templates/music-beginner/objects/engine_setup.md +54 -0
- data/templates/music-beginner/objects/voice_bass.md +43 -0
- data/templates/music-beginner/objects/voice_keys.md +48 -0
- data/templates/music-beginner/objects/voice_pad.md +46 -0
- data/templates/music-beginner/objects/voice_percussion.md +45 -0
- data/templates/music-beginner/objects/voice_texture.md +37 -0
- data/templates/music-beginner/primitives/play_pattern.rb +118 -0
- data/templates/music-beginner/primitives/query_music_engine.rb +94 -0
- data/templates/music-beginner/primitives/set_instrument.rb +47 -0
- data/templates/music-beginner/primitives/set_tempo.rb +43 -0
- data/templates/music-beginner/primitives/set_voice_level.rb +46 -0
- data/templates/music-beginner/primitives/stop_voice.rb +37 -0
- data/templates/music-beginner/primitives/support/osc_bridge.rb +25 -0
- data/templates/music-beginner/vendor/osc-ruby-1.1.5/LICENSE +19 -0
- data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/address_pattern.rb +58 -0
- data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/broadcast_client.rb +24 -0
- data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/bundle.rb +46 -0
- data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/client.rb +13 -0
- data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/core_ext/time.rb +6 -0
- data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/em_server.rb +63 -0
- data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/message.rb +46 -0
- data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/network_packet.rb +42 -0
- data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_argument.rb +26 -0
- data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_packet.rb +140 -0
- data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_types.rb +53 -0
- data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/server.rb +91 -0
- data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby.rb +18 -0
- data/templates/music-pro/extras/AUDITION.md +100 -0
- data/templates/music-pro/extras/sonic_pi_boot.rb +546 -0
- data/templates/music-pro/manifest.yml +19 -0
- data/templates/music-pro/objects/conductor.md +126 -0
- data/templates/music-pro/objects/sonic_pi.md +35 -0
- data/templates/music-pro/objects/voice_harmony.md +48 -0
- data/templates/music-pro/objects/voice_rhythm.md +43 -0
- data/templates/music-pro/objects/voice_texture.md +42 -0
- data/templates/music-pro/primitives/define_patch.rb +46 -0
- data/templates/music-pro/primitives/query_music_engine.rb +69 -0
- data/templates/music-pro/primitives/set_live_pattern.rb +70 -0
- data/templates/music-pro/primitives/set_tempo.rb +31 -0
- data/templates/music-pro/primitives/stop_voice.rb +31 -0
- data/templates/music-pro/primitives/support/music_protocol.rb +136 -0
- data/templates/music-pro/primitives/support/osc_bridge.rb +33 -0
- data/templates/music-pro/primitives/trigger_event.rb +44 -0
- data/templates/music-pro/vendor/osc-ruby-1.1.5/LICENSE +19 -0
- data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/address_pattern.rb +58 -0
- data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/broadcast_client.rb +24 -0
- data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/bundle.rb +46 -0
- data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/client.rb +13 -0
- data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/core_ext/time.rb +6 -0
- data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/em_server.rb +63 -0
- data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/message.rb +46 -0
- data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/network_packet.rb +42 -0
- data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_argument.rb +26 -0
- data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_packet.rb +140 -0
- data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_types.rb +53 -0
- data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/server.rb +91 -0
- data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby.rb +18 -0
- data/templates/writer/objects/coordinator.md +14 -3
- data/templates/writer/objects/editor.md +1 -1
- data/templates/writer/objects/researcher.md +2 -2
- data/tools/thread-explorer.html +28 -1
- metadata +175 -65
- data/CLAUDE.md +0 -176
- data/Gemfile +0 -10
- data/Gemfile.lock +0 -233
- data/IMPLEMENTATION_PLAN.md +0 -1073
- data/Rakefile +0 -27
- data/design-doc-v2.md +0 -1232
- data/frontend/.gitignore +0 -3
- data/frontend/index.html +0 -17
- data/frontend/package-lock.json +0 -4540
- data/frontend/package.json +0 -36
- data/frontend/postcss.config.js +0 -6
- data/frontend/src/App.tsx +0 -103
- data/frontend/src/canvas/CanvasView.tsx +0 -113
- data/frontend/src/canvas/ForceLayout.ts +0 -115
- data/frontend/src/canvas/SceneManager.ts +0 -587
- data/frontend/src/canvas/canvasStore.ts +0 -47
- data/frontend/src/canvas/constants.ts +0 -95
- data/frontend/src/canvas/controls/CameraControls.ts +0 -98
- data/frontend/src/canvas/edges/MessageArc.ts +0 -149
- data/frontend/src/canvas/inspector/InspectorPanel.tsx +0 -31
- data/frontend/src/canvas/inspector/POInspector.tsx +0 -262
- data/frontend/src/canvas/inspector/ToolCallInspector.tsx +0 -67
- data/frontend/src/canvas/nodes/PONode.ts +0 -249
- data/frontend/src/canvas/nodes/ToolCallNode.ts +0 -116
- data/frontend/src/canvas/types.ts +0 -24
- data/frontend/src/components/ContextMenu.tsx +0 -68
- data/frontend/src/components/Inspector.tsx +0 -232
- data/frontend/src/components/MarkdownMessage.tsx +0 -155
- data/frontend/src/components/MethodList.tsx +0 -90
- data/frontend/src/components/ModelSelector.tsx +0 -115
- data/frontend/src/components/NotificationPanel.tsx +0 -130
- data/frontend/src/components/ObjectList.tsx +0 -78
- data/frontend/src/components/PaneSlot.tsx +0 -30
- data/frontend/src/components/SourcePane.tsx +0 -202
- data/frontend/src/components/SystemBar.tsx +0 -74
- data/frontend/src/components/Transcript.tsx +0 -76
- data/frontend/src/components/UsagePanel.tsx +0 -105
- data/frontend/src/components/Workspace.tsx +0 -260
- data/frontend/src/components/index.ts +0 -12
- data/frontend/src/hooks/useResize.ts +0 -55
- data/frontend/src/hooks/useWebSocket.ts +0 -517
- data/frontend/src/index.css +0 -103
- data/frontend/src/main.tsx +0 -10
- data/frontend/src/store/index.ts +0 -279
- data/frontend/src/types/index.ts +0 -165
- data/frontend/tailwind.config.js +0 -44
- data/frontend/tsconfig.json +0 -30
- data/frontend/vite.config.ts +0 -29
- data/lib/prompt_objects/server/public/assets/index-D1myxE0l.js +0 -4345
- data/lib/prompt_objects/server/public/assets/index-DdCcwC-Z.css +0 -1
- data/lib/prompt_objects/universal/add_primitive.rb +0 -118
- data/lib/prompt_objects/universal/ask_human.rb +0 -109
- data/lib/prompt_objects/universal/create_primitive.rb +0 -174
- data/lib/prompt_objects/universal/list_primitives.rb +0 -145
- data/lib/prompt_objects/universal/think.rb +0 -41
- data/prompt_objects.gemspec +0 -50
- data/templates/basic/.gitignore +0 -2
- data/templates/developer/.gitignore +0 -5
- data/templates/empty/.gitignore +0 -5
- data/templates/empty/objects/.gitkeep +0 -0
- data/templates/minimal/.gitignore +0 -5
- data/templates/writer/.gitignore +0 -5
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PromptObjects
|
|
4
|
+
module Execution
|
|
5
|
+
# Bounded worker pool for synchronous adapters and capabilities. Completion
|
|
6
|
+
# is signalled through an IO so an Async fiber yields instead of blocking
|
|
7
|
+
# the server reactor while a worker is occupied.
|
|
8
|
+
class BlockingExecutor
|
|
9
|
+
class RejectedError < StandardError; end
|
|
10
|
+
|
|
11
|
+
Job = Data.define(:work, :completion, :result)
|
|
12
|
+
STOP = Object.new.freeze
|
|
13
|
+
|
|
14
|
+
def initialize(size: 4, queue_capacity: size * 4)
|
|
15
|
+
raise ArgumentError, "size must be positive" unless size.positive?
|
|
16
|
+
raise ArgumentError, "queue_capacity must be positive" unless queue_capacity.positive?
|
|
17
|
+
|
|
18
|
+
@jobs = SizedQueue.new(queue_capacity)
|
|
19
|
+
@state_mutex = Mutex.new
|
|
20
|
+
@closed = false
|
|
21
|
+
@workers = Array.new(size) { start_worker }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def call(&work)
|
|
25
|
+
raise ArgumentError, "a block is required" unless work
|
|
26
|
+
|
|
27
|
+
reader, writer = IO.pipe
|
|
28
|
+
result = {}
|
|
29
|
+
enqueue(Job.new(work: work, completion: writer, result: result))
|
|
30
|
+
reader.read(1)
|
|
31
|
+
raise result[:error] if result.key?(:error)
|
|
32
|
+
|
|
33
|
+
result[:value]
|
|
34
|
+
ensure
|
|
35
|
+
reader&.close unless reader&.closed?
|
|
36
|
+
writer&.close unless writer&.closed?
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def close
|
|
40
|
+
workers = @state_mutex.synchronize do
|
|
41
|
+
return if @closed
|
|
42
|
+
|
|
43
|
+
@closed = true
|
|
44
|
+
@workers.dup
|
|
45
|
+
end
|
|
46
|
+
workers.size.times { @jobs.push(STOP) }
|
|
47
|
+
workers.each(&:join)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def enqueue(job)
|
|
53
|
+
@state_mutex.synchronize do
|
|
54
|
+
raise RejectedError, "blocking executor is closed" if @closed
|
|
55
|
+
|
|
56
|
+
@jobs.push(job, true)
|
|
57
|
+
end
|
|
58
|
+
rescue ThreadError
|
|
59
|
+
raise RejectedError, "blocking executor queue is full"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def start_worker
|
|
63
|
+
Thread.new do
|
|
64
|
+
Thread.current.name = "prompt-objects-worker" if Thread.current.respond_to?(:name=)
|
|
65
|
+
while (job = @jobs.pop)
|
|
66
|
+
break if job.equal?(STOP)
|
|
67
|
+
|
|
68
|
+
execute(job)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def execute(job)
|
|
74
|
+
job.result[:value] = job.work.call
|
|
75
|
+
rescue Exception => e # rubocop:disable Lint/RescueException -- propagate worker failures to the caller
|
|
76
|
+
job.result[:error] = e
|
|
77
|
+
ensure
|
|
78
|
+
begin
|
|
79
|
+
job.completion.write(".")
|
|
80
|
+
rescue IOError, Errno::EPIPE
|
|
81
|
+
# The awaiting Async task was cancelled; the work is allowed to finish.
|
|
82
|
+
ensure
|
|
83
|
+
job.completion.close unless job.completion.closed?
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PromptObjects
|
|
4
|
+
module Execution
|
|
5
|
+
class CancelledError < StandardError; end
|
|
6
|
+
|
|
7
|
+
# Cooperative cancellation shared by a run and its attached descendants.
|
|
8
|
+
class CancellationToken
|
|
9
|
+
def initialize
|
|
10
|
+
@cancelled = false
|
|
11
|
+
@reason = nil
|
|
12
|
+
@mutex = Mutex.new
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def cancel(reason = "cancelled")
|
|
16
|
+
@mutex.synchronize do
|
|
17
|
+
@cancelled = true
|
|
18
|
+
@reason = reason
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def cancelled?
|
|
23
|
+
@mutex.synchronize { @cancelled }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def reason
|
|
27
|
+
@mutex.synchronize { @reason }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def check!
|
|
31
|
+
raise CancelledError, reason if cancelled?
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Immutable identity and dependencies passed to one run or tool call.
|
|
36
|
+
class Context
|
|
37
|
+
attr_reader :env, :bus, :human_queue, :workspace_session_id, :thread_id,
|
|
38
|
+
:run_id, :caller_po, :target_po, :source_type, :source_name,
|
|
39
|
+
:current_capability, :calling_po, :cancellation_token,
|
|
40
|
+
:trace_id, :span_id, :deadline, :resource_lock_manager,
|
|
41
|
+
:event_publisher, :tui_mode
|
|
42
|
+
|
|
43
|
+
def initialize(env:, workspace_session_id:, thread_id:, run_id:, target_po:,
|
|
44
|
+
source_type:, source_name: nil, caller_po: nil,
|
|
45
|
+
current_capability: nil, cancellation_token: CancellationToken.new,
|
|
46
|
+
trace_id: SecureRandom.uuid, span_id: SecureRandom.uuid,
|
|
47
|
+
deadline: nil, resource_lock_manager: nil, event_publisher: nil,
|
|
48
|
+
tui_mode: false)
|
|
49
|
+
@env = env
|
|
50
|
+
@bus = env.bus
|
|
51
|
+
@human_queue = env.human_queue
|
|
52
|
+
@workspace_session_id = workspace_session_id
|
|
53
|
+
@thread_id = thread_id
|
|
54
|
+
@run_id = run_id
|
|
55
|
+
@caller_po = caller_po
|
|
56
|
+
@target_po = target_po
|
|
57
|
+
@source_type = source_type
|
|
58
|
+
@source_name = source_name
|
|
59
|
+
@current_capability = current_capability
|
|
60
|
+
@calling_po = caller_po
|
|
61
|
+
@cancellation_token = cancellation_token
|
|
62
|
+
@trace_id = trace_id
|
|
63
|
+
@span_id = span_id
|
|
64
|
+
@deadline = deadline
|
|
65
|
+
@resource_lock_manager = resource_lock_manager
|
|
66
|
+
@event_publisher = event_publisher
|
|
67
|
+
@tui_mode = tui_mode
|
|
68
|
+
freeze
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def for_tool_call(tool_call)
|
|
72
|
+
self.class.new(
|
|
73
|
+
**shared_attributes,
|
|
74
|
+
caller_po: target_po,
|
|
75
|
+
current_capability: tool_call.name,
|
|
76
|
+
span_id: SecureRandom.uuid
|
|
77
|
+
)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def for_child_run(run_id:, thread_id:, target_po:, source_name: nil)
|
|
81
|
+
self.class.new(
|
|
82
|
+
**shared_attributes,
|
|
83
|
+
run_id: run_id,
|
|
84
|
+
thread_id: thread_id,
|
|
85
|
+
target_po: target_po,
|
|
86
|
+
caller_po: self.target_po,
|
|
87
|
+
source_type: "delegation",
|
|
88
|
+
source_name: source_name || self.target_po,
|
|
89
|
+
current_capability: nil,
|
|
90
|
+
span_id: SecureRandom.uuid
|
|
91
|
+
)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def env_data_scope
|
|
95
|
+
workspace_session_id
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def check_cancelled!
|
|
99
|
+
cancellation_token.check!
|
|
100
|
+
return true unless deadline && Time.now >= deadline
|
|
101
|
+
|
|
102
|
+
raise CancelledError, "deadline exceeded"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def log_message(to:, message:)
|
|
106
|
+
bus.publish(from: current_capability || source_name || source_type, to: to, message: message)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def log_response(from:, message:)
|
|
110
|
+
bus.publish(from: from, to: current_capability || target_po, message: message)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
private
|
|
114
|
+
|
|
115
|
+
def shared_attributes
|
|
116
|
+
{
|
|
117
|
+
env: env,
|
|
118
|
+
workspace_session_id: workspace_session_id,
|
|
119
|
+
thread_id: thread_id,
|
|
120
|
+
run_id: run_id,
|
|
121
|
+
target_po: target_po,
|
|
122
|
+
source_type: source_type,
|
|
123
|
+
source_name: source_name,
|
|
124
|
+
caller_po: caller_po,
|
|
125
|
+
current_capability: current_capability,
|
|
126
|
+
cancellation_token: cancellation_token,
|
|
127
|
+
trace_id: trace_id,
|
|
128
|
+
span_id: span_id,
|
|
129
|
+
deadline: deadline,
|
|
130
|
+
resource_lock_manager: resource_lock_manager,
|
|
131
|
+
event_publisher: event_publisher,
|
|
132
|
+
tui_mode: tui_mode
|
|
133
|
+
}
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PromptObjects
|
|
4
|
+
module Execution
|
|
5
|
+
# Stable names shared by the runtime protocol, persistence layer, and UI.
|
|
6
|
+
# The protocol fixtures in protocol/v1/fixtures are the wire-format examples.
|
|
7
|
+
module Contracts
|
|
8
|
+
PROTOCOL_VERSION = 1
|
|
9
|
+
|
|
10
|
+
THREAD_TYPES = %w[human autonomous artifact delegation service api].freeze
|
|
11
|
+
RUN_STATUSES = %w[queued running waiting completed failed cancelled skipped interrupted].freeze
|
|
12
|
+
TOOL_EXECUTION_STATUSES = %w[pending running completed failed cancelled timed_out].freeze
|
|
13
|
+
RUN_EVENT_KINDS = %w[
|
|
14
|
+
run_queued run_started model_started stream_delta model_completed
|
|
15
|
+
tool_batch_started tool_started tool_completed tool_failed tool_cancelled
|
|
16
|
+
delegation_started delegation_completed artifact_upserted env_data_changed
|
|
17
|
+
run_waiting run_completed run_failed run_cancelled run_skipped run_interrupted
|
|
18
|
+
].freeze
|
|
19
|
+
|
|
20
|
+
SNAPSHOT_TYPES = %w[workspace_snapshot thread_snapshot artifact_snapshot].freeze
|
|
21
|
+
MUTATION_TYPES = %w[artifact_upserted artifact_removed env_data_changed].freeze
|
|
22
|
+
ENVELOPE_TYPES = (SNAPSHOT_TYPES + MUTATION_TYPES + ["run_event"]).freeze
|
|
23
|
+
|
|
24
|
+
module_function
|
|
25
|
+
|
|
26
|
+
def validate_envelope!(envelope)
|
|
27
|
+
data = stringify_keys(envelope)
|
|
28
|
+
version = data.fetch("version")
|
|
29
|
+
type = data.fetch("type")
|
|
30
|
+
payload = data.fetch("payload")
|
|
31
|
+
|
|
32
|
+
raise ArgumentError, "unsupported protocol version: #{version}" unless version == PROTOCOL_VERSION
|
|
33
|
+
raise ArgumentError, "unknown protocol message type: #{type}" unless ENVELOPE_TYPES.include?(type)
|
|
34
|
+
raise ArgumentError, "payload must be an object" unless payload.is_a?(Hash)
|
|
35
|
+
|
|
36
|
+
validate_run_event!(payload) if type == "run_event"
|
|
37
|
+
true
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def validate_run_event!(payload)
|
|
41
|
+
%w[workspace_session_id thread_id run_id sequence workspace_cursor kind timestamp data].each do |key|
|
|
42
|
+
payload.fetch(key)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
kind = payload.fetch("kind")
|
|
46
|
+
raise ArgumentError, "unknown run event kind: #{kind}" unless RUN_EVENT_KINDS.include?(kind)
|
|
47
|
+
raise ArgumentError, "run event sequence must be positive" unless payload.fetch("sequence").to_i.positive?
|
|
48
|
+
raise ArgumentError, "workspace cursor must be positive" unless payload.fetch("workspace_cursor").to_i.positive?
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def stringify_keys(value)
|
|
52
|
+
return value.transform_keys(&:to_s).transform_values { |item| stringify_keys(item) } if value.is_a?(Hash)
|
|
53
|
+
return value.map { |item| stringify_keys(item) } if value.is_a?(Array)
|
|
54
|
+
|
|
55
|
+
value
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PromptObjects
|
|
4
|
+
module Execution
|
|
5
|
+
# A small counting semaphore used for scheduler resources that must remain
|
|
6
|
+
# distinct (workers, providers, and later resource leases).
|
|
7
|
+
class PermitPool
|
|
8
|
+
def initialize(limit)
|
|
9
|
+
raise ArgumentError, "limit must be positive" unless limit.positive?
|
|
10
|
+
|
|
11
|
+
@limit = limit
|
|
12
|
+
@available = limit
|
|
13
|
+
@mutex = Mutex.new
|
|
14
|
+
@condition = ConditionVariable.new
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
attr_reader :limit
|
|
18
|
+
|
|
19
|
+
def with
|
|
20
|
+
acquire
|
|
21
|
+
yield
|
|
22
|
+
ensure
|
|
23
|
+
release
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def acquire
|
|
27
|
+
@mutex.synchronize do
|
|
28
|
+
@condition.wait(@mutex) until @available.positive?
|
|
29
|
+
@available -= 1
|
|
30
|
+
end
|
|
31
|
+
true
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def release
|
|
35
|
+
@mutex.synchronize do
|
|
36
|
+
raise ThreadError, "permit pool released without an acquisition" if @available >= @limit
|
|
37
|
+
|
|
38
|
+
@available += 1
|
|
39
|
+
@condition.signal
|
|
40
|
+
end
|
|
41
|
+
true
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def available
|
|
45
|
+
@mutex.synchronize { @available }
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PromptObjects
|
|
4
|
+
module Execution
|
|
5
|
+
ResolvedPolicy = Data.define(:parallel_safe, :side_effect, :workload, :resource_keys, :signature)
|
|
6
|
+
|
|
7
|
+
class Policy
|
|
8
|
+
SIDE_EFFECTS = %i[read write exclusive].freeze
|
|
9
|
+
WORKLOADS = %i[blocking cpu].freeze
|
|
10
|
+
|
|
11
|
+
def initialize(parallel_safe:, side_effect:, resource_keys: nil, workload: :blocking)
|
|
12
|
+
raise ArgumentError, "unknown side effect: #{side_effect}" unless SIDE_EFFECTS.include?(side_effect.to_sym)
|
|
13
|
+
raise ArgumentError, "unknown workload: #{workload}" unless WORKLOADS.include?(workload.to_sym)
|
|
14
|
+
|
|
15
|
+
@parallel_safe = !!parallel_safe
|
|
16
|
+
@side_effect = side_effect.to_sym
|
|
17
|
+
@workload = workload.to_sym
|
|
18
|
+
@resource_resolver = resource_keys
|
|
19
|
+
freeze
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
attr_reader :parallel_safe, :side_effect, :workload
|
|
23
|
+
|
|
24
|
+
def resolve(arguments:, context:, capability:)
|
|
25
|
+
keys = if @resource_resolver
|
|
26
|
+
Array(@resource_resolver.call(arguments, context, capability))
|
|
27
|
+
elsif side_effect == :exclusive
|
|
28
|
+
["workspace:#{context.workspace_session_id}:exclusive"]
|
|
29
|
+
else
|
|
30
|
+
[]
|
|
31
|
+
end
|
|
32
|
+
ResolvedPolicy.new(
|
|
33
|
+
parallel_safe: parallel_safe,
|
|
34
|
+
side_effect: side_effect,
|
|
35
|
+
workload: workload,
|
|
36
|
+
resource_keys: keys.compact.map(&:to_s).uniq.sort.freeze,
|
|
37
|
+
signature: signature(capability).freeze
|
|
38
|
+
)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def signature(capability = nil)
|
|
42
|
+
resolver_location = @resource_resolver&.source_location&.join(":")
|
|
43
|
+
[capability&.class&.name, parallel_safe, side_effect, workload, resolver_location].compact.join("|")
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "set"
|
|
4
|
+
require "timeout"
|
|
5
|
+
|
|
6
|
+
module PromptObjects
|
|
7
|
+
module Execution
|
|
8
|
+
# Atomically leases a set of resource keys. All-or-nothing acquisition
|
|
9
|
+
# avoids deadlocks when two tool calls need overlapping key sets.
|
|
10
|
+
class ResourceLockManager
|
|
11
|
+
class LockTimeoutError < Timeout::Error; end
|
|
12
|
+
|
|
13
|
+
def initialize
|
|
14
|
+
@leased = Set.new
|
|
15
|
+
@mutex = Mutex.new
|
|
16
|
+
@condition = ConditionVariable.new
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def with(resource_keys, timeout: 30, cancellation_token: nil)
|
|
20
|
+
keys = Array(resource_keys).compact.map(&:to_s).uniq.sort
|
|
21
|
+
return yield if keys.empty?
|
|
22
|
+
|
|
23
|
+
acquire(keys, timeout: timeout, cancellation_token: cancellation_token)
|
|
24
|
+
acquired = true
|
|
25
|
+
yield
|
|
26
|
+
ensure
|
|
27
|
+
release(keys) if acquired
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def locked_keys
|
|
31
|
+
@mutex.synchronize { @leased.to_a.sort }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def acquire(keys, timeout:, cancellation_token:)
|
|
37
|
+
deadline = monotonic_time + timeout
|
|
38
|
+
@mutex.synchronize do
|
|
39
|
+
until (@leased & keys).empty?
|
|
40
|
+
cancellation_token&.check!
|
|
41
|
+
remaining = deadline - monotonic_time
|
|
42
|
+
raise LockTimeoutError, "timed out waiting for resources: #{keys.join(', ')}" unless remaining.positive?
|
|
43
|
+
|
|
44
|
+
@condition.wait(@mutex, [remaining, 0.05].min)
|
|
45
|
+
end
|
|
46
|
+
cancellation_token&.check!
|
|
47
|
+
@leased.merge(keys)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def release(keys)
|
|
52
|
+
@mutex.synchronize do
|
|
53
|
+
keys.each { |key| @leased.delete(key) }
|
|
54
|
+
@condition.broadcast
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def monotonic_time
|
|
59
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|