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
|
@@ -3,13 +3,16 @@
|
|
|
3
3
|
require "sqlite3"
|
|
4
4
|
require "json"
|
|
5
5
|
require "securerandom"
|
|
6
|
+
require "monitor"
|
|
6
7
|
|
|
7
8
|
module PromptObjects
|
|
8
9
|
module Session
|
|
9
10
|
# SQLite-based session storage for conversation history.
|
|
10
11
|
# Each environment has its own sessions.db file (gitignored for privacy).
|
|
11
12
|
class Store
|
|
12
|
-
SCHEMA_VERSION =
|
|
13
|
+
SCHEMA_VERSION = 11
|
|
14
|
+
|
|
15
|
+
attr_reader :migration_backup_path
|
|
13
16
|
|
|
14
17
|
# Thread types for conversation branching
|
|
15
18
|
THREAD_TYPES = %w[root continuation delegation fork].freeze
|
|
@@ -20,6 +23,7 @@ module PromptObjects
|
|
|
20
23
|
# @param db_path [String] Path to the SQLite database file
|
|
21
24
|
def initialize(db_path)
|
|
22
25
|
@db_path = db_path
|
|
26
|
+
@db_monitor = Monitor.new
|
|
23
27
|
@db = SQLite3::Database.new(db_path)
|
|
24
28
|
@db.results_as_hash = true
|
|
25
29
|
|
|
@@ -29,12 +33,27 @@ module PromptObjects
|
|
|
29
33
|
# Set busy timeout to 5 seconds - wait for locks instead of failing immediately
|
|
30
34
|
@db.busy_timeout = 5000
|
|
31
35
|
|
|
36
|
+
backup_before_destructive_migration
|
|
32
37
|
setup_schema
|
|
38
|
+
@db.execute("PRAGMA foreign_keys=ON")
|
|
33
39
|
end
|
|
34
40
|
|
|
35
41
|
# Close the database connection.
|
|
36
42
|
def close
|
|
37
|
-
@db.close if @db
|
|
43
|
+
@db_monitor.synchronize { @db.close if @db }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Repositories use this serialized connection boundary during the
|
|
47
|
+
# migration away from direct Store SQL. Monitor is reentrant so a
|
|
48
|
+
# repository transaction may safely call a repository read helper.
|
|
49
|
+
def with_connection
|
|
50
|
+
@db_monitor.synchronize { yield @db }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def transaction
|
|
54
|
+
@db_monitor.synchronize do
|
|
55
|
+
@db.transaction { yield @db }
|
|
56
|
+
end
|
|
38
57
|
end
|
|
39
58
|
|
|
40
59
|
# --- Session/Thread CRUD ---
|
|
@@ -51,13 +70,13 @@ module PromptObjects
|
|
|
51
70
|
# @param thread_type [String] Type of thread: root, continuation, delegation, fork
|
|
52
71
|
# @return [String] Session ID
|
|
53
72
|
def create_session(po_name:, name: nil, source: "tui", source_client: nil, metadata: {},
|
|
54
|
-
parent_session_id: nil, parent_message_id: nil, parent_po: nil,
|
|
55
|
-
|
|
73
|
+
parent_session_id: nil, parent_message_id: nil, parent_po: nil,
|
|
74
|
+
thread_type: "root", workspace_session_id: nil, id: SecureRandom.uuid)
|
|
56
75
|
now = Time.now.utc.iso8601
|
|
57
76
|
|
|
58
|
-
@db.execute(<<~SQL, [id, po_name, name, source, source_client, source, now, now, metadata.to_json, parent_session_id, parent_message_id, parent_po, thread_type])
|
|
59
|
-
INSERT INTO sessions (id, po_name, name, source, source_client, last_message_source, created_at, updated_at, metadata, parent_session_id, parent_message_id, parent_po, thread_type)
|
|
60
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
77
|
+
@db.execute(<<~SQL, [id, po_name, name, source, source_client, source, now, now, metadata.to_json, parent_session_id, parent_message_id, parent_po, thread_type, workspace_session_id])
|
|
78
|
+
INSERT INTO sessions (id, po_name, name, source, source_client, last_message_source, created_at, updated_at, metadata, parent_session_id, parent_message_id, parent_po, thread_type, workspace_session_id)
|
|
79
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
61
80
|
SQL
|
|
62
81
|
|
|
63
82
|
id
|
|
@@ -165,6 +184,29 @@ module PromptObjects
|
|
|
165
184
|
rows.map { |row| parse_session_row(row, include_count: true) }
|
|
166
185
|
end
|
|
167
186
|
|
|
187
|
+
# List environment-level sessions (po_name IS NULL) — the shared-context
|
|
188
|
+
# "jam" sessions managed from the UI, most recent first.
|
|
189
|
+
# @param limit [Integer, nil] Maximum number of sessions
|
|
190
|
+
# @return [Array<Hash>] Session data with message counts
|
|
191
|
+
def list_environment_sessions(limit: nil)
|
|
192
|
+
sql = <<~SQL
|
|
193
|
+
SELECT s.*, COUNT(m.id) as message_count
|
|
194
|
+
FROM sessions s
|
|
195
|
+
LEFT JOIN messages m ON m.session_id = s.id
|
|
196
|
+
WHERE s.po_name IS NULL
|
|
197
|
+
GROUP BY s.id
|
|
198
|
+
ORDER BY s.updated_at DESC
|
|
199
|
+
SQL
|
|
200
|
+
|
|
201
|
+
params = []
|
|
202
|
+
if limit
|
|
203
|
+
sql += " LIMIT ?"
|
|
204
|
+
params << limit
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
@db.execute(sql, params).map { |row| parse_session_row(row, include_count: true) }
|
|
208
|
+
end
|
|
209
|
+
|
|
168
210
|
# --- Thread Navigation ---
|
|
169
211
|
|
|
170
212
|
# Get all child threads of a session.
|
|
@@ -409,10 +451,13 @@ module PromptObjects
|
|
|
409
451
|
@db.execute("UPDATE sessions SET #{updates.join(', ')} WHERE id = ?", params)
|
|
410
452
|
end
|
|
411
453
|
|
|
412
|
-
# Delete a session and
|
|
454
|
+
# Delete a session, its messages, and any env_data scoped to it.
|
|
455
|
+
# (env_data is keyed by root_thread_id; for a non-root sub-thread this
|
|
456
|
+
# DELETE is a harmless no-op since its data lives under the root.)
|
|
413
457
|
# @param id [String] Session ID
|
|
414
458
|
def delete_session(id)
|
|
415
459
|
@db.execute("DELETE FROM messages WHERE session_id = ?", [id])
|
|
460
|
+
@db.execute("DELETE FROM env_data WHERE root_thread_id = ?", [id])
|
|
416
461
|
@db.execute("DELETE FROM sessions WHERE id = ?", [id])
|
|
417
462
|
end
|
|
418
463
|
|
|
@@ -595,6 +640,137 @@ module PromptObjects
|
|
|
595
640
|
row["count"]
|
|
596
641
|
end
|
|
597
642
|
|
|
643
|
+
# --- Environment Data (Shared Key-Value Store) ---
|
|
644
|
+
|
|
645
|
+
# Resolve the root thread ID for a session by walking up the delegation chain.
|
|
646
|
+
# @param session_id [String] Any session ID in a delegation chain
|
|
647
|
+
# @return [String] The root thread's session ID
|
|
648
|
+
def resolve_root_thread(session_id)
|
|
649
|
+
lineage = get_thread_lineage(session_id)
|
|
650
|
+
return session_id if lineage.empty?
|
|
651
|
+
|
|
652
|
+
lineage.first[:id]
|
|
653
|
+
end
|
|
654
|
+
|
|
655
|
+
# Store a key-value pair scoped to a root thread.
|
|
656
|
+
# Uses INSERT OR REPLACE to create or overwrite.
|
|
657
|
+
# @param root_thread_id [String] Root thread scope
|
|
658
|
+
# @param key [String] Data key
|
|
659
|
+
# @param short_description [String] Brief description for discoverability
|
|
660
|
+
# @param value [Object] Data value (will be JSON-serialized)
|
|
661
|
+
# @param stored_by [String] PO name that stored this
|
|
662
|
+
def store_env_data(root_thread_id:, key:, short_description:, value:, stored_by:)
|
|
663
|
+
now = Time.now.utc.iso8601
|
|
664
|
+
json_value = JSON.generate(value)
|
|
665
|
+
|
|
666
|
+
@db.execute(<<~SQL, [root_thread_id, key, short_description, json_value, stored_by, now, now])
|
|
667
|
+
INSERT INTO env_data (root_thread_id, key, short_description, value, stored_by, created_at, updated_at)
|
|
668
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
669
|
+
ON CONFLICT(root_thread_id, key) DO UPDATE SET
|
|
670
|
+
short_description = excluded.short_description,
|
|
671
|
+
value = excluded.value,
|
|
672
|
+
stored_by = excluded.stored_by,
|
|
673
|
+
updated_at = excluded.updated_at
|
|
674
|
+
SQL
|
|
675
|
+
end
|
|
676
|
+
|
|
677
|
+
# Get a single env data entry by key.
|
|
678
|
+
# @param root_thread_id [String] Root thread scope
|
|
679
|
+
# @param key [String] Data key
|
|
680
|
+
# @return [Hash, nil] Entry with parsed value, or nil if not found
|
|
681
|
+
def get_env_data(root_thread_id:, key:)
|
|
682
|
+
row = @db.get_first_row(<<~SQL, [root_thread_id, key])
|
|
683
|
+
SELECT key, short_description, value, stored_by, created_at, updated_at
|
|
684
|
+
FROM env_data
|
|
685
|
+
WHERE root_thread_id = ? AND key = ?
|
|
686
|
+
SQL
|
|
687
|
+
|
|
688
|
+
return nil unless row
|
|
689
|
+
|
|
690
|
+
{
|
|
691
|
+
key: row["key"],
|
|
692
|
+
short_description: row["short_description"],
|
|
693
|
+
value: JSON.parse(row["value"], symbolize_names: true),
|
|
694
|
+
stored_by: row["stored_by"],
|
|
695
|
+
created_at: row["created_at"],
|
|
696
|
+
updated_at: row["updated_at"]
|
|
697
|
+
}
|
|
698
|
+
end
|
|
699
|
+
|
|
700
|
+
# List all env data keys and descriptions for a root thread (no values).
|
|
701
|
+
# @param root_thread_id [String] Root thread scope
|
|
702
|
+
# @return [Array<Hash>] Entries with key and short_description only
|
|
703
|
+
def list_env_data(root_thread_id:)
|
|
704
|
+
rows = @db.execute(<<~SQL, [root_thread_id])
|
|
705
|
+
SELECT key, short_description FROM env_data
|
|
706
|
+
WHERE root_thread_id = ?
|
|
707
|
+
ORDER BY key ASC
|
|
708
|
+
SQL
|
|
709
|
+
|
|
710
|
+
rows.map { |row| { key: row["key"], short_description: row["short_description"] } }
|
|
711
|
+
end
|
|
712
|
+
|
|
713
|
+
# List all env data entries with full values for a root thread.
|
|
714
|
+
# @param root_thread_id [String] Root thread scope
|
|
715
|
+
# @return [Array<Hash>] Full entries with parsed values
|
|
716
|
+
def list_env_data_full(root_thread_id:)
|
|
717
|
+
rows = @db.execute(<<~SQL, [root_thread_id])
|
|
718
|
+
SELECT key, short_description, value, stored_by, created_at, updated_at
|
|
719
|
+
FROM env_data WHERE root_thread_id = ? ORDER BY key ASC
|
|
720
|
+
SQL
|
|
721
|
+
|
|
722
|
+
rows.map do |row|
|
|
723
|
+
{
|
|
724
|
+
key: row["key"],
|
|
725
|
+
short_description: row["short_description"],
|
|
726
|
+
value: JSON.parse(row["value"]),
|
|
727
|
+
stored_by: row["stored_by"],
|
|
728
|
+
created_at: row["created_at"],
|
|
729
|
+
updated_at: row["updated_at"]
|
|
730
|
+
}
|
|
731
|
+
end
|
|
732
|
+
end
|
|
733
|
+
|
|
734
|
+
# Update an existing env data entry.
|
|
735
|
+
# @param root_thread_id [String] Root thread scope
|
|
736
|
+
# @param key [String] Data key
|
|
737
|
+
# @param short_description [String, nil] New description (keeps existing if nil)
|
|
738
|
+
# @param value [Object, nil] New value (keeps existing if nil)
|
|
739
|
+
# @param stored_by [String] PO name performing the update
|
|
740
|
+
# @return [Boolean] True if updated, false if key not found
|
|
741
|
+
def update_env_data(root_thread_id:, key:, short_description: nil, value: nil, stored_by:)
|
|
742
|
+
existing = get_env_data(root_thread_id: root_thread_id, key: key)
|
|
743
|
+
return false unless existing
|
|
744
|
+
|
|
745
|
+
updates = ["updated_at = ?", "stored_by = ?"]
|
|
746
|
+
params = [Time.now.utc.iso8601, stored_by]
|
|
747
|
+
|
|
748
|
+
if short_description
|
|
749
|
+
updates << "short_description = ?"
|
|
750
|
+
params << short_description
|
|
751
|
+
end
|
|
752
|
+
|
|
753
|
+
if value
|
|
754
|
+
updates << "value = ?"
|
|
755
|
+
params << JSON.generate(value)
|
|
756
|
+
end
|
|
757
|
+
|
|
758
|
+
params << root_thread_id
|
|
759
|
+
params << key
|
|
760
|
+
|
|
761
|
+
@db.execute("UPDATE env_data SET #{updates.join(', ')} WHERE root_thread_id = ? AND key = ?", params)
|
|
762
|
+
true
|
|
763
|
+
end
|
|
764
|
+
|
|
765
|
+
# Delete an env data entry.
|
|
766
|
+
# @param root_thread_id [String] Root thread scope
|
|
767
|
+
# @param key [String] Data key
|
|
768
|
+
# @return [Boolean] True if deleted, false if key not found
|
|
769
|
+
def delete_env_data(root_thread_id:, key:)
|
|
770
|
+
@db.execute("DELETE FROM env_data WHERE root_thread_id = ? AND key = ?", [root_thread_id, key])
|
|
771
|
+
@db.changes > 0
|
|
772
|
+
end
|
|
773
|
+
|
|
598
774
|
# --- Usage Aggregation ---
|
|
599
775
|
|
|
600
776
|
# Get total token usage for a session.
|
|
@@ -763,7 +939,7 @@ module PromptObjects
|
|
|
763
939
|
tree = get_thread_tree(session_id)
|
|
764
940
|
return nil unless tree
|
|
765
941
|
|
|
766
|
-
serialize_tree_for_export(tree)
|
|
942
|
+
serialize_tree_for_export(tree, is_root: true)
|
|
767
943
|
end
|
|
768
944
|
|
|
769
945
|
# --- Import ---
|
|
@@ -908,11 +1084,11 @@ module PromptObjects
|
|
|
908
1084
|
end
|
|
909
1085
|
end
|
|
910
1086
|
|
|
911
|
-
def serialize_tree_for_export(node)
|
|
1087
|
+
def serialize_tree_for_export(node, is_root: false)
|
|
912
1088
|
session = node[:session]
|
|
913
1089
|
messages = get_messages(session[:id])
|
|
914
1090
|
|
|
915
|
-
{
|
|
1091
|
+
result = {
|
|
916
1092
|
session: {
|
|
917
1093
|
id: session[:id],
|
|
918
1094
|
po_name: session[:po_name],
|
|
@@ -934,6 +1110,13 @@ module PromptObjects
|
|
|
934
1110
|
},
|
|
935
1111
|
children: (node[:children] || []).map { |c| serialize_tree_for_export(c) }
|
|
936
1112
|
}
|
|
1113
|
+
|
|
1114
|
+
if is_root
|
|
1115
|
+
env_data = list_env_data_full(root_thread_id: session[:id])
|
|
1116
|
+
result[:env_data] = env_data unless env_data.empty?
|
|
1117
|
+
end
|
|
1118
|
+
|
|
1119
|
+
result
|
|
937
1120
|
end
|
|
938
1121
|
|
|
939
1122
|
def setup_schema
|
|
@@ -959,9 +1142,18 @@ module PromptObjects
|
|
|
959
1142
|
|
|
960
1143
|
def create_schema
|
|
961
1144
|
@db.execute_batch(<<~SQL)
|
|
1145
|
+
CREATE TABLE IF NOT EXISTS workspace_sessions (
|
|
1146
|
+
id TEXT PRIMARY KEY,
|
|
1147
|
+
name TEXT,
|
|
1148
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
1149
|
+
created_at TEXT NOT NULL,
|
|
1150
|
+
updated_at TEXT NOT NULL,
|
|
1151
|
+
metadata TEXT NOT NULL DEFAULT '{}'
|
|
1152
|
+
);
|
|
1153
|
+
|
|
962
1154
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
963
1155
|
id TEXT PRIMARY KEY,
|
|
964
|
-
po_name TEXT
|
|
1156
|
+
po_name TEXT,
|
|
965
1157
|
name TEXT,
|
|
966
1158
|
source TEXT DEFAULT 'tui',
|
|
967
1159
|
source_client TEXT,
|
|
@@ -973,13 +1165,24 @@ module PromptObjects
|
|
|
973
1165
|
parent_session_id TEXT,
|
|
974
1166
|
parent_message_id INTEGER,
|
|
975
1167
|
parent_po TEXT,
|
|
976
|
-
thread_type TEXT DEFAULT 'root'
|
|
1168
|
+
thread_type TEXT DEFAULT 'root',
|
|
1169
|
+
-- Environment-level sessions (v8)
|
|
1170
|
+
session_type TEXT DEFAULT 'environment',
|
|
1171
|
+
initiated_by TEXT,
|
|
1172
|
+
-- Run-based runtime foundation (v10)
|
|
1173
|
+
workspace_session_id TEXT REFERENCES workspace_sessions(id),
|
|
1174
|
+
parent_run_id TEXT,
|
|
1175
|
+
retention_policy TEXT,
|
|
1176
|
+
archived INTEGER NOT NULL DEFAULT 0,
|
|
1177
|
+
last_run_status TEXT,
|
|
1178
|
+
last_activity_at TEXT
|
|
977
1179
|
);
|
|
978
1180
|
|
|
979
1181
|
CREATE INDEX IF NOT EXISTS idx_sessions_po_name ON sessions(po_name);
|
|
980
1182
|
CREATE INDEX IF NOT EXISTS idx_sessions_updated_at ON sessions(updated_at);
|
|
981
1183
|
CREATE INDEX IF NOT EXISTS idx_sessions_source ON sessions(source);
|
|
982
1184
|
CREATE INDEX IF NOT EXISTS idx_sessions_parent ON sessions(parent_session_id);
|
|
1185
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_workspace ON sessions(workspace_session_id);
|
|
983
1186
|
|
|
984
1187
|
CREATE TABLE IF NOT EXISTS messages (
|
|
985
1188
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -990,10 +1193,21 @@ module PromptObjects
|
|
|
990
1193
|
tool_calls TEXT,
|
|
991
1194
|
tool_results TEXT,
|
|
992
1195
|
usage TEXT,
|
|
993
|
-
created_at TEXT NOT NULL
|
|
1196
|
+
created_at TEXT NOT NULL,
|
|
1197
|
+
-- Environment-level sessions (v8)
|
|
1198
|
+
target_po TEXT,
|
|
1199
|
+
from_source TEXT,
|
|
1200
|
+
-- Run provenance and stable logical ordering (v10)
|
|
1201
|
+
message_uid TEXT,
|
|
1202
|
+
run_id TEXT,
|
|
1203
|
+
turn_ordinal INTEGER,
|
|
1204
|
+
position INTEGER
|
|
994
1205
|
);
|
|
995
1206
|
|
|
996
1207
|
CREATE INDEX IF NOT EXISTS idx_messages_session_id ON messages(session_id);
|
|
1208
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_messages_uid ON messages(message_uid) WHERE message_uid IS NOT NULL;
|
|
1209
|
+
CREATE INDEX IF NOT EXISTS idx_messages_run ON messages(run_id);
|
|
1210
|
+
CREATE INDEX IF NOT EXISTS idx_messages_turn ON messages(session_id, turn_ordinal, position);
|
|
997
1211
|
|
|
998
1212
|
-- Full-text search index for message content
|
|
999
1213
|
CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(
|
|
@@ -1030,6 +1244,144 @@ module PromptObjects
|
|
|
1030
1244
|
|
|
1031
1245
|
CREATE INDEX IF NOT EXISTS idx_events_session ON events(session_id);
|
|
1032
1246
|
CREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp);
|
|
1247
|
+
|
|
1248
|
+
-- Shared environment data, scoped to session (v7 original, v8 adds session_id alias)
|
|
1249
|
+
CREATE TABLE IF NOT EXISTS env_data (
|
|
1250
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1251
|
+
root_thread_id TEXT NOT NULL,
|
|
1252
|
+
session_id TEXT,
|
|
1253
|
+
key TEXT NOT NULL,
|
|
1254
|
+
short_description TEXT NOT NULL,
|
|
1255
|
+
value TEXT NOT NULL,
|
|
1256
|
+
stored_by TEXT NOT NULL,
|
|
1257
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
1258
|
+
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
1259
|
+
workspace_session_id TEXT REFERENCES workspace_sessions(id),
|
|
1260
|
+
UNIQUE(root_thread_id, key)
|
|
1261
|
+
);
|
|
1262
|
+
|
|
1263
|
+
CREATE INDEX IF NOT EXISTS idx_env_data_root ON env_data(root_thread_id);
|
|
1264
|
+
CREATE INDEX IF NOT EXISTS idx_env_data_session ON env_data(session_id);
|
|
1265
|
+
CREATE INDEX IF NOT EXISTS idx_env_data_workspace ON env_data(workspace_session_id);
|
|
1266
|
+
|
|
1267
|
+
CREATE TABLE IF NOT EXISTS runs (
|
|
1268
|
+
id TEXT PRIMARY KEY,
|
|
1269
|
+
client_request_id TEXT,
|
|
1270
|
+
workspace_session_id TEXT NOT NULL REFERENCES workspace_sessions(id),
|
|
1271
|
+
thread_id TEXT NOT NULL REFERENCES sessions(id),
|
|
1272
|
+
po_name TEXT NOT NULL,
|
|
1273
|
+
turn_ordinal INTEGER NOT NULL,
|
|
1274
|
+
input_content TEXT,
|
|
1275
|
+
input_message_id INTEGER REFERENCES messages(id),
|
|
1276
|
+
history_cutoff_message_id INTEGER REFERENCES messages(id),
|
|
1277
|
+
definition_version TEXT NOT NULL,
|
|
1278
|
+
provider TEXT,
|
|
1279
|
+
model TEXT,
|
|
1280
|
+
capability_policy_version TEXT,
|
|
1281
|
+
source_type TEXT NOT NULL,
|
|
1282
|
+
source_name TEXT,
|
|
1283
|
+
parent_run_id TEXT REFERENCES runs(id),
|
|
1284
|
+
root_run_id TEXT,
|
|
1285
|
+
status TEXT NOT NULL,
|
|
1286
|
+
priority INTEGER NOT NULL DEFAULT 0,
|
|
1287
|
+
enqueued_at TEXT NOT NULL,
|
|
1288
|
+
started_at TEXT,
|
|
1289
|
+
finished_at TEXT,
|
|
1290
|
+
cancellation_reason TEXT,
|
|
1291
|
+
final_response TEXT,
|
|
1292
|
+
usage TEXT,
|
|
1293
|
+
error_class TEXT,
|
|
1294
|
+
error_message TEXT,
|
|
1295
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
1296
|
+
UNIQUE(thread_id, turn_ordinal)
|
|
1297
|
+
);
|
|
1298
|
+
|
|
1299
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_runs_client_request
|
|
1300
|
+
ON runs(workspace_session_id, source_type, client_request_id)
|
|
1301
|
+
WHERE client_request_id IS NOT NULL;
|
|
1302
|
+
CREATE INDEX IF NOT EXISTS idx_runs_workspace_status ON runs(workspace_session_id, status);
|
|
1303
|
+
CREATE INDEX IF NOT EXISTS idx_runs_thread_status ON runs(thread_id, status);
|
|
1304
|
+
CREATE INDEX IF NOT EXISTS idx_runs_parent ON runs(parent_run_id);
|
|
1305
|
+
|
|
1306
|
+
CREATE TABLE IF NOT EXISTS run_definitions (
|
|
1307
|
+
run_id TEXT PRIMARY KEY REFERENCES runs(id),
|
|
1308
|
+
po_name TEXT NOT NULL,
|
|
1309
|
+
version TEXT NOT NULL,
|
|
1310
|
+
prompt TEXT NOT NULL,
|
|
1311
|
+
config TEXT NOT NULL,
|
|
1312
|
+
provider TEXT,
|
|
1313
|
+
model TEXT,
|
|
1314
|
+
capability_policy_version TEXT NOT NULL,
|
|
1315
|
+
created_at TEXT NOT NULL
|
|
1316
|
+
);
|
|
1317
|
+
|
|
1318
|
+
CREATE TABLE IF NOT EXISTS workspace_events (
|
|
1319
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1320
|
+
workspace_session_id TEXT NOT NULL REFERENCES workspace_sessions(id),
|
|
1321
|
+
event_type TEXT NOT NULL,
|
|
1322
|
+
entity_type TEXT,
|
|
1323
|
+
entity_id TEXT,
|
|
1324
|
+
payload TEXT NOT NULL DEFAULT '{}',
|
|
1325
|
+
created_at TEXT NOT NULL
|
|
1326
|
+
);
|
|
1327
|
+
|
|
1328
|
+
CREATE INDEX IF NOT EXISTS idx_workspace_events_scope ON workspace_events(workspace_session_id, id);
|
|
1329
|
+
|
|
1330
|
+
CREATE TABLE IF NOT EXISTS run_events (
|
|
1331
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1332
|
+
run_id TEXT NOT NULL REFERENCES runs(id),
|
|
1333
|
+
sequence INTEGER NOT NULL,
|
|
1334
|
+
workspace_cursor INTEGER NOT NULL UNIQUE REFERENCES workspace_events(id),
|
|
1335
|
+
workspace_session_id TEXT NOT NULL REFERENCES workspace_sessions(id),
|
|
1336
|
+
thread_id TEXT NOT NULL REFERENCES sessions(id),
|
|
1337
|
+
kind TEXT NOT NULL,
|
|
1338
|
+
timestamp TEXT NOT NULL,
|
|
1339
|
+
data TEXT NOT NULL DEFAULT '{}',
|
|
1340
|
+
UNIQUE(run_id, sequence)
|
|
1341
|
+
);
|
|
1342
|
+
|
|
1343
|
+
CREATE INDEX IF NOT EXISTS idx_run_events_run ON run_events(run_id, sequence);
|
|
1344
|
+
CREATE INDEX IF NOT EXISTS idx_run_events_workspace ON run_events(workspace_session_id, workspace_cursor);
|
|
1345
|
+
|
|
1346
|
+
CREATE TABLE IF NOT EXISTS tool_executions (
|
|
1347
|
+
id TEXT PRIMARY KEY,
|
|
1348
|
+
provider_tool_call_id TEXT NOT NULL,
|
|
1349
|
+
run_id TEXT NOT NULL REFERENCES runs(id),
|
|
1350
|
+
batch_id TEXT NOT NULL,
|
|
1351
|
+
batch_position INTEGER NOT NULL,
|
|
1352
|
+
capability_name TEXT NOT NULL,
|
|
1353
|
+
capability_policy_version TEXT,
|
|
1354
|
+
status TEXT NOT NULL,
|
|
1355
|
+
resource_keys TEXT NOT NULL DEFAULT '[]',
|
|
1356
|
+
arguments TEXT NOT NULL DEFAULT '{}',
|
|
1357
|
+
result TEXT,
|
|
1358
|
+
error_class TEXT,
|
|
1359
|
+
error_message TEXT,
|
|
1360
|
+
cancellation_reason TEXT,
|
|
1361
|
+
started_at TEXT,
|
|
1362
|
+
finished_at TEXT,
|
|
1363
|
+
UNIQUE(run_id, provider_tool_call_id)
|
|
1364
|
+
);
|
|
1365
|
+
|
|
1366
|
+
CREATE INDEX IF NOT EXISTS idx_tool_executions_run ON tool_executions(run_id, batch_position);
|
|
1367
|
+
|
|
1368
|
+
CREATE TABLE IF NOT EXISTS artifacts (
|
|
1369
|
+
id TEXT PRIMARY KEY,
|
|
1370
|
+
workspace_session_id TEXT NOT NULL REFERENCES workspace_sessions(id),
|
|
1371
|
+
owner_po TEXT NOT NULL,
|
|
1372
|
+
owner_thread_id TEXT REFERENCES sessions(id),
|
|
1373
|
+
producing_run_id TEXT REFERENCES runs(id),
|
|
1374
|
+
title TEXT NOT NULL,
|
|
1375
|
+
html TEXT NOT NULL,
|
|
1376
|
+
revision INTEGER NOT NULL DEFAULT 1,
|
|
1377
|
+
mode TEXT NOT NULL DEFAULT 'replace',
|
|
1378
|
+
created_at TEXT NOT NULL,
|
|
1379
|
+
updated_at TEXT NOT NULL,
|
|
1380
|
+
metadata TEXT NOT NULL DEFAULT '{}'
|
|
1381
|
+
);
|
|
1382
|
+
|
|
1383
|
+
CREATE INDEX IF NOT EXISTS idx_artifacts_workspace ON artifacts(workspace_session_id, updated_at);
|
|
1384
|
+
CREATE INDEX IF NOT EXISTS idx_artifacts_owner_thread ON artifacts(owner_thread_id);
|
|
1033
1385
|
SQL
|
|
1034
1386
|
end
|
|
1035
1387
|
|
|
@@ -1107,6 +1459,399 @@ module PromptObjects
|
|
|
1107
1459
|
# Add usage column for token tracking
|
|
1108
1460
|
@db.execute("ALTER TABLE messages ADD COLUMN usage TEXT")
|
|
1109
1461
|
end
|
|
1462
|
+
|
|
1463
|
+
if from_version < 7
|
|
1464
|
+
# Add shared environment data table for delegation chains
|
|
1465
|
+
@db.execute_batch(<<~SQL)
|
|
1466
|
+
CREATE TABLE IF NOT EXISTS env_data (
|
|
1467
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1468
|
+
root_thread_id TEXT NOT NULL,
|
|
1469
|
+
key TEXT NOT NULL,
|
|
1470
|
+
short_description TEXT NOT NULL,
|
|
1471
|
+
value TEXT NOT NULL,
|
|
1472
|
+
stored_by TEXT NOT NULL,
|
|
1473
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
1474
|
+
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
1475
|
+
UNIQUE(root_thread_id, key)
|
|
1476
|
+
);
|
|
1477
|
+
|
|
1478
|
+
CREATE INDEX IF NOT EXISTS idx_env_data_root ON env_data(root_thread_id);
|
|
1479
|
+
SQL
|
|
1480
|
+
end
|
|
1481
|
+
|
|
1482
|
+
if from_version < 8
|
|
1483
|
+
# Environment-level sessions: add session_type, initiated_by to sessions;
|
|
1484
|
+
# target_po, from_source to messages; session_id to env_data
|
|
1485
|
+
@db.execute_batch(<<~SQL)
|
|
1486
|
+
ALTER TABLE sessions ADD COLUMN session_type TEXT DEFAULT 'environment';
|
|
1487
|
+
ALTER TABLE sessions ADD COLUMN initiated_by TEXT;
|
|
1488
|
+
|
|
1489
|
+
ALTER TABLE messages ADD COLUMN target_po TEXT;
|
|
1490
|
+
ALTER TABLE messages ADD COLUMN from_source TEXT;
|
|
1491
|
+
|
|
1492
|
+
ALTER TABLE env_data ADD COLUMN session_id TEXT;
|
|
1493
|
+
CREATE INDEX IF NOT EXISTS idx_env_data_session ON env_data(session_id);
|
|
1494
|
+
SQL
|
|
1495
|
+
|
|
1496
|
+
# Migrate existing data: set session_type based on thread_type,
|
|
1497
|
+
# populate target_po from session's po_name, copy root_thread_id to session_id
|
|
1498
|
+
@db.execute("UPDATE sessions SET session_type = 'delegation' WHERE thread_type = 'delegation'")
|
|
1499
|
+
@db.execute("UPDATE sessions SET initiated_by = 'human' WHERE session_type = 'environment'")
|
|
1500
|
+
@db.execute("UPDATE messages SET target_po = (SELECT po_name FROM sessions WHERE sessions.id = messages.session_id) WHERE target_po IS NULL")
|
|
1501
|
+
@db.execute("UPDATE messages SET from_source = 'human' WHERE role = 'user' AND from_source IS NULL")
|
|
1502
|
+
@db.execute("UPDATE env_data SET session_id = root_thread_id WHERE session_id IS NULL")
|
|
1503
|
+
end
|
|
1504
|
+
|
|
1505
|
+
if from_version < 9
|
|
1506
|
+
# v8 introduced environment sessions (po_name IS NULL), but databases
|
|
1507
|
+
# created before that have `po_name TEXT NOT NULL`, and ALTER TABLE
|
|
1508
|
+
# can't drop a NOT NULL constraint. Rebuild the sessions table with a
|
|
1509
|
+
# nullable po_name so create_environment_session works on upgrade.
|
|
1510
|
+
# (Fresh DBs go through create_schema, which is already nullable.)
|
|
1511
|
+
@db.execute_batch(<<~SQL)
|
|
1512
|
+
CREATE TABLE sessions_new (
|
|
1513
|
+
id TEXT PRIMARY KEY,
|
|
1514
|
+
po_name TEXT,
|
|
1515
|
+
name TEXT,
|
|
1516
|
+
source TEXT DEFAULT 'tui',
|
|
1517
|
+
source_client TEXT,
|
|
1518
|
+
last_message_source TEXT,
|
|
1519
|
+
created_at TEXT NOT NULL,
|
|
1520
|
+
updated_at TEXT NOT NULL,
|
|
1521
|
+
metadata TEXT DEFAULT '{}',
|
|
1522
|
+
parent_session_id TEXT,
|
|
1523
|
+
parent_message_id INTEGER,
|
|
1524
|
+
parent_po TEXT,
|
|
1525
|
+
thread_type TEXT DEFAULT 'root',
|
|
1526
|
+
session_type TEXT DEFAULT 'environment',
|
|
1527
|
+
initiated_by TEXT
|
|
1528
|
+
);
|
|
1529
|
+
|
|
1530
|
+
INSERT INTO sessions_new (
|
|
1531
|
+
id, po_name, name, source, source_client, last_message_source,
|
|
1532
|
+
created_at, updated_at, metadata, parent_session_id,
|
|
1533
|
+
parent_message_id, parent_po, thread_type, session_type, initiated_by
|
|
1534
|
+
)
|
|
1535
|
+
SELECT
|
|
1536
|
+
id, po_name, name, source, source_client, last_message_source,
|
|
1537
|
+
created_at, updated_at, metadata, parent_session_id,
|
|
1538
|
+
parent_message_id, parent_po, thread_type, session_type, initiated_by
|
|
1539
|
+
FROM sessions;
|
|
1540
|
+
|
|
1541
|
+
DROP TABLE sessions;
|
|
1542
|
+
ALTER TABLE sessions_new RENAME TO sessions;
|
|
1543
|
+
|
|
1544
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_po_name ON sessions(po_name);
|
|
1545
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_updated_at ON sessions(updated_at);
|
|
1546
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_source ON sessions(source);
|
|
1547
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_parent ON sessions(parent_session_id);
|
|
1548
|
+
SQL
|
|
1549
|
+
end
|
|
1550
|
+
|
|
1551
|
+
migrate_schema_v10 if from_version < 10
|
|
1552
|
+
migrate_schema_v11 if from_version < 11
|
|
1553
|
+
end
|
|
1554
|
+
|
|
1555
|
+
def migrate_schema_v11
|
|
1556
|
+
@db.transaction do
|
|
1557
|
+
@db.execute_batch(<<~SQL)
|
|
1558
|
+
CREATE TABLE IF NOT EXISTS run_definitions (
|
|
1559
|
+
run_id TEXT PRIMARY KEY REFERENCES runs(id),
|
|
1560
|
+
po_name TEXT NOT NULL,
|
|
1561
|
+
version TEXT NOT NULL,
|
|
1562
|
+
prompt TEXT NOT NULL,
|
|
1563
|
+
config TEXT NOT NULL,
|
|
1564
|
+
provider TEXT,
|
|
1565
|
+
model TEXT,
|
|
1566
|
+
capability_policy_version TEXT NOT NULL,
|
|
1567
|
+
created_at TEXT NOT NULL
|
|
1568
|
+
);
|
|
1569
|
+
SQL
|
|
1570
|
+
end
|
|
1571
|
+
end
|
|
1572
|
+
|
|
1573
|
+
def migrate_schema_v10
|
|
1574
|
+
@db.transaction do
|
|
1575
|
+
@db.execute_batch(<<~SQL)
|
|
1576
|
+
CREATE TABLE IF NOT EXISTS workspace_sessions (
|
|
1577
|
+
id TEXT PRIMARY KEY,
|
|
1578
|
+
name TEXT,
|
|
1579
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
1580
|
+
created_at TEXT NOT NULL,
|
|
1581
|
+
updated_at TEXT NOT NULL,
|
|
1582
|
+
metadata TEXT NOT NULL DEFAULT '{}'
|
|
1583
|
+
);
|
|
1584
|
+
SQL
|
|
1585
|
+
|
|
1586
|
+
add_column_unless_exists("sessions", "workspace_session_id", "TEXT REFERENCES workspace_sessions(id)")
|
|
1587
|
+
add_column_unless_exists("sessions", "parent_run_id", "TEXT")
|
|
1588
|
+
add_column_unless_exists("sessions", "retention_policy", "TEXT")
|
|
1589
|
+
add_column_unless_exists("sessions", "archived", "INTEGER NOT NULL DEFAULT 0")
|
|
1590
|
+
add_column_unless_exists("sessions", "last_run_status", "TEXT")
|
|
1591
|
+
add_column_unless_exists("sessions", "last_activity_at", "TEXT")
|
|
1592
|
+
|
|
1593
|
+
if table_exists?("messages")
|
|
1594
|
+
add_column_unless_exists("messages", "message_uid", "TEXT")
|
|
1595
|
+
add_column_unless_exists("messages", "run_id", "TEXT")
|
|
1596
|
+
add_column_unless_exists("messages", "turn_ordinal", "INTEGER")
|
|
1597
|
+
add_column_unless_exists("messages", "position", "INTEGER")
|
|
1598
|
+
end
|
|
1599
|
+
|
|
1600
|
+
if table_exists?("env_data")
|
|
1601
|
+
add_column_unless_exists("env_data", "workspace_session_id", "TEXT REFERENCES workspace_sessions(id)")
|
|
1602
|
+
end
|
|
1603
|
+
|
|
1604
|
+
create_v10_runtime_tables
|
|
1605
|
+
backfill_workspace_sessions
|
|
1606
|
+
backfill_artifacts_from_env_data
|
|
1607
|
+
create_v10_indexes
|
|
1608
|
+
end
|
|
1609
|
+
end
|
|
1610
|
+
|
|
1611
|
+
def create_v10_runtime_tables
|
|
1612
|
+
@db.execute_batch(<<~SQL)
|
|
1613
|
+
CREATE TABLE IF NOT EXISTS runs (
|
|
1614
|
+
id TEXT PRIMARY KEY,
|
|
1615
|
+
client_request_id TEXT,
|
|
1616
|
+
workspace_session_id TEXT NOT NULL REFERENCES workspace_sessions(id),
|
|
1617
|
+
thread_id TEXT NOT NULL REFERENCES sessions(id),
|
|
1618
|
+
po_name TEXT NOT NULL,
|
|
1619
|
+
turn_ordinal INTEGER NOT NULL,
|
|
1620
|
+
input_content TEXT,
|
|
1621
|
+
input_message_id INTEGER,
|
|
1622
|
+
history_cutoff_message_id INTEGER,
|
|
1623
|
+
definition_version TEXT NOT NULL,
|
|
1624
|
+
provider TEXT,
|
|
1625
|
+
model TEXT,
|
|
1626
|
+
capability_policy_version TEXT,
|
|
1627
|
+
source_type TEXT NOT NULL,
|
|
1628
|
+
source_name TEXT,
|
|
1629
|
+
parent_run_id TEXT REFERENCES runs(id),
|
|
1630
|
+
root_run_id TEXT,
|
|
1631
|
+
status TEXT NOT NULL,
|
|
1632
|
+
priority INTEGER NOT NULL DEFAULT 0,
|
|
1633
|
+
enqueued_at TEXT NOT NULL,
|
|
1634
|
+
started_at TEXT,
|
|
1635
|
+
finished_at TEXT,
|
|
1636
|
+
cancellation_reason TEXT,
|
|
1637
|
+
final_response TEXT,
|
|
1638
|
+
usage TEXT,
|
|
1639
|
+
error_class TEXT,
|
|
1640
|
+
error_message TEXT,
|
|
1641
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
1642
|
+
UNIQUE(thread_id, turn_ordinal)
|
|
1643
|
+
);
|
|
1644
|
+
|
|
1645
|
+
CREATE TABLE IF NOT EXISTS workspace_events (
|
|
1646
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1647
|
+
workspace_session_id TEXT NOT NULL REFERENCES workspace_sessions(id),
|
|
1648
|
+
event_type TEXT NOT NULL,
|
|
1649
|
+
entity_type TEXT,
|
|
1650
|
+
entity_id TEXT,
|
|
1651
|
+
payload TEXT NOT NULL DEFAULT '{}',
|
|
1652
|
+
created_at TEXT NOT NULL
|
|
1653
|
+
);
|
|
1654
|
+
|
|
1655
|
+
CREATE TABLE IF NOT EXISTS run_events (
|
|
1656
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1657
|
+
run_id TEXT NOT NULL REFERENCES runs(id),
|
|
1658
|
+
sequence INTEGER NOT NULL,
|
|
1659
|
+
workspace_cursor INTEGER NOT NULL UNIQUE REFERENCES workspace_events(id),
|
|
1660
|
+
workspace_session_id TEXT NOT NULL REFERENCES workspace_sessions(id),
|
|
1661
|
+
thread_id TEXT NOT NULL REFERENCES sessions(id),
|
|
1662
|
+
kind TEXT NOT NULL,
|
|
1663
|
+
timestamp TEXT NOT NULL,
|
|
1664
|
+
data TEXT NOT NULL DEFAULT '{}',
|
|
1665
|
+
UNIQUE(run_id, sequence)
|
|
1666
|
+
);
|
|
1667
|
+
|
|
1668
|
+
CREATE TABLE IF NOT EXISTS tool_executions (
|
|
1669
|
+
id TEXT PRIMARY KEY,
|
|
1670
|
+
provider_tool_call_id TEXT NOT NULL,
|
|
1671
|
+
run_id TEXT NOT NULL REFERENCES runs(id),
|
|
1672
|
+
batch_id TEXT NOT NULL,
|
|
1673
|
+
batch_position INTEGER NOT NULL,
|
|
1674
|
+
capability_name TEXT NOT NULL,
|
|
1675
|
+
capability_policy_version TEXT,
|
|
1676
|
+
status TEXT NOT NULL,
|
|
1677
|
+
resource_keys TEXT NOT NULL DEFAULT '[]',
|
|
1678
|
+
arguments TEXT NOT NULL DEFAULT '{}',
|
|
1679
|
+
result TEXT,
|
|
1680
|
+
error_class TEXT,
|
|
1681
|
+
error_message TEXT,
|
|
1682
|
+
cancellation_reason TEXT,
|
|
1683
|
+
started_at TEXT,
|
|
1684
|
+
finished_at TEXT,
|
|
1685
|
+
UNIQUE(run_id, provider_tool_call_id)
|
|
1686
|
+
);
|
|
1687
|
+
|
|
1688
|
+
CREATE TABLE IF NOT EXISTS artifacts (
|
|
1689
|
+
id TEXT PRIMARY KEY,
|
|
1690
|
+
workspace_session_id TEXT NOT NULL REFERENCES workspace_sessions(id),
|
|
1691
|
+
owner_po TEXT NOT NULL,
|
|
1692
|
+
owner_thread_id TEXT REFERENCES sessions(id),
|
|
1693
|
+
producing_run_id TEXT REFERENCES runs(id),
|
|
1694
|
+
title TEXT NOT NULL,
|
|
1695
|
+
html TEXT NOT NULL,
|
|
1696
|
+
revision INTEGER NOT NULL DEFAULT 1,
|
|
1697
|
+
mode TEXT NOT NULL DEFAULT 'replace',
|
|
1698
|
+
created_at TEXT NOT NULL,
|
|
1699
|
+
updated_at TEXT NOT NULL,
|
|
1700
|
+
metadata TEXT NOT NULL DEFAULT '{}'
|
|
1701
|
+
);
|
|
1702
|
+
SQL
|
|
1703
|
+
end
|
|
1704
|
+
|
|
1705
|
+
def create_v10_indexes
|
|
1706
|
+
@db.execute("CREATE INDEX IF NOT EXISTS idx_sessions_workspace ON sessions(workspace_session_id)")
|
|
1707
|
+
if table_exists?("messages")
|
|
1708
|
+
@db.execute("CREATE UNIQUE INDEX IF NOT EXISTS idx_messages_uid ON messages(message_uid) WHERE message_uid IS NOT NULL")
|
|
1709
|
+
@db.execute("CREATE INDEX IF NOT EXISTS idx_messages_run ON messages(run_id)")
|
|
1710
|
+
@db.execute("CREATE INDEX IF NOT EXISTS idx_messages_turn ON messages(session_id, turn_ordinal, position)")
|
|
1711
|
+
end
|
|
1712
|
+
@db.execute("CREATE INDEX IF NOT EXISTS idx_env_data_workspace ON env_data(workspace_session_id)") if table_exists?("env_data")
|
|
1713
|
+
@db.execute(<<~SQL)
|
|
1714
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_runs_client_request
|
|
1715
|
+
ON runs(workspace_session_id, source_type, client_request_id)
|
|
1716
|
+
WHERE client_request_id IS NOT NULL
|
|
1717
|
+
SQL
|
|
1718
|
+
@db.execute("CREATE INDEX IF NOT EXISTS idx_runs_workspace_status ON runs(workspace_session_id, status)")
|
|
1719
|
+
@db.execute("CREATE INDEX IF NOT EXISTS idx_runs_thread_status ON runs(thread_id, status)")
|
|
1720
|
+
@db.execute("CREATE INDEX IF NOT EXISTS idx_runs_parent ON runs(parent_run_id)")
|
|
1721
|
+
@db.execute("CREATE INDEX IF NOT EXISTS idx_workspace_events_scope ON workspace_events(workspace_session_id, id)")
|
|
1722
|
+
@db.execute("CREATE INDEX IF NOT EXISTS idx_run_events_run ON run_events(run_id, sequence)")
|
|
1723
|
+
@db.execute("CREATE INDEX IF NOT EXISTS idx_run_events_workspace ON run_events(workspace_session_id, workspace_cursor)")
|
|
1724
|
+
@db.execute("CREATE INDEX IF NOT EXISTS idx_tool_executions_run ON tool_executions(run_id, batch_position)")
|
|
1725
|
+
@db.execute("CREATE INDEX IF NOT EXISTS idx_artifacts_workspace ON artifacts(workspace_session_id, updated_at)")
|
|
1726
|
+
@db.execute("CREATE INDEX IF NOT EXISTS idx_artifacts_owner_thread ON artifacts(owner_thread_id)")
|
|
1727
|
+
end
|
|
1728
|
+
|
|
1729
|
+
def backup_before_destructive_migration
|
|
1730
|
+
version = get_schema_version
|
|
1731
|
+
return if @db_path == ":memory:" || version.zero? || version >= SCHEMA_VERSION
|
|
1732
|
+
return unless File.file?(@db_path)
|
|
1733
|
+
|
|
1734
|
+
timestamp = Time.now.utc.strftime("%Y%m%dT%H%M%S%6NZ")
|
|
1735
|
+
@migration_backup_path = "#{@db_path}.backup-v#{version}-#{timestamp}"
|
|
1736
|
+
destination = SQLite3::Database.new(@migration_backup_path)
|
|
1737
|
+
backup = SQLite3::Backup.new(destination, "main", @db, "main")
|
|
1738
|
+
backup.step(-1)
|
|
1739
|
+
backup.finish
|
|
1740
|
+
ensure
|
|
1741
|
+
destination&.close
|
|
1742
|
+
end
|
|
1743
|
+
|
|
1744
|
+
def table_exists?(table)
|
|
1745
|
+
!@db.get_first_value(
|
|
1746
|
+
"SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?",
|
|
1747
|
+
[table]
|
|
1748
|
+
).nil?
|
|
1749
|
+
end
|
|
1750
|
+
|
|
1751
|
+
def column_exists?(table, column)
|
|
1752
|
+
return false unless table_exists?(table)
|
|
1753
|
+
|
|
1754
|
+
@db.table_info(table).any? { |definition| definition["name"] == column }
|
|
1755
|
+
end
|
|
1756
|
+
|
|
1757
|
+
def add_column_unless_exists(table, column, definition)
|
|
1758
|
+
return if column_exists?(table, column)
|
|
1759
|
+
|
|
1760
|
+
@db.execute("ALTER TABLE #{table} ADD COLUMN #{column} #{definition}")
|
|
1761
|
+
end
|
|
1762
|
+
|
|
1763
|
+
def backfill_workspace_sessions
|
|
1764
|
+
environment_rows = @db.execute(<<~SQL)
|
|
1765
|
+
SELECT id, name, created_at, updated_at, metadata
|
|
1766
|
+
FROM sessions
|
|
1767
|
+
WHERE po_name IS NULL
|
|
1768
|
+
ORDER BY created_at ASC
|
|
1769
|
+
SQL
|
|
1770
|
+
|
|
1771
|
+
environment_rows.each do |row|
|
|
1772
|
+
@db.execute(<<~SQL, [row["id"], row["name"], row["created_at"], row["updated_at"], row["metadata"] || "{}"])
|
|
1773
|
+
INSERT OR IGNORE INTO workspace_sessions (id, name, status, created_at, updated_at, metadata)
|
|
1774
|
+
VALUES (?, ?, 'active', ?, ?, ?)
|
|
1775
|
+
SQL
|
|
1776
|
+
@db.execute("UPDATE sessions SET workspace_session_id = ? WHERE id = ?", [row["id"], row["id"]])
|
|
1777
|
+
end
|
|
1778
|
+
|
|
1779
|
+
workspace_ids = @db.execute("SELECT id FROM workspace_sessions ORDER BY created_at ASC").map { |row| row["id"] }
|
|
1780
|
+
fallback_id = if workspace_ids.one?
|
|
1781
|
+
workspace_ids.first
|
|
1782
|
+
else
|
|
1783
|
+
create_imported_workspace
|
|
1784
|
+
end
|
|
1785
|
+
|
|
1786
|
+
@db.execute(
|
|
1787
|
+
"UPDATE sessions SET workspace_session_id = ? WHERE workspace_session_id IS NULL",
|
|
1788
|
+
[fallback_id]
|
|
1789
|
+
)
|
|
1790
|
+
|
|
1791
|
+
return unless table_exists?("env_data")
|
|
1792
|
+
|
|
1793
|
+
@db.execute(<<~SQL)
|
|
1794
|
+
UPDATE env_data
|
|
1795
|
+
SET workspace_session_id = session_id
|
|
1796
|
+
WHERE workspace_session_id IS NULL
|
|
1797
|
+
AND session_id IN (SELECT id FROM workspace_sessions)
|
|
1798
|
+
SQL
|
|
1799
|
+
@db.execute(<<~SQL)
|
|
1800
|
+
UPDATE env_data
|
|
1801
|
+
SET workspace_session_id = (
|
|
1802
|
+
SELECT workspace_session_id FROM sessions WHERE sessions.id = env_data.root_thread_id
|
|
1803
|
+
)
|
|
1804
|
+
WHERE workspace_session_id IS NULL
|
|
1805
|
+
SQL
|
|
1806
|
+
@db.execute(
|
|
1807
|
+
"UPDATE env_data SET workspace_session_id = ? WHERE workspace_session_id IS NULL",
|
|
1808
|
+
[fallback_id]
|
|
1809
|
+
)
|
|
1810
|
+
end
|
|
1811
|
+
|
|
1812
|
+
def create_imported_workspace
|
|
1813
|
+
existing = @db.get_first_value("SELECT id FROM workspace_sessions WHERE name = 'Imported legacy threads' LIMIT 1")
|
|
1814
|
+
return existing if existing
|
|
1815
|
+
|
|
1816
|
+
id = SecureRandom.uuid
|
|
1817
|
+
now = Time.now.utc.iso8601
|
|
1818
|
+
@db.execute(<<~SQL, [id, now, now])
|
|
1819
|
+
INSERT INTO workspace_sessions (id, name, status, created_at, updated_at, metadata)
|
|
1820
|
+
VALUES (?, 'Imported legacy threads', 'active', ?, ?, '{}')
|
|
1821
|
+
SQL
|
|
1822
|
+
id
|
|
1823
|
+
end
|
|
1824
|
+
|
|
1825
|
+
def backfill_artifacts_from_env_data
|
|
1826
|
+
return unless table_exists?("env_data")
|
|
1827
|
+
|
|
1828
|
+
rows = @db.execute(<<~SQL)
|
|
1829
|
+
SELECT key, workspace_session_id, value, stored_by, created_at, updated_at
|
|
1830
|
+
FROM env_data
|
|
1831
|
+
WHERE key LIKE 'artifact_%'
|
|
1832
|
+
SQL
|
|
1833
|
+
rows.each do |row|
|
|
1834
|
+
value = JSON.parse(row["value"])
|
|
1835
|
+
title = value["title"]
|
|
1836
|
+
html = value["html"]
|
|
1837
|
+
next unless row["workspace_session_id"] && title && html
|
|
1838
|
+
|
|
1839
|
+
owner_po = value["rendered_by"] || row["stored_by"] || "unknown"
|
|
1840
|
+
owner_thread_id = @db.get_first_value(<<~SQL, [row["workspace_session_id"], owner_po])
|
|
1841
|
+
SELECT id FROM sessions
|
|
1842
|
+
WHERE workspace_session_id = ? AND po_name = ?
|
|
1843
|
+
ORDER BY updated_at DESC
|
|
1844
|
+
LIMIT 1
|
|
1845
|
+
SQL
|
|
1846
|
+
@db.execute(<<~SQL, [row["key"], row["workspace_session_id"], owner_po, owner_thread_id, title, html, row["created_at"], row["updated_at"]])
|
|
1847
|
+
INSERT OR IGNORE INTO artifacts (
|
|
1848
|
+
id, workspace_session_id, owner_po, owner_thread_id, producing_run_id,
|
|
1849
|
+
title, html, revision, mode, created_at, updated_at, metadata
|
|
1850
|
+
) VALUES (?, ?, ?, ?, NULL, ?, ?, 1, 'replace', ?, ?, '{}')
|
|
1851
|
+
SQL
|
|
1852
|
+
rescue JSON::ParserError
|
|
1853
|
+
next
|
|
1854
|
+
end
|
|
1110
1855
|
end
|
|
1111
1856
|
|
|
1112
1857
|
def empty_usage
|
|
@@ -1184,7 +1929,17 @@ module PromptObjects
|
|
|
1184
1929
|
parent_session_id: row["parent_session_id"],
|
|
1185
1930
|
parent_message_id: row["parent_message_id"],
|
|
1186
1931
|
parent_po: row["parent_po"],
|
|
1187
|
-
thread_type: row["thread_type"] || "root"
|
|
1932
|
+
thread_type: row["thread_type"] || "root",
|
|
1933
|
+
# Environment session fields (v8)
|
|
1934
|
+
session_type: row["session_type"] || "environment",
|
|
1935
|
+
initiated_by: row["initiated_by"],
|
|
1936
|
+
# Run-based runtime fields (v10)
|
|
1937
|
+
workspace_session_id: row["workspace_session_id"],
|
|
1938
|
+
parent_run_id: row["parent_run_id"],
|
|
1939
|
+
retention_policy: row["retention_policy"],
|
|
1940
|
+
archived: row["archived"].to_i == 1,
|
|
1941
|
+
last_run_status: row["last_run_status"],
|
|
1942
|
+
last_activity_at: row["last_activity_at"] ? Time.parse(row["last_activity_at"]) : nil
|
|
1188
1943
|
}
|
|
1189
1944
|
result[:message_count] = row["message_count"] if include_count && row["message_count"]
|
|
1190
1945
|
result
|
|
@@ -1213,6 +1968,10 @@ module PromptObjects
|
|
|
1213
1968
|
tool_calls: row["tool_calls"] ? JSON.parse(row["tool_calls"], symbolize_names: true) : nil,
|
|
1214
1969
|
tool_results: row["tool_results"] ? JSON.parse(row["tool_results"], symbolize_names: true) : nil,
|
|
1215
1970
|
usage: row["usage"] ? JSON.parse(row["usage"], symbolize_names: true) : nil,
|
|
1971
|
+
message_uid: row["message_uid"],
|
|
1972
|
+
run_id: row["run_id"],
|
|
1973
|
+
turn_ordinal: row["turn_ordinal"],
|
|
1974
|
+
position: row["position"],
|
|
1216
1975
|
created_at: row["created_at"] ? Time.parse(row["created_at"]) : nil
|
|
1217
1976
|
}
|
|
1218
1977
|
end
|