Class: Ask::AppServer::SessionManager
- Inherits:
-
Object
- Object
- Ask::AppServer::SessionManager
- Defined in:
- lib/ask/app_server/session_manager.rb
Overview
Manages the lifecycle of agent sessions exposed through the app-server.
Orchestrates creation, resumption, subscription, messaging, aborting, and event polling across AgentAdapter instances stored in SessionStore.
Constant Summary collapse
- DEFAULT_TOOLS =
Default tools if none specified.
%w[bash read write edit glob grep].freeze
- DEFAULT_MODEL =
Default model if none specified.
ENV.fetch("ASK_APP_SERVER_MODEL", "opencode_go/deepseek-v4-flash")
Instance Attribute Summary collapse
-
#blocked_tools ⇒ Object
readonly
Returns the value of attribute blocked_tools.
-
#permission_mode ⇒ Object
Returns the value of attribute permission_mode.
-
#permission_timeout ⇒ Object
readonly
Returns the value of attribute permission_timeout.
-
#store ⇒ Object
readonly
Returns the value of attribute store.
Instance Method Summary collapse
-
#create_session(workspace_path: nil, mode: nil, model: nil, tools: nil, system_prompt: nil) ⇒ Object
Create a new session.
-
#destroy_session(session_id) ⇒ Object
Remove a session.
-
#get(session_id) ⇒ Object
Get an adapter by session ID.
-
#get_events(session_id, after_seq:, limit: nil) ⇒ Object
Get events for a session after a sequence number.
-
#initialize(store: nil, permission_mode: :on_request, blocked_tools: nil, permission_timeout: 300) ⇒ SessionManager
constructor
A new instance of SessionManager.
-
#list_sessions(limit: 20) ⇒ Object
List active sessions.
-
#on_new_permission_handler(&block) ⇒ Object
Register a callback that fires for every new PermissionHandler created.
-
#pending_notifications(session_id) ⇒ Object
Notify subscribers of new events.
-
#permission_handler_for(session_id) ⇒ Object
Get the permission handler for a session, if any.
-
#read_workspace_state(session_id = nil) ⇒ Object
Read workspace state.
-
#send_message(session_id, content) ⇒ Object
Send a message to a session.
-
#subscribe(session_id, delivery_kind: "web-remote-replayable") ⇒ Object
Subscribe to a session's events.
-
#subscribed?(session_id) ⇒ Boolean
Check if a session is subscribed.
Constructor Details
#initialize(store: nil, permission_mode: :on_request, blocked_tools: nil, permission_timeout: 300) ⇒ SessionManager
Returns a new instance of SessionManager.
21 22 23 24 25 26 27 28 29 |
# File 'lib/ask/app_server/session_manager.rb', line 21 def initialize(store: nil, permission_mode: :on_request, blocked_tools: nil, permission_timeout: 300) @store = store || SessionStore.new @permission_mode = @blocked_tools = blocked_tools @permission_timeout = @permission_handlers = {} # session_id => PermissionHandler @on_new_handler = nil # callback for new permission handlers @logger = Logger.new($stdout, level: ENV["DEBUG"] ? Logger::DEBUG || Logger::DEBUG : Logger::WARN) end |
Instance Attribute Details
#blocked_tools ⇒ Object (readonly)
Returns the value of attribute blocked_tools.
18 19 20 |
# File 'lib/ask/app_server/session_manager.rb', line 18 def blocked_tools @blocked_tools end |
#permission_mode ⇒ Object
Returns the value of attribute permission_mode.
17 18 19 |
# File 'lib/ask/app_server/session_manager.rb', line 17 def @permission_mode end |
#permission_timeout ⇒ Object (readonly)
Returns the value of attribute permission_timeout.
19 20 21 |
# File 'lib/ask/app_server/session_manager.rb', line 19 def @permission_timeout end |
#store ⇒ Object (readonly)
Returns the value of attribute store.
16 17 18 |
# File 'lib/ask/app_server/session_manager.rb', line 16 def store @store end |
Instance Method Details
#create_session(workspace_path: nil, mode: nil, model: nil, tools: nil, system_prompt: nil) ⇒ Object
Create a new session. Returns the session ID.
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/ask/app_server/session_manager.rb', line 44 def create_session(workspace_path: nil, mode: nil, model: nil, tools: nil, system_prompt: nil) = (mode) # Extract provider prefix from model string (e.g., "opencode_go/deepseek-v4-flash") model_id, model_provider = parse_model_string(model || DEFAULT_MODEL) model_for_agent = model_provider ? model_id : (model || DEFAULT_MODEL) # Create the permission handler if needed = if == :on_request handler_opts = { mode: :on_request } handler_opts[:blocked_tools] = @blocked_tools if @blocked_tools handler_opts[:timeout] = @permission_timeout if @permission_timeout handler = PermissionHandler.new(**handler_opts) @permission_handlers[session_id_cache] = handler if session_id_cache handler end hooks = {} hooks[:before_tool] = [] if adapter = AgentAdapter.new( model: model_for_agent, tools: tools || DEFAULT_TOOLS, system_prompt: system_prompt || build_default_system_prompt(workspace_path), agent_dir: workspace_path, hooks: hooks ) session_id = adapter.start_session @store.add(session_id, adapter) # Register the permission handler by session ID if @permission_handlers[session_id] = @on_new_handler&.call() end @logger.info("Created session #{session_id} (model=#{model || DEFAULT_MODEL}, permission_mode=#{})") session_id end |
#destroy_session(session_id) ⇒ Object
Remove a session.
93 94 95 96 97 98 99 100 101 102 103 104 105 |
# File 'lib/ask/app_server/session_manager.rb', line 93 def destroy_session(session_id) adapter = @store.get(session_id) return false unless adapter # Cancel any pending permission requests handler = @permission_handlers.delete(session_id) handler&.cancel_all! adapter.abort_turn! if adapter.running @store.remove(session_id) @logger.info("Destroyed session #{session_id}") true end |
#get(session_id) ⇒ Object
Get an adapter by session ID.
108 109 110 |
# File 'lib/ask/app_server/session_manager.rb', line 108 def get(session_id) @store.get(session_id) end |
#get_events(session_id, after_seq:, limit: nil) ⇒ Object
Get events for a session after a sequence number.
142 143 144 145 146 147 148 149 150 151 152 153 154 |
# File 'lib/ask/app_server/session_manager.rb', line 142 def get_events(session_id, after_seq:, limit: nil) adapter = @store.get(session_id) raise Ask::AppServer::SessionNotFound, "Session #{session_id} not found" unless adapter events = adapter.events_after(after_seq.to_i) events = events.first(limit) if limit && limit > 0 { sessionId: session_id, events: events, hasMore: limit && limit > 0 && events.length >= limit } end |
#list_sessions(limit: 20) ⇒ Object
List active sessions.
113 114 115 |
# File 'lib/ask/app_server/session_manager.rb', line 113 def list_sessions(limit: 20) @store.list(limit: limit) end |
#on_new_permission_handler(&block) ⇒ Object
Register a callback that fires for every new PermissionHandler created. The server uses this to wire its protocol sender callback.
33 34 35 |
# File 'lib/ask/app_server/session_manager.rb', line 33 def (&block) @on_new_handler = block end |
#pending_notifications(session_id) ⇒ Object
Notify subscribers of new events.
187 188 189 190 191 192 |
# File 'lib/ask/app_server/session_manager.rb', line 187 def pending_notifications(session_id) adapter = @store.get(session_id) return [] unless adapter && subscribed?(session_id) adapter.drain_events end |
#permission_handler_for(session_id) ⇒ Object
Get the permission handler for a session, if any.
88 89 90 |
# File 'lib/ask/app_server/session_manager.rb', line 88 def (session_id) @permission_handlers[session_id] end |
#read_workspace_state(session_id = nil) ⇒ Object
Read workspace state.
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 |
# File 'lib/ask/app_server/session_manager.rb', line 157 def read_workspace_state(session_id = nil) settings = { model: { current: { modelId: ENV.fetch("ASK_APP_SERVER_MODEL", DEFAULT_MODEL), providerId: resolve_provider_id } }, permissions: { mode: @permission_mode.to_s } } if session_id adapter = @store.get(session_id) if adapter settings[:sessionId] = session_id settings[:running] = adapter.running end end { settings: settings } end |
#send_message(session_id, content) ⇒ Object
Send a message to a session.
118 119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/ask/app_server/session_manager.rb', line 118 def (session_id, content) adapter = @store.get(session_id) raise Ask::AppServer::SessionNotFound, "Session #{session_id} not found" unless adapter if adapter.running @logger.info("Session #{session_id} busy, injecting message") adapter.(content) else adapter.(content) end true end |
#subscribe(session_id, delivery_kind: "web-remote-replayable") ⇒ Object
Subscribe to a session's events.
133 134 135 136 137 138 139 |
# File 'lib/ask/app_server/session_manager.rb', line 133 def subscribe(session_id, delivery_kind: "web-remote-replayable") adapter = @store.get(session_id) raise Ask::AppServer::SessionNotFound, "Session #{session_id} not found" unless adapter @store.subscribe(session_id, delivery_kind: delivery_kind) { subscribed: true, sessionId: session_id, deliveryKind: delivery_kind } end |
#subscribed?(session_id) ⇒ Boolean
Check if a session is subscribed.
182 183 184 |
# File 'lib/ask/app_server/session_manager.rb', line 182 def subscribed?(session_id) @store.subscribed?(session_id) end |