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
|
@@ -27,9 +27,17 @@ module PromptObjects
|
|
|
27
27
|
puts ""
|
|
28
28
|
puts "Prompt Objects:"
|
|
29
29
|
runtime.registry.prompt_objects.each do |po|
|
|
30
|
-
|
|
30
|
+
auto = po.autonomous? ? " (autonomous: #{po.autonomous_config['interval']}s)" : ""
|
|
31
|
+
puts " - #{po.name}: #{po.description}#{auto}"
|
|
31
32
|
end
|
|
32
33
|
puts ""
|
|
34
|
+
if runtime.services.any?
|
|
35
|
+
puts "Services:"
|
|
36
|
+
runtime.services.each do |s|
|
|
37
|
+
puts " - #{s.name}: #{s.description || '(no description)'}"
|
|
38
|
+
end
|
|
39
|
+
puts ""
|
|
40
|
+
end
|
|
33
41
|
puts "Press Ctrl+C to stop"
|
|
34
42
|
puts ""
|
|
35
43
|
|
|
@@ -59,6 +67,9 @@ module PromptObjects
|
|
|
59
67
|
puts "Broadcast: PO modified (programmatic) - #{po.name}"
|
|
60
68
|
}
|
|
61
69
|
|
|
70
|
+
# Register broadcast callback for services to push WebSocket messages
|
|
71
|
+
runtime.on_broadcast = ->(message) { app.broadcast(message) }
|
|
72
|
+
|
|
62
73
|
# Start file watcher for live updates (manual file edits)
|
|
63
74
|
file_watcher = nil
|
|
64
75
|
if env_path
|
|
@@ -69,6 +80,12 @@ module PromptObjects
|
|
|
69
80
|
file_watcher.start
|
|
70
81
|
end
|
|
71
82
|
|
|
83
|
+
# Start environment services (calls service.start, no scheduling yet)
|
|
84
|
+
runtime.start_services if runtime.services.any?
|
|
85
|
+
|
|
86
|
+
# Start environment connectors (Discord/Slack/webhook bridges)
|
|
87
|
+
runtime.start_connectors if runtime.connectors.any?
|
|
88
|
+
|
|
72
89
|
# Write .server file for CLI discovery
|
|
73
90
|
server_file = write_server_file(env_path, host: host, port: port) if env_path
|
|
74
91
|
|
|
@@ -80,15 +97,167 @@ module PromptObjects
|
|
|
80
97
|
endpoint
|
|
81
98
|
)
|
|
82
99
|
|
|
100
|
+
# Schedule service tick loops as Async tasks.
|
|
101
|
+
# These cooperate with Falcon's event loop instead of fighting it.
|
|
102
|
+
schedule_service_ticks(task, runtime)
|
|
103
|
+
|
|
104
|
+
# Schedule autonomous PO execution as Async tasks.
|
|
105
|
+
schedule_autonomous_pos(task, runtime, app)
|
|
106
|
+
|
|
83
107
|
task.async do
|
|
84
108
|
server.run
|
|
85
109
|
end
|
|
86
110
|
ensure
|
|
111
|
+
runtime.stop_connectors if runtime.connectors.any?
|
|
112
|
+
runtime.stop_services if runtime.services.any?
|
|
113
|
+
runtime.shutdown_execution(wait: false)
|
|
87
114
|
file_watcher&.stop
|
|
115
|
+
app.close
|
|
88
116
|
remove_server_file(server_file) if server_file
|
|
89
117
|
end
|
|
90
118
|
end
|
|
91
119
|
|
|
120
|
+
# Schedule Async tick tasks for services that have an interval.
|
|
121
|
+
# Runs inside the Async event loop so ticks cooperate with Falcon.
|
|
122
|
+
# @param task [Async::Task] The parent Async task
|
|
123
|
+
# @param runtime [Runtime] The environment runtime
|
|
124
|
+
def self.schedule_service_ticks(task, runtime)
|
|
125
|
+
runtime.services.each do |service|
|
|
126
|
+
next unless service.interval
|
|
127
|
+
|
|
128
|
+
task.async do |subtask|
|
|
129
|
+
puts "Service tick loop started: #{service.name} (every #{service.interval}s)"
|
|
130
|
+
loop do
|
|
131
|
+
subtask.sleep service.interval
|
|
132
|
+
service.tick
|
|
133
|
+
rescue => e
|
|
134
|
+
warn "Service #{service.name} tick error: #{e.class}: #{e.message}"
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Schedule Async tasks for autonomous POs that think on their own interval.
|
|
141
|
+
# Uses runtime.inject_message — the same code path as human messages,
|
|
142
|
+
# service events, and webhooks.
|
|
143
|
+
# @param task [Async::Task] The parent Async task
|
|
144
|
+
# @param runtime [Runtime] The environment runtime
|
|
145
|
+
# @param app [App] The server app (unused — broadcasting goes through runtime)
|
|
146
|
+
def self.schedule_autonomous_pos(task, runtime, app)
|
|
147
|
+
runtime.registry.prompt_objects.each do |po|
|
|
148
|
+
next unless po.autonomous?
|
|
149
|
+
|
|
150
|
+
auto_config = po.autonomous_config
|
|
151
|
+
next unless auto_config["start_on_boot"]
|
|
152
|
+
|
|
153
|
+
po_name = po.name
|
|
154
|
+
|
|
155
|
+
task.async do |subtask|
|
|
156
|
+
puts "Autonomous PO scheduled: #{po_name} (every #{auto_config['interval']}s, " \
|
|
157
|
+
"initial delay #{auto_config['initial_delay']}s)"
|
|
158
|
+
|
|
159
|
+
waiting_state = nil
|
|
160
|
+
loop do
|
|
161
|
+
current = current_autonomous_config(runtime, po_name)
|
|
162
|
+
break unless current && current["wait_for_human"] && !runtime.human_message_received?(po_name)
|
|
163
|
+
|
|
164
|
+
current_waiting_state = [current["interval"], current["initial_delay"]]
|
|
165
|
+
if current_waiting_state != waiting_state
|
|
166
|
+
runtime.update_autonomous_schedule(
|
|
167
|
+
po_name,
|
|
168
|
+
status: "waiting_for_human",
|
|
169
|
+
interval: current["interval"],
|
|
170
|
+
initial_delay: current["initial_delay"],
|
|
171
|
+
wait_for_human: true,
|
|
172
|
+
next_run_at: nil
|
|
173
|
+
)
|
|
174
|
+
waiting_state = current_waiting_state
|
|
175
|
+
end
|
|
176
|
+
subtask.sleep 0.1
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
next unless wait_for_live_autonomous_delay(
|
|
180
|
+
subtask, runtime, po_name, duration_key: "initial_delay"
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
loop do
|
|
184
|
+
current = current_autonomous_config(runtime, po_name)
|
|
185
|
+
break unless current
|
|
186
|
+
|
|
187
|
+
begin
|
|
188
|
+
runtime.update_autonomous_schedule(
|
|
189
|
+
po_name,
|
|
190
|
+
status: "running",
|
|
191
|
+
next_run_at: nil
|
|
192
|
+
)
|
|
193
|
+
# Autonomous work uses its own coalesced, summary-retention thread.
|
|
194
|
+
execution = lambda do
|
|
195
|
+
runtime.inject_message(
|
|
196
|
+
target_po: po_name,
|
|
197
|
+
content: current["message"].strip,
|
|
198
|
+
from_source: "timer:#{po_name}"
|
|
199
|
+
)
|
|
200
|
+
end
|
|
201
|
+
if app.respond_to?(:blocking_executor)
|
|
202
|
+
app.blocking_executor.call(&execution)
|
|
203
|
+
else
|
|
204
|
+
execution.call
|
|
205
|
+
end
|
|
206
|
+
rescue => e
|
|
207
|
+
warn "Autonomous PO #{po_name} error: #{e.class}: #{e.message}"
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
break unless wait_for_live_autonomous_delay(
|
|
211
|
+
subtask, runtime, po_name, duration_key: "interval"
|
|
212
|
+
)
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Sleep until the PO's current autonomous deadline while re-reading its
|
|
219
|
+
# frontmatter. In-app and external source edits can therefore change the
|
|
220
|
+
# message used by the next tick and move an interval deadline without a
|
|
221
|
+
# server restart.
|
|
222
|
+
def self.wait_for_live_autonomous_delay(subtask, runtime, po_name, duration_key:)
|
|
223
|
+
started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
224
|
+
published_deadline = nil
|
|
225
|
+
|
|
226
|
+
loop do
|
|
227
|
+
config = current_autonomous_config(runtime, po_name)
|
|
228
|
+
return false unless config
|
|
229
|
+
|
|
230
|
+
duration = [config[duration_key].to_f, 0].max
|
|
231
|
+
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at
|
|
232
|
+
remaining = duration - elapsed
|
|
233
|
+
return true if remaining <= 0
|
|
234
|
+
|
|
235
|
+
deadline = Time.now.utc + remaining
|
|
236
|
+
deadline_key = [duration, config["interval"], config["initial_delay"], config["wait_for_human"]]
|
|
237
|
+
if deadline_key != published_deadline
|
|
238
|
+
runtime.update_autonomous_schedule(
|
|
239
|
+
po_name,
|
|
240
|
+
status: "scheduled",
|
|
241
|
+
interval: config["interval"],
|
|
242
|
+
initial_delay: config["initial_delay"],
|
|
243
|
+
wait_for_human: config["wait_for_human"],
|
|
244
|
+
next_run_at: deadline.iso8601(6)
|
|
245
|
+
)
|
|
246
|
+
published_deadline = deadline_key
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
subtask.sleep([remaining, 0.1].min)
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def self.current_autonomous_config(runtime, po_name)
|
|
254
|
+
po = runtime.registry.get(po_name)
|
|
255
|
+
return nil unless po.is_a?(PromptObject) && po.autonomous?
|
|
256
|
+
|
|
257
|
+
config = po.autonomous_config
|
|
258
|
+
config if config["start_on_boot"]
|
|
259
|
+
end
|
|
260
|
+
|
|
92
261
|
# Write a .server file so CLI clients can discover the running server.
|
|
93
262
|
# @param env_path [String] Path to the environment directory
|
|
94
263
|
# @param host [String] Server host
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PromptObjects
|
|
4
|
+
# Base class for environment background services.
|
|
5
|
+
# Services run alongside the server and can publish to the message bus,
|
|
6
|
+
# write to env_data, and perform periodic background work.
|
|
7
|
+
#
|
|
8
|
+
# Subclasses live in an environment's services/ directory and are loaded
|
|
9
|
+
# automatically when the server starts.
|
|
10
|
+
#
|
|
11
|
+
# Example:
|
|
12
|
+
# module PromptObjects
|
|
13
|
+
# module Services
|
|
14
|
+
# class MyHeartbeat < PromptObjects::Service
|
|
15
|
+
# def description = "Polls external API every 10s"
|
|
16
|
+
# def interval = 10
|
|
17
|
+
# def tick
|
|
18
|
+
# @runtime.bus.publish(from: "service:#{name}", to: "broadcast", message: "heartbeat")
|
|
19
|
+
# end
|
|
20
|
+
# end
|
|
21
|
+
# end
|
|
22
|
+
# end
|
|
23
|
+
# Namespace for custom environment services.
|
|
24
|
+
module Services; end
|
|
25
|
+
|
|
26
|
+
class Service
|
|
27
|
+
attr_reader :name, :description
|
|
28
|
+
|
|
29
|
+
def initialize(runtime:)
|
|
30
|
+
@runtime = runtime
|
|
31
|
+
@name = self.class.name.split("::").last
|
|
32
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
33
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
34
|
+
.downcase
|
|
35
|
+
@description = nil
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Called when the server starts. Override to initialize resources.
|
|
39
|
+
def start; end
|
|
40
|
+
|
|
41
|
+
# Called when the server stops. Override to clean up resources.
|
|
42
|
+
def stop; end
|
|
43
|
+
|
|
44
|
+
# Seconds between ticks. Return nil to disable auto-tick.
|
|
45
|
+
def interval; nil; end
|
|
46
|
+
|
|
47
|
+
# Called every `interval` seconds. Override to perform periodic work.
|
|
48
|
+
def tick; end
|
|
49
|
+
|
|
50
|
+
# --- Shared environment data ---
|
|
51
|
+
#
|
|
52
|
+
# Services read and write the same env_data store as POs and autonomous
|
|
53
|
+
# ticks, scoped to the environment's active session. This replaces the old
|
|
54
|
+
# "__services__" sentinel thread id — heartbeat-style services no longer
|
|
55
|
+
# need to invent a scope, and what they store is visible to POs (and the
|
|
56
|
+
# live env data pane in the UI).
|
|
57
|
+
|
|
58
|
+
# Store a value in shared env_data, scoped to the active environment session.
|
|
59
|
+
# Creates the environment session on first write if one doesn't exist yet.
|
|
60
|
+
# @param key [String] Data key
|
|
61
|
+
# @param value [Object] JSON-serializable value
|
|
62
|
+
# @param short_description [String, nil] Summary for discoverability
|
|
63
|
+
# @return [Boolean] True if stored
|
|
64
|
+
def set_env_data(key, value, short_description: nil)
|
|
65
|
+
scope = env_data_scope(create: true)
|
|
66
|
+
return false unless scope
|
|
67
|
+
|
|
68
|
+
@runtime.session_store.store_env_data(
|
|
69
|
+
root_thread_id: scope,
|
|
70
|
+
key: key,
|
|
71
|
+
short_description: short_description || key.to_s,
|
|
72
|
+
value: value,
|
|
73
|
+
stored_by: "service:#{name}"
|
|
74
|
+
)
|
|
75
|
+
@runtime.notify_env_data_changed(
|
|
76
|
+
action: "set", root_thread_id: scope, key: key, stored_by: "service:#{name}"
|
|
77
|
+
)
|
|
78
|
+
true
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Read a value from shared env_data.
|
|
82
|
+
# @param key [String] Data key
|
|
83
|
+
# @return [Object, nil] The stored value, or nil if absent
|
|
84
|
+
def get_env_data(key)
|
|
85
|
+
scope = env_data_scope
|
|
86
|
+
return nil unless scope
|
|
87
|
+
|
|
88
|
+
entry = @runtime.session_store.get_env_data(root_thread_id: scope, key: key)
|
|
89
|
+
entry && entry[:value]
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# List env_data keys and descriptions in the active environment session.
|
|
93
|
+
# @return [Array<Hash>] Entries with :key and :short_description
|
|
94
|
+
def list_env_data
|
|
95
|
+
scope = env_data_scope
|
|
96
|
+
return [] unless scope
|
|
97
|
+
|
|
98
|
+
@runtime.session_store.list_env_data(root_thread_id: scope)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Delete an env_data entry.
|
|
102
|
+
# @param key [String] Data key
|
|
103
|
+
# @return [Boolean] True if a row was deleted
|
|
104
|
+
def delete_env_data(key)
|
|
105
|
+
scope = env_data_scope
|
|
106
|
+
return false unless scope
|
|
107
|
+
|
|
108
|
+
deleted = @runtime.session_store.delete_env_data(root_thread_id: scope, key: key)
|
|
109
|
+
if deleted
|
|
110
|
+
@runtime.notify_env_data_changed(
|
|
111
|
+
action: "delete", root_thread_id: scope, key: key, stored_by: "service:#{name}"
|
|
112
|
+
)
|
|
113
|
+
end
|
|
114
|
+
deleted
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
private
|
|
118
|
+
|
|
119
|
+
# Resolve the env_data scope (the active environment session). Pass
|
|
120
|
+
# create: true to establish the session on first write.
|
|
121
|
+
# @return [String, nil]
|
|
122
|
+
def env_data_scope(create: false)
|
|
123
|
+
return nil unless @runtime&.session_store
|
|
124
|
+
|
|
125
|
+
@runtime.active_session(initiated_by: "service:#{name}") if create
|
|
126
|
+
@runtime.env_data_scope
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|