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
|
@@ -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,38 @@ 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)
|
|
108
|
+
when %r{^/sessions/([^/]+)/env_data$}
|
|
109
|
+
get_session_env_data($1)
|
|
61
110
|
when %r{^/sessions/([^/]+)/usage$}
|
|
62
111
|
get_session_usage($1, _request)
|
|
63
112
|
when %r{^/sessions/([^/]+)/export$}
|
|
@@ -69,12 +118,32 @@ module PromptObjects
|
|
|
69
118
|
|
|
70
119
|
def route_post(request, path)
|
|
71
120
|
case path
|
|
121
|
+
when "/messages"
|
|
122
|
+
inject_message(request.body.read)
|
|
72
123
|
when "/prompt_objects"
|
|
73
124
|
create_prompt_object(request.body.read)
|
|
74
125
|
when %r{^/prompt_objects/([^/]+)/message$}
|
|
75
126
|
send_message($1, request.body.read)
|
|
76
127
|
when %r{^/prompt_objects/([^/]+)/sessions$}
|
|
77
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)
|
|
78
147
|
else
|
|
79
148
|
{ error: "Not found", path: path }
|
|
80
149
|
end
|
|
@@ -122,13 +191,89 @@ module PromptObjects
|
|
|
122
191
|
po_full(po)
|
|
123
192
|
end
|
|
124
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.
|
|
125
197
|
def create_prompt_object(body)
|
|
126
|
-
|
|
127
|
-
|
|
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 }
|
|
128
235
|
end
|
|
129
236
|
|
|
130
237
|
# === Messages ===
|
|
131
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.
|
|
132
277
|
def send_message(po_name, body)
|
|
133
278
|
po = @runtime.registry.get(po_name)
|
|
134
279
|
|
|
@@ -138,50 +283,76 @@ module PromptObjects
|
|
|
138
283
|
|
|
139
284
|
params = JSON.parse(body)
|
|
140
285
|
message = params["message"]
|
|
141
|
-
session_id = params["session_id"]
|
|
142
286
|
new_thread = params["new_thread"]
|
|
143
287
|
|
|
144
288
|
return { error: "Message is required" } unless message && !message.empty?
|
|
145
289
|
|
|
146
|
-
|
|
290
|
+
workspace_id = params["workspace_session_id"] || @runtime.active_session(initiated_by: "api")[:id]
|
|
291
|
+
thread_id = params["thread_id"] || params["session_id"]
|
|
147
292
|
if new_thread
|
|
148
|
-
|
|
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
|
|
149
299
|
end
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
context.current_capability = "human"
|
|
161
|
-
|
|
162
|
-
# Log to bus
|
|
163
|
-
@runtime.bus.publish(from: "human", to: po.name, message: message, session_id: request_session_id)
|
|
164
|
-
|
|
165
|
-
response = po.receive(message, context: context)
|
|
166
|
-
|
|
167
|
-
# Log response to bus
|
|
168
|
-
@runtime.bus.publish(from: po.name, to: "human", message: response, session_id: request_session_id)
|
|
169
|
-
|
|
170
|
-
# Count events for this session
|
|
171
|
-
event_count = if @runtime.session_store
|
|
172
|
-
@runtime.session_store.get_events(session_id: request_session_id).length
|
|
173
|
-
end
|
|
174
|
-
|
|
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
|
+
)
|
|
175
310
|
{
|
|
176
311
|
response: response,
|
|
177
|
-
po_name:
|
|
178
|
-
session_id:
|
|
179
|
-
|
|
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
|
|
180
319
|
}
|
|
181
320
|
rescue JSON::ParserError
|
|
182
321
|
{ error: "Invalid JSON body" }
|
|
183
322
|
end
|
|
184
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
|
+
|
|
185
356
|
# === Sessions ===
|
|
186
357
|
|
|
187
358
|
def list_sessions(po_name)
|
|
@@ -191,9 +362,12 @@ module PromptObjects
|
|
|
191
362
|
return { error: "Not found", name: po_name }
|
|
192
363
|
end
|
|
193
364
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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) }
|
|
197
371
|
|
|
198
372
|
{ sessions: sessions }
|
|
199
373
|
end
|
|
@@ -205,23 +379,19 @@ module PromptObjects
|
|
|
205
379
|
return { error: "Prompt object not found", name: po_name }
|
|
206
380
|
end
|
|
207
381
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
session = @runtime.session_store.get_session(session_id)
|
|
211
|
-
|
|
212
|
-
unless session && session[:po_name] == po_name
|
|
382
|
+
thread = @thread_repository.get(session_id)
|
|
383
|
+
unless thread && thread.po_name == po_name
|
|
213
384
|
return { error: "Session not found", id: session_id }
|
|
214
385
|
end
|
|
215
|
-
|
|
216
|
-
messages = @runtime.session_store.get_messages(session_id)
|
|
386
|
+
messages = @message_repository.list(thread_id: session_id, limit: 10_000)
|
|
217
387
|
|
|
218
388
|
{
|
|
219
|
-
id:
|
|
220
|
-
name:
|
|
221
|
-
po_name:
|
|
389
|
+
id: thread.id,
|
|
390
|
+
name: thread.name,
|
|
391
|
+
po_name: thread.po_name,
|
|
222
392
|
messages: messages.map { |m| format_message(m) },
|
|
223
|
-
created_at:
|
|
224
|
-
updated_at:
|
|
393
|
+
created_at: thread.created_at&.iso8601,
|
|
394
|
+
updated_at: thread.updated_at&.iso8601
|
|
225
395
|
}
|
|
226
396
|
end
|
|
227
397
|
|
|
@@ -235,15 +405,173 @@ module PromptObjects
|
|
|
235
405
|
params = body.empty? ? {} : JSON.parse(body)
|
|
236
406
|
session_name = params["name"]
|
|
237
407
|
|
|
238
|
-
|
|
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
|
+
)
|
|
239
415
|
|
|
240
416
|
{
|
|
241
417
|
success: true,
|
|
242
|
-
session_id:
|
|
418
|
+
session_id: thread.id,
|
|
419
|
+
thread_id: thread.id,
|
|
420
|
+
workspace_session_id: thread.workspace_session_id,
|
|
243
421
|
name: session_name
|
|
244
422
|
}
|
|
245
423
|
end
|
|
246
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
|
+
|
|
247
575
|
# === Primitives ===
|
|
248
576
|
|
|
249
577
|
def list_primitives
|
|
@@ -314,6 +642,144 @@ module PromptObjects
|
|
|
314
642
|
}
|
|
315
643
|
end
|
|
316
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
|
+
|
|
772
|
+
# === Env Data ===
|
|
773
|
+
|
|
774
|
+
def get_session_env_data(session_id)
|
|
775
|
+
return { error: "No session store" } unless @runtime.session_store
|
|
776
|
+
|
|
777
|
+
root_thread_id = @runtime.env_data_scope(session_id)
|
|
778
|
+
entries = @runtime.session_store.list_env_data_full(root_thread_id: root_thread_id)
|
|
779
|
+
|
|
780
|
+
{ root_thread_id: root_thread_id, entries: entries }
|
|
781
|
+
end
|
|
782
|
+
|
|
317
783
|
# === Usage ===
|
|
318
784
|
|
|
319
785
|
def get_session_usage(session_id, request)
|
|
@@ -365,6 +831,44 @@ module PromptObjects
|
|
|
365
831
|
|
|
366
832
|
# === Helpers ===
|
|
367
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
|
+
|
|
368
872
|
def po_summary(po)
|
|
369
873
|
po.to_summary_hash(registry: @runtime.registry)
|
|
370
874
|
end
|
|
@@ -398,7 +902,12 @@ module PromptObjects
|
|
|
398
902
|
status,
|
|
399
903
|
{
|
|
400
904
|
"content-type" => "application/json",
|
|
401
|
-
"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"
|
|
402
911
|
},
|
|
403
912
|
[body]
|
|
404
913
|
]
|