prompt_objects 0.6.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 +126 -0
- data/README.md +93 -22
- 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 +1337 -20
- 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 +171 -519
- 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 +549 -53
- data/lib/prompt_objects/server/app.rb +133 -14
- 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 -227
- data/lib/prompt_objects/server.rb +170 -1
- data/lib/prompt_objects/service.rb +129 -0
- data/lib/prompt_objects/session/store.rb +600 -13
- 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 +23 -15
- 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 +8 -14
- 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 +10 -14
- 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/{store_env_data.rb → set_env_data.rb} +34 -20
- data/lib/prompt_objects/version.rb +5 -0
- data/lib/prompt_objects.rb +33 -8
- 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 +2 -2
- data/templates/arc-agi-1/objects/solver.md +6 -5
- data/templates/arc-agi-1/objects/verifier.md +2 -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 +1 -1
- metadata +172 -65
- data/CLAUDE.md +0 -177
- data/Gemfile +0 -10
- data/Rakefile +0 -27
- 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 -104
- 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/EnvDataPane.tsx +0 -69
- data/frontend/src/components/Inspector.tsx +0 -263
- 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 -559
- data/frontend/src/index.css +0 -102
- data/frontend/src/main.tsx +0 -10
- data/frontend/src/store/index.ts +0 -311
- data/frontend/src/types/index.ts +0 -176
- 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-DEPawnfZ.js +0 -4345
- data/lib/prompt_objects/server/public/assets/index-oMrRce1m.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/lib/prompt_objects/universal/update_env_data.rb +0 -88
- 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
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "json"
|
|
4
4
|
require "rack"
|
|
5
|
+
require "yaml"
|
|
5
6
|
|
|
6
7
|
module PromptObjects
|
|
7
8
|
module Server
|
|
@@ -11,11 +12,27 @@ module PromptObjects
|
|
|
11
12
|
class Routes
|
|
12
13
|
def initialize(runtime)
|
|
13
14
|
@runtime = runtime
|
|
15
|
+
if runtime.session_store
|
|
16
|
+
@workspace_repository = Repositories::WorkspaceSessionRepository.new(runtime.session_store)
|
|
17
|
+
@thread_repository = Repositories::ThreadRepository.new(runtime.session_store)
|
|
18
|
+
@run_repository = Repositories::RunRepository.new(runtime.session_store)
|
|
19
|
+
@message_repository = Repositories::MessageRepository.new(runtime.session_store)
|
|
20
|
+
@event_repository = Repositories::EventRepository.new(runtime.session_store)
|
|
21
|
+
@artifact_repository = Repositories::ArtifactRepository.new(runtime.session_store)
|
|
22
|
+
@env_data_repository = Repositories::EnvDataRepository.new(runtime.session_store)
|
|
23
|
+
@run_trace_builder = Operations::RunTraceBuilder.new(runtime.session_store)
|
|
24
|
+
end
|
|
14
25
|
end
|
|
15
26
|
|
|
16
27
|
def call(env)
|
|
17
28
|
request = Rack::Request.new(env)
|
|
18
|
-
|
|
29
|
+
# Falcon delivers PATH_INFO as ASCII-8BIT. SQLite binds binary strings
|
|
30
|
+
# as BLOBs, which never compare equal to TEXT columns — so any keyed
|
|
31
|
+
# lookup from a URL path (artifacts, sessions) would silently miss.
|
|
32
|
+
# Normalize to UTF-8 before routing.
|
|
33
|
+
path = request.path_info.dup.force_encoding(Encoding::UTF_8)
|
|
34
|
+
path = path.scrub unless path.valid_encoding?
|
|
35
|
+
path = path.sub("/api", "")
|
|
19
36
|
|
|
20
37
|
response = route(request, path)
|
|
21
38
|
json_response(response)
|
|
@@ -42,6 +59,8 @@ module PromptObjects
|
|
|
42
59
|
case path
|
|
43
60
|
when "/environment"
|
|
44
61
|
get_environment
|
|
62
|
+
when "/diagnostics"
|
|
63
|
+
@runtime.operational_diagnostics
|
|
45
64
|
when "/prompt_objects"
|
|
46
65
|
list_prompt_objects
|
|
47
66
|
when "/primitives"
|
|
@@ -56,8 +75,36 @@ module PromptObjects
|
|
|
56
75
|
list_sessions($1)
|
|
57
76
|
when %r{^/prompt_objects/([^/]+)$}
|
|
58
77
|
get_prompt_object($1)
|
|
78
|
+
when "/sessions"
|
|
79
|
+
get_environment_sessions
|
|
80
|
+
when "/workspaces"
|
|
81
|
+
list_workspaces
|
|
82
|
+
when %r{^/workspaces/([^/]+)/snapshot$}
|
|
83
|
+
get_workspace_snapshot($1)
|
|
84
|
+
when %r{^/workspaces/([^/]+)/threads$}
|
|
85
|
+
list_runtime_threads($1, _request)
|
|
86
|
+
when %r{^/workspaces/([^/]+)/runs$}
|
|
87
|
+
list_runtime_runs($1, _request)
|
|
88
|
+
when %r{^/workspaces/([^/]+)/events$}
|
|
89
|
+
list_workspace_events($1, _request)
|
|
90
|
+
when %r{^/workspaces/([^/]+)/artifacts$}
|
|
91
|
+
list_runtime_artifacts($1)
|
|
92
|
+
when %r{^/workspaces/([^/]+)/env_data$}
|
|
93
|
+
list_runtime_env_data($1)
|
|
94
|
+
when %r{^/threads/([^/]+)/messages$}
|
|
95
|
+
list_runtime_messages($1, _request)
|
|
96
|
+
when %r{^/threads/([^/]+)$}
|
|
97
|
+
get_runtime_thread($1)
|
|
98
|
+
when %r{^/runs/([^/]+)/events$}
|
|
99
|
+
list_run_events($1, _request)
|
|
100
|
+
when %r{^/runs/([^/]+)/trace$}
|
|
101
|
+
get_run_trace($1)
|
|
102
|
+
when %r{^/runs/([^/]+)$}
|
|
103
|
+
get_runtime_run($1)
|
|
59
104
|
when %r{^/events/session/([^/]+)$}
|
|
60
105
|
get_session_events($1)
|
|
106
|
+
when %r{^/artifacts/([^/]+)$}
|
|
107
|
+
get_artifact($1)
|
|
61
108
|
when %r{^/sessions/([^/]+)/env_data$}
|
|
62
109
|
get_session_env_data($1)
|
|
63
110
|
when %r{^/sessions/([^/]+)/usage$}
|
|
@@ -71,12 +118,32 @@ module PromptObjects
|
|
|
71
118
|
|
|
72
119
|
def route_post(request, path)
|
|
73
120
|
case path
|
|
121
|
+
when "/messages"
|
|
122
|
+
inject_message(request.body.read)
|
|
74
123
|
when "/prompt_objects"
|
|
75
124
|
create_prompt_object(request.body.read)
|
|
76
125
|
when %r{^/prompt_objects/([^/]+)/message$}
|
|
77
126
|
send_message($1, request.body.read)
|
|
78
127
|
when %r{^/prompt_objects/([^/]+)/sessions$}
|
|
79
128
|
create_session($1, request.body.read)
|
|
129
|
+
when "/sessions"
|
|
130
|
+
create_environment_session_endpoint(request.body.read)
|
|
131
|
+
when %r{^/sessions/([^/]+)/activate$}
|
|
132
|
+
activate_session($1)
|
|
133
|
+
when %r{^/sessions/([^/]+)/rename$}
|
|
134
|
+
rename_session_endpoint($1, request.body.read)
|
|
135
|
+
when %r{^/sessions/([^/]+)/delete$}
|
|
136
|
+
delete_session_endpoint($1)
|
|
137
|
+
when %r{^/artifacts/([^/]+)/message$}
|
|
138
|
+
artifact_message($1, request.body.read)
|
|
139
|
+
when %r{^/runs/([^/]+)/cancel$}
|
|
140
|
+
cancel_runtime_run($1, request.body.read)
|
|
141
|
+
when %r{^/workspaces/([^/]+)/threads$}
|
|
142
|
+
create_runtime_thread($1, request.body.read)
|
|
143
|
+
when %r{^/threads/([^/]+)/rename$}
|
|
144
|
+
rename_runtime_thread($1, request.body.read)
|
|
145
|
+
when %r{^/threads/([^/]+)/archive$}
|
|
146
|
+
archive_runtime_thread($1)
|
|
80
147
|
else
|
|
81
148
|
{ error: "Not found", path: path }
|
|
82
149
|
end
|
|
@@ -124,13 +191,89 @@ module PromptObjects
|
|
|
124
191
|
po_full(po)
|
|
125
192
|
end
|
|
126
193
|
|
|
194
|
+
# POST /api/prompt_objects — create a new Prompt-Object from JSON.
|
|
195
|
+
# Body: { name, description, capabilities?, identity?, behavior? }
|
|
196
|
+
# Mirrors the create_capability universal: writes a .md and loads it.
|
|
127
197
|
def create_prompt_object(body)
|
|
128
|
-
|
|
129
|
-
|
|
198
|
+
params = JSON.parse(body)
|
|
199
|
+
name = params["name"].to_s
|
|
200
|
+
description = params["description"].to_s
|
|
201
|
+
capabilities = params["capabilities"] || []
|
|
202
|
+
identity = params["identity"] || "You are a helpful prompt object."
|
|
203
|
+
behavior = params["behavior"] || "Help with the request."
|
|
204
|
+
|
|
205
|
+
return { error: "name is required" } if name.empty?
|
|
206
|
+
unless name.match?(/\A[a-z][a-z0-9_]*\z/)
|
|
207
|
+
return { error: "name must be lowercase letters, numbers, and underscores, starting with a letter" }
|
|
208
|
+
end
|
|
209
|
+
return { error: "A capability named '#{name}' already exists" } if @runtime.registry.exists?(name)
|
|
210
|
+
return { error: "No objects directory available" } unless @runtime.objects_dir
|
|
211
|
+
|
|
212
|
+
frontmatter = { "name" => name, "description" => description, "capabilities" => capabilities }
|
|
213
|
+
body_md = <<~MD
|
|
214
|
+
# #{name.split('_').map(&:capitalize).join(' ')}
|
|
215
|
+
|
|
216
|
+
## Identity
|
|
217
|
+
|
|
218
|
+
#{identity}
|
|
219
|
+
|
|
220
|
+
## Behavior
|
|
221
|
+
|
|
222
|
+
#{behavior}
|
|
223
|
+
MD
|
|
224
|
+
content = "#{frontmatter.to_yaml}---\n\n#{body_md}"
|
|
225
|
+
|
|
226
|
+
path = File.join(@runtime.objects_dir, "#{name}.md")
|
|
227
|
+
File.write(path, content, encoding: "UTF-8")
|
|
228
|
+
po = @runtime.load_prompt_object(path)
|
|
229
|
+
|
|
230
|
+
po_summary(po)
|
|
231
|
+
rescue JSON::ParserError
|
|
232
|
+
{ error: "Invalid JSON body" }
|
|
233
|
+
rescue => e
|
|
234
|
+
{ error: e.message }
|
|
130
235
|
end
|
|
131
236
|
|
|
132
237
|
# === Messages ===
|
|
133
238
|
|
|
239
|
+
# POST /api/messages — Universal message injection endpoint.
|
|
240
|
+
# External systems (webhooks, cron, bots) use this to trigger POs.
|
|
241
|
+
# Uses runtime.inject_message — same code path as human/timer messages.
|
|
242
|
+
def inject_message(body)
|
|
243
|
+
params = JSON.parse(body)
|
|
244
|
+
target_po = params["target_po"]
|
|
245
|
+
content = params["content"]
|
|
246
|
+
from_source = params["from_source"] || "api"
|
|
247
|
+
|
|
248
|
+
return { error: "target_po is required" } unless target_po && !target_po.empty?
|
|
249
|
+
return { error: "content is required" } unless content && !content.empty?
|
|
250
|
+
|
|
251
|
+
run, response = @runtime.execute_message_run(
|
|
252
|
+
target_po: target_po,
|
|
253
|
+
content: content,
|
|
254
|
+
from_source: from_source,
|
|
255
|
+
workspace_session_id: params["workspace_session_id"],
|
|
256
|
+
thread_id: params["thread_id"],
|
|
257
|
+
client_request_id: params["client_request_id"],
|
|
258
|
+
deadline: params["deadline"],
|
|
259
|
+
tui_mode: false
|
|
260
|
+
)
|
|
261
|
+
{
|
|
262
|
+
response: response,
|
|
263
|
+
run_id: run.id,
|
|
264
|
+
thread_id: run.thread_id,
|
|
265
|
+
workspace_session_id: run.workspace_session_id,
|
|
266
|
+
status: run.status,
|
|
267
|
+
target_po: target_po,
|
|
268
|
+
from_source: from_source
|
|
269
|
+
}
|
|
270
|
+
rescue JSON::ParserError
|
|
271
|
+
{ error: "Invalid JSON body" }
|
|
272
|
+
rescue PromptObjects::Error => e
|
|
273
|
+
{ error: e.message }
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# POST /api/prompt_objects/:name/message — Legacy per-PO message endpoint.
|
|
134
277
|
def send_message(po_name, body)
|
|
135
278
|
po = @runtime.registry.get(po_name)
|
|
136
279
|
|
|
@@ -140,50 +283,76 @@ module PromptObjects
|
|
|
140
283
|
|
|
141
284
|
params = JSON.parse(body)
|
|
142
285
|
message = params["message"]
|
|
143
|
-
session_id = params["session_id"]
|
|
144
286
|
new_thread = params["new_thread"]
|
|
145
287
|
|
|
146
288
|
return { error: "Message is required" } unless message && !message.empty?
|
|
147
289
|
|
|
148
|
-
|
|
290
|
+
workspace_id = params["workspace_session_id"] || @runtime.active_session(initiated_by: "api")[:id]
|
|
291
|
+
thread_id = params["thread_id"] || params["session_id"]
|
|
149
292
|
if new_thread
|
|
150
|
-
|
|
293
|
+
thread_id = @runtime.create_runtime_thread(
|
|
294
|
+
workspace_session_id: workspace_id,
|
|
295
|
+
po_name: po_name,
|
|
296
|
+
thread_type: "api",
|
|
297
|
+
name: params["name"]
|
|
298
|
+
).id
|
|
151
299
|
end
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
context.current_capability = "human"
|
|
163
|
-
|
|
164
|
-
# Log to bus
|
|
165
|
-
@runtime.bus.publish(from: "human", to: po.name, message: message, session_id: request_session_id)
|
|
166
|
-
|
|
167
|
-
response = po.receive(message, context: context)
|
|
168
|
-
|
|
169
|
-
# Log response to bus
|
|
170
|
-
@runtime.bus.publish(from: po.name, to: "human", message: response, session_id: request_session_id)
|
|
171
|
-
|
|
172
|
-
# Count events for this session
|
|
173
|
-
event_count = if @runtime.session_store
|
|
174
|
-
@runtime.session_store.get_events(session_id: request_session_id).length
|
|
175
|
-
end
|
|
176
|
-
|
|
300
|
+
run, response = @runtime.execute_message_run(
|
|
301
|
+
target_po: po_name,
|
|
302
|
+
content: message,
|
|
303
|
+
from_source: params["from_source"] || "api",
|
|
304
|
+
workspace_session_id: workspace_id,
|
|
305
|
+
thread_id: thread_id,
|
|
306
|
+
client_request_id: params["client_request_id"],
|
|
307
|
+
deadline: params["deadline"],
|
|
308
|
+
tui_mode: false
|
|
309
|
+
)
|
|
177
310
|
{
|
|
178
311
|
response: response,
|
|
179
|
-
po_name:
|
|
180
|
-
session_id:
|
|
181
|
-
|
|
312
|
+
po_name: po_name,
|
|
313
|
+
session_id: run.thread_id,
|
|
314
|
+
thread_id: run.thread_id,
|
|
315
|
+
run_id: run.id,
|
|
316
|
+
workspace_session_id: run.workspace_session_id,
|
|
317
|
+
status: run.status,
|
|
318
|
+
event_count: @event_repository.run_events(run_id: run.id).length
|
|
182
319
|
}
|
|
183
320
|
rescue JSON::ParserError
|
|
184
321
|
{ error: "Invalid JSON body" }
|
|
185
322
|
end
|
|
186
323
|
|
|
324
|
+
def cancel_runtime_run(run_id, body)
|
|
325
|
+
params = body.to_s.empty? ? {} : JSON.parse(body)
|
|
326
|
+
cancelled = @runtime.cancel_run(run_id, reason: params["reason"] || "api request")
|
|
327
|
+
{ run_id: run_id, cancelled: cancelled }
|
|
328
|
+
rescue JSON::ParserError
|
|
329
|
+
{ error: "Invalid JSON body" }
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def create_runtime_thread(workspace_id, body)
|
|
333
|
+
params = JSON.parse(body)
|
|
334
|
+
thread = @runtime.create_runtime_thread(
|
|
335
|
+
workspace_session_id: workspace_id,
|
|
336
|
+
po_name: params["po_name"],
|
|
337
|
+
thread_type: params["thread_type"] || "human",
|
|
338
|
+
name: params["name"]
|
|
339
|
+
)
|
|
340
|
+
serialize_record(thread)
|
|
341
|
+
rescue JSON::ParserError
|
|
342
|
+
{ error: "Invalid JSON body" }
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def rename_runtime_thread(thread_id, body)
|
|
346
|
+
params = JSON.parse(body)
|
|
347
|
+
serialize_record(@runtime.rename_runtime_thread(thread_id, name: params["name"]))
|
|
348
|
+
rescue JSON::ParserError
|
|
349
|
+
{ error: "Invalid JSON body" }
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
def archive_runtime_thread(thread_id)
|
|
353
|
+
serialize_record(@runtime.archive_runtime_thread(thread_id))
|
|
354
|
+
end
|
|
355
|
+
|
|
187
356
|
# === Sessions ===
|
|
188
357
|
|
|
189
358
|
def list_sessions(po_name)
|
|
@@ -193,9 +362,12 @@ module PromptObjects
|
|
|
193
362
|
return { error: "Not found", name: po_name }
|
|
194
363
|
end
|
|
195
364
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
365
|
+
workspace_id = @runtime.active_session(initiated_by: "api")[:id]
|
|
366
|
+
sessions = @thread_repository.list(
|
|
367
|
+
workspace_session_id: workspace_id,
|
|
368
|
+
po_name: po_name,
|
|
369
|
+
include_archived: true
|
|
370
|
+
).map { |thread| serialize_record(thread) }
|
|
199
371
|
|
|
200
372
|
{ sessions: sessions }
|
|
201
373
|
end
|
|
@@ -207,23 +379,19 @@ module PromptObjects
|
|
|
207
379
|
return { error: "Prompt object not found", name: po_name }
|
|
208
380
|
end
|
|
209
381
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
session = @runtime.session_store.get_session(session_id)
|
|
213
|
-
|
|
214
|
-
unless session && session[:po_name] == po_name
|
|
382
|
+
thread = @thread_repository.get(session_id)
|
|
383
|
+
unless thread && thread.po_name == po_name
|
|
215
384
|
return { error: "Session not found", id: session_id }
|
|
216
385
|
end
|
|
217
|
-
|
|
218
|
-
messages = @runtime.session_store.get_messages(session_id)
|
|
386
|
+
messages = @message_repository.list(thread_id: session_id, limit: 10_000)
|
|
219
387
|
|
|
220
388
|
{
|
|
221
|
-
id:
|
|
222
|
-
name:
|
|
223
|
-
po_name:
|
|
389
|
+
id: thread.id,
|
|
390
|
+
name: thread.name,
|
|
391
|
+
po_name: thread.po_name,
|
|
224
392
|
messages: messages.map { |m| format_message(m) },
|
|
225
|
-
created_at:
|
|
226
|
-
updated_at:
|
|
393
|
+
created_at: thread.created_at&.iso8601,
|
|
394
|
+
updated_at: thread.updated_at&.iso8601
|
|
227
395
|
}
|
|
228
396
|
end
|
|
229
397
|
|
|
@@ -237,15 +405,173 @@ module PromptObjects
|
|
|
237
405
|
params = body.empty? ? {} : JSON.parse(body)
|
|
238
406
|
session_name = params["name"]
|
|
239
407
|
|
|
240
|
-
|
|
408
|
+
workspace_id = params["workspace_session_id"] || @runtime.active_session(initiated_by: "api")[:id]
|
|
409
|
+
thread = @runtime.create_runtime_thread(
|
|
410
|
+
workspace_session_id: workspace_id,
|
|
411
|
+
po_name: po_name,
|
|
412
|
+
thread_type: "human",
|
|
413
|
+
name: session_name
|
|
414
|
+
)
|
|
241
415
|
|
|
242
416
|
{
|
|
243
417
|
success: true,
|
|
244
|
-
session_id:
|
|
418
|
+
session_id: thread.id,
|
|
419
|
+
thread_id: thread.id,
|
|
420
|
+
workspace_session_id: thread.workspace_session_id,
|
|
245
421
|
name: session_name
|
|
246
422
|
}
|
|
247
423
|
end
|
|
248
424
|
|
|
425
|
+
# === Run runtime queries ===
|
|
426
|
+
|
|
427
|
+
def list_workspaces
|
|
428
|
+
return no_runtime_store unless @workspace_repository
|
|
429
|
+
|
|
430
|
+
{ workspaces: @workspace_repository.list.map { |workspace| serialize_record(workspace) } }
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
def get_workspace_snapshot(id)
|
|
434
|
+
return no_runtime_store unless @workspace_repository
|
|
435
|
+
|
|
436
|
+
workspace = @workspace_repository.get(id)
|
|
437
|
+
return { error: "Workspace not found", id: id } unless workspace
|
|
438
|
+
|
|
439
|
+
{
|
|
440
|
+
version: Execution::Contracts::PROTOCOL_VERSION,
|
|
441
|
+
type: "workspace_snapshot",
|
|
442
|
+
payload: {
|
|
443
|
+
cursor: @event_repository.current_cursor(workspace_session_id: id),
|
|
444
|
+
workspace: serialize_record(workspace),
|
|
445
|
+
threads: @thread_repository.list(
|
|
446
|
+
workspace_session_id: id,
|
|
447
|
+
include_archived: true
|
|
448
|
+
).map { |thread| serialize_record(thread) },
|
|
449
|
+
runs: @run_repository.list(
|
|
450
|
+
workspace_session_id: id,
|
|
451
|
+
limit: 500
|
|
452
|
+
).map { |run| serialize_record(run) },
|
|
453
|
+
artifacts: @artifact_repository.list(
|
|
454
|
+
workspace_session_id: id
|
|
455
|
+
).map { |artifact| serialize_record(artifact) }
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
def list_runtime_threads(workspace_id, request)
|
|
461
|
+
return no_runtime_store unless @thread_repository
|
|
462
|
+
return { error: "Workspace not found", id: workspace_id } unless @workspace_repository.get(workspace_id)
|
|
463
|
+
|
|
464
|
+
threads = @thread_repository.list(
|
|
465
|
+
workspace_session_id: workspace_id,
|
|
466
|
+
po_name: request.params["po_name"],
|
|
467
|
+
include_archived: request.params["include_archived"] == "true"
|
|
468
|
+
)
|
|
469
|
+
{ threads: threads.map { |thread| serialize_record(thread) } }
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
def get_runtime_thread(id)
|
|
473
|
+
return no_runtime_store unless @thread_repository
|
|
474
|
+
|
|
475
|
+
thread = @thread_repository.get(id)
|
|
476
|
+
thread ? serialize_record(thread) : { error: "Thread not found", id: id }
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
def list_runtime_messages(thread_id, request)
|
|
480
|
+
return no_runtime_store unless @message_repository
|
|
481
|
+
return { error: "Thread not found", id: thread_id } unless @thread_repository.get(thread_id)
|
|
482
|
+
|
|
483
|
+
after_id = nonnegative_integer(request.params["after_id"])
|
|
484
|
+
limit = bounded_limit(request.params["limit"], maximum: 500)
|
|
485
|
+
messages = @message_repository.list(thread_id: thread_id, after_id: after_id, limit: limit)
|
|
486
|
+
{
|
|
487
|
+
thread_id: thread_id,
|
|
488
|
+
messages: messages.map { |message| serialize_runtime_message(message) },
|
|
489
|
+
next_after_id: messages.last&.dig(:id)
|
|
490
|
+
}
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
def list_runtime_runs(workspace_id, request)
|
|
494
|
+
return no_runtime_store unless @run_repository
|
|
495
|
+
return { error: "Workspace not found", id: workspace_id } unless @workspace_repository.get(workspace_id)
|
|
496
|
+
|
|
497
|
+
statuses = request.params["status"]&.split(",")
|
|
498
|
+
runs = @run_repository.list(
|
|
499
|
+
workspace_session_id: workspace_id,
|
|
500
|
+
thread_id: request.params["thread_id"],
|
|
501
|
+
statuses: statuses,
|
|
502
|
+
limit: bounded_limit(request.params["limit"], maximum: 500)
|
|
503
|
+
)
|
|
504
|
+
{ runs: runs.map { |run| serialize_record(run) } }
|
|
505
|
+
rescue ArgumentError => e
|
|
506
|
+
{ error: e.message }
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
def get_runtime_run(id)
|
|
510
|
+
return no_runtime_store unless @run_repository
|
|
511
|
+
|
|
512
|
+
run = @run_repository.get(id)
|
|
513
|
+
run ? serialize_record(run) : { error: "Run not found", id: id }
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
def list_run_events(run_id, request)
|
|
517
|
+
return no_runtime_store unless @event_repository
|
|
518
|
+
return { error: "Run not found", id: run_id } unless @run_repository.get(run_id)
|
|
519
|
+
|
|
520
|
+
events = @event_repository.run_events(
|
|
521
|
+
run_id: run_id,
|
|
522
|
+
after_sequence: nonnegative_integer(request.params["after_sequence"])
|
|
523
|
+
)
|
|
524
|
+
{ run_id: run_id, events: events.map { |event| serialize_record(event) } }
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
def get_run_trace(run_id)
|
|
528
|
+
return no_runtime_store unless @run_trace_builder
|
|
529
|
+
return { error: "Run not found", id: run_id } unless @run_repository.get(run_id)
|
|
530
|
+
|
|
531
|
+
{ trace: serialize_value(@run_trace_builder.build(run_id)) }
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
def list_workspace_events(workspace_id, request)
|
|
535
|
+
return no_runtime_store unless @event_repository
|
|
536
|
+
return { error: "Workspace not found", id: workspace_id } unless @workspace_repository.get(workspace_id)
|
|
537
|
+
|
|
538
|
+
events = @event_repository.workspace_events(
|
|
539
|
+
workspace_session_id: workspace_id,
|
|
540
|
+
after_cursor: nonnegative_integer(request.params["after_cursor"]),
|
|
541
|
+
limit: bounded_limit(request.params["limit"], maximum: 1_000)
|
|
542
|
+
)
|
|
543
|
+
{
|
|
544
|
+
workspace_session_id: workspace_id,
|
|
545
|
+
events: events.map { |event| serialize_value(event) },
|
|
546
|
+
next_cursor: events.last&.dig(:cursor) || nonnegative_integer(request.params["after_cursor"])
|
|
547
|
+
}
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
def list_runtime_artifacts(workspace_id)
|
|
551
|
+
return no_runtime_store unless @artifact_repository
|
|
552
|
+
return { error: "Workspace not found", id: workspace_id } unless @workspace_repository.get(workspace_id)
|
|
553
|
+
|
|
554
|
+
{
|
|
555
|
+
workspace_session_id: workspace_id,
|
|
556
|
+
cursor: @event_repository.current_cursor(workspace_session_id: workspace_id),
|
|
557
|
+
artifacts: @artifact_repository.list(
|
|
558
|
+
workspace_session_id: workspace_id
|
|
559
|
+
).map { |artifact| serialize_record(artifact) }
|
|
560
|
+
}
|
|
561
|
+
end
|
|
562
|
+
|
|
563
|
+
def list_runtime_env_data(workspace_id)
|
|
564
|
+
return no_runtime_store unless @env_data_repository
|
|
565
|
+
return { error: "Workspace not found", id: workspace_id } unless @workspace_repository.get(workspace_id)
|
|
566
|
+
|
|
567
|
+
{
|
|
568
|
+
workspace_session_id: workspace_id,
|
|
569
|
+
entries: @env_data_repository.list(
|
|
570
|
+
workspace_session_id: workspace_id
|
|
571
|
+
).map { |entry| serialize_value(entry) }
|
|
572
|
+
}
|
|
573
|
+
end
|
|
574
|
+
|
|
249
575
|
# === Primitives ===
|
|
250
576
|
|
|
251
577
|
def list_primitives
|
|
@@ -316,12 +642,139 @@ module PromptObjects
|
|
|
316
642
|
}
|
|
317
643
|
end
|
|
318
644
|
|
|
645
|
+
# === Artifacts ===
|
|
646
|
+
|
|
647
|
+
def get_artifact(key)
|
|
648
|
+
return { error: "No session store" } unless @runtime.session_store
|
|
649
|
+
|
|
650
|
+
artifact = @artifact_repository&.get(key)
|
|
651
|
+
return serialize_record(artifact).merge(rendered_by: artifact.owner_po) if artifact
|
|
652
|
+
|
|
653
|
+
{ error: "Artifact not found", key: key }
|
|
654
|
+
rescue => e
|
|
655
|
+
{ error: "Artifact lookup failed: #{e.message}", key: key }
|
|
656
|
+
end
|
|
657
|
+
|
|
658
|
+
# POST /api/artifacts/:key/message — BURP (Bidirectional UI Rendering
|
|
659
|
+
# Protocol). Interactive artifacts call PromptObjects.send(...) in the
|
|
660
|
+
# browser, which lands here; the message is injected into the PO that
|
|
661
|
+
# rendered the artifact, with provenance "artifact:<title>".
|
|
662
|
+
MAX_ARTIFACT_MESSAGE_LENGTH = 4_000
|
|
663
|
+
|
|
664
|
+
def artifact_message(key, body)
|
|
665
|
+
params = JSON.parse(body)
|
|
666
|
+
content = params["content"]
|
|
667
|
+
|
|
668
|
+
return { error: "content is required" } unless content.is_a?(String) && !content.strip.empty?
|
|
669
|
+
if content.length > MAX_ARTIFACT_MESSAGE_LENGTH
|
|
670
|
+
return { error: "content too long (max #{MAX_ARTIFACT_MESSAGE_LENGTH} characters)" }
|
|
671
|
+
end
|
|
672
|
+
|
|
673
|
+
artifact = get_artifact(key)
|
|
674
|
+
return artifact if artifact[:error]
|
|
675
|
+
|
|
676
|
+
target_po = artifact[:owner_po] || artifact[:rendered_by]
|
|
677
|
+
unless target_po && @runtime.registry.get(target_po).is_a?(PromptObject)
|
|
678
|
+
return { error: "Rendering PO '#{target_po}' not found for artifact", key: key }
|
|
679
|
+
end
|
|
680
|
+
|
|
681
|
+
from_source = "artifact:#{artifact[:title] || key}"
|
|
682
|
+
# The from_source provenance is UI metadata the LLM never sees — make
|
|
683
|
+
# the interface origin visible in the message itself, so the PO knows
|
|
684
|
+
# to respond by re-rendering the artifact rather than with prose.
|
|
685
|
+
framed_content = %([via your artifact "#{artifact[:title] || key}"] #{content}\n\n) +
|
|
686
|
+
"(The human is looking at the artifact, not this chat — respond by re-rendering " \
|
|
687
|
+
"\"#{artifact[:title] || key}\" with render_artifact in replace mode, updated for this action.)"
|
|
688
|
+
run = @runtime.enqueue_run(
|
|
689
|
+
target_po: target_po,
|
|
690
|
+
content: framed_content,
|
|
691
|
+
from_source: from_source,
|
|
692
|
+
workspace_session_id: artifact[:workspace_session_id],
|
|
693
|
+
thread_id: artifact[:owner_thread_id],
|
|
694
|
+
client_request_id: params["client_request_id"],
|
|
695
|
+
deadline: params["deadline"]
|
|
696
|
+
)
|
|
697
|
+
@runtime.schedule_run(run.id, tui_mode: false)
|
|
698
|
+
{
|
|
699
|
+
queued: true,
|
|
700
|
+
run_id: run.id,
|
|
701
|
+
thread_id: run.thread_id,
|
|
702
|
+
workspace_session_id: run.workspace_session_id,
|
|
703
|
+
target_po: target_po,
|
|
704
|
+
from_source: from_source
|
|
705
|
+
}
|
|
706
|
+
rescue JSON::ParserError
|
|
707
|
+
{ error: "Invalid JSON body" }
|
|
708
|
+
rescue PromptObjects::Error => e
|
|
709
|
+
{ error: e.message }
|
|
710
|
+
end
|
|
711
|
+
|
|
712
|
+
# === Environment Sessions (shared-context "jams") ===
|
|
713
|
+
|
|
714
|
+
# GET /api/sessions — list environment sessions, with which is active.
|
|
715
|
+
def get_environment_sessions
|
|
716
|
+
return { error: "No session store" } unless @runtime.session_store
|
|
717
|
+
|
|
718
|
+
{ sessions: @runtime.environment_sessions.map { |s| serialize_environment_session(s) } }
|
|
719
|
+
end
|
|
720
|
+
|
|
721
|
+
# POST /api/sessions — create a new environment session (becomes active).
|
|
722
|
+
def create_environment_session_endpoint(body)
|
|
723
|
+
return { error: "No session store" } unless @runtime.session_store
|
|
724
|
+
|
|
725
|
+
name = parse_json(body)["name"]
|
|
726
|
+
session = @runtime.create_environment_session(name: name)
|
|
727
|
+
serialize_environment_session(session.merge(active: true))
|
|
728
|
+
end
|
|
729
|
+
|
|
730
|
+
# POST /api/sessions/:id/activate — switch the active environment session.
|
|
731
|
+
def activate_session(id)
|
|
732
|
+
session = @runtime.switch_active_session(id)
|
|
733
|
+
return { error: "Session not found", id: id } unless session
|
|
734
|
+
|
|
735
|
+
serialize_environment_session(session.merge(active: true))
|
|
736
|
+
end
|
|
737
|
+
|
|
738
|
+
# POST /api/sessions/:id/rename — rename an environment session.
|
|
739
|
+
def rename_session_endpoint(id, body)
|
|
740
|
+
name = parse_json(body)["name"]
|
|
741
|
+
return { error: "name is required" } if name.nil? || name.to_s.empty?
|
|
742
|
+
|
|
743
|
+
session = @runtime.rename_session(id, name)
|
|
744
|
+
return { error: "Session not found", id: id } unless session
|
|
745
|
+
|
|
746
|
+
serialize_environment_session(session)
|
|
747
|
+
end
|
|
748
|
+
|
|
749
|
+
# POST /api/sessions/:id/delete — delete an environment session.
|
|
750
|
+
def delete_session_endpoint(id)
|
|
751
|
+
ok = @runtime.delete_environment_session(id)
|
|
752
|
+
ok ? { deleted: id } : { error: "Session not found", id: id }
|
|
753
|
+
end
|
|
754
|
+
|
|
755
|
+
def serialize_environment_session(s)
|
|
756
|
+
{
|
|
757
|
+
id: s[:id],
|
|
758
|
+
name: s[:name],
|
|
759
|
+
active: s[:active] || false,
|
|
760
|
+
message_count: s[:message_count] || 0,
|
|
761
|
+
created_at: s[:created_at]&.iso8601,
|
|
762
|
+
updated_at: s[:updated_at]&.iso8601
|
|
763
|
+
}
|
|
764
|
+
end
|
|
765
|
+
|
|
766
|
+
def parse_json(body)
|
|
767
|
+
JSON.parse(body.to_s.empty? ? "{}" : body)
|
|
768
|
+
rescue JSON::ParserError
|
|
769
|
+
{}
|
|
770
|
+
end
|
|
771
|
+
|
|
319
772
|
# === Env Data ===
|
|
320
773
|
|
|
321
774
|
def get_session_env_data(session_id)
|
|
322
775
|
return { error: "No session store" } unless @runtime.session_store
|
|
323
776
|
|
|
324
|
-
root_thread_id = @runtime.
|
|
777
|
+
root_thread_id = @runtime.env_data_scope(session_id)
|
|
325
778
|
entries = @runtime.session_store.list_env_data_full(root_thread_id: root_thread_id)
|
|
326
779
|
|
|
327
780
|
{ root_thread_id: root_thread_id, entries: entries }
|
|
@@ -378,6 +831,44 @@ module PromptObjects
|
|
|
378
831
|
|
|
379
832
|
# === Helpers ===
|
|
380
833
|
|
|
834
|
+
def no_runtime_store
|
|
835
|
+
{ error: "Run runtime storage is unavailable" }
|
|
836
|
+
end
|
|
837
|
+
|
|
838
|
+
def bounded_limit(value, maximum:)
|
|
839
|
+
requested = value.to_i
|
|
840
|
+
requested = 100 unless requested.positive?
|
|
841
|
+
[requested, maximum].min
|
|
842
|
+
end
|
|
843
|
+
|
|
844
|
+
def nonnegative_integer(value)
|
|
845
|
+
[value.to_i, 0].max
|
|
846
|
+
end
|
|
847
|
+
|
|
848
|
+
def serialize_record(record)
|
|
849
|
+
serialize_value(record.to_h)
|
|
850
|
+
end
|
|
851
|
+
|
|
852
|
+
def serialize_runtime_message(message)
|
|
853
|
+
serialize_value(message).merge(
|
|
854
|
+
id: message[:message_uid] || message[:id].to_s,
|
|
855
|
+
cursor_id: message[:id]
|
|
856
|
+
)
|
|
857
|
+
end
|
|
858
|
+
|
|
859
|
+
def serialize_value(value)
|
|
860
|
+
case value
|
|
861
|
+
when Time
|
|
862
|
+
value.iso8601
|
|
863
|
+
when Hash
|
|
864
|
+
value.to_h { |key, nested| [key, serialize_value(nested)] }
|
|
865
|
+
when Array
|
|
866
|
+
value.map { |nested| serialize_value(nested) }
|
|
867
|
+
else
|
|
868
|
+
value
|
|
869
|
+
end
|
|
870
|
+
end
|
|
871
|
+
|
|
381
872
|
def po_summary(po)
|
|
382
873
|
po.to_summary_hash(registry: @runtime.registry)
|
|
383
874
|
end
|
|
@@ -411,7 +902,12 @@ module PromptObjects
|
|
|
411
902
|
status,
|
|
412
903
|
{
|
|
413
904
|
"content-type" => "application/json",
|
|
414
|
-
"access-control-allow-origin" => "*"
|
|
905
|
+
"access-control-allow-origin" => "*",
|
|
906
|
+
# The API is live state — sessions, artifacts, PO status all change
|
|
907
|
+
# constantly. Without this, browsers heuristically cache same-URL
|
|
908
|
+
# GETs, so a refresh after creating a session (or rendering an
|
|
909
|
+
# artifact) re-reads a stale list and the new thing never appears.
|
|
910
|
+
"cache-control" => "no-store, must-revalidate"
|
|
415
911
|
},
|
|
416
912
|
[body]
|
|
417
913
|
]
|