ask-app-server 0.1.2 → 0.4.2
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 +99 -0
- data/README.md +60 -23
- data/lib/ask/app_server/agent_adapter.rb +239 -57
- data/lib/ask/app_server/cli.rb +31 -16
- data/lib/ask/app_server/connection.rb +73 -0
- data/lib/ask/app_server/event_translator.rb +173 -117
- data/lib/ask/app_server/herdr_reporter.rb +201 -0
- data/lib/ask/app_server/server.rb +333 -185
- data/lib/ask/app_server/session_manager.rb +166 -80
- data/lib/ask/app_server/socket_server.rb +116 -0
- data/lib/ask/app_server/version.rb +1 -1
- data/lib/ask/app_server.rb +2 -0
- data/lib/ask-app-server.rb +4 -0
- metadata +22 -5
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
module Ask
|
|
4
4
|
module AppServer
|
|
5
|
-
# Manages the lifecycle of agent sessions exposed through the
|
|
5
|
+
# Manages the lifecycle of agent sessions exposed through the session
|
|
6
|
+
# host (ask-app-server).
|
|
6
7
|
#
|
|
7
8
|
# Orchestrates creation, resumption, subscription, messaging, aborting,
|
|
8
|
-
#
|
|
9
|
+
# interaction resolution (approvals, plan), closing, and event polling
|
|
10
|
+
# across AgentAdapter instances stored in SessionStore.
|
|
9
11
|
class SessionManager
|
|
10
12
|
# Default tools if none specified.
|
|
11
13
|
DEFAULT_TOOLS = %w[bash read write edit glob grep].freeze
|
|
@@ -13,6 +15,9 @@ module Ask
|
|
|
13
15
|
# Default model if none specified.
|
|
14
16
|
DEFAULT_MODEL = ENV.fetch("ASK_APP_SERVER_MODEL", "opencode_go/deepseek-v4-flash")
|
|
15
17
|
|
|
18
|
+
# Tools gated behind human approval when approval is :require.
|
|
19
|
+
DEFAULT_REQUIRE_APPROVAL = %w[write edit bash destroy].freeze
|
|
20
|
+
|
|
16
21
|
attr_reader :store
|
|
17
22
|
attr_reader :permission_mode
|
|
18
23
|
attr_reader :blocked_tools
|
|
@@ -21,89 +26,86 @@ module Ask
|
|
|
21
26
|
def initialize(store: nil, permission_mode: :on_request, blocked_tools: nil, permission_timeout: 300)
|
|
22
27
|
@store = store || SessionStore.new
|
|
23
28
|
@permission_mode = permission_mode
|
|
24
|
-
@blocked_tools = blocked_tools
|
|
29
|
+
@blocked_tools = (blocked_tools || DEFAULT_REQUIRE_APPROVAL).map(&:to_s)
|
|
25
30
|
@permission_timeout = permission_timeout
|
|
26
|
-
@
|
|
27
|
-
@on_new_handler = nil # callback for new permission handlers
|
|
31
|
+
@session_event_observers = []
|
|
28
32
|
@logger = Logger.new($stdout, level: ENV["DEBUG"] ? Logger::DEBUG || Logger::DEBUG : Logger::WARN)
|
|
29
33
|
end
|
|
30
34
|
|
|
31
|
-
# Register
|
|
32
|
-
#
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
# Register an observer for canonical events across all sessions.
|
|
36
|
+
# Called with (session_id, Ask::SessionProtocol::Events::Event) for
|
|
37
|
+
# every emitted event, as it happens.
|
|
38
|
+
def on_session_event(&block)
|
|
39
|
+
@session_event_observers << block
|
|
35
40
|
end
|
|
36
41
|
|
|
37
|
-
# Set the permission mode for new sessions.
|
|
42
|
+
# Set the permission mode for new sessions (:on_request, :never, :auto).
|
|
38
43
|
def permission_mode=(mode)
|
|
39
44
|
@permission_mode = mode.to_sym
|
|
40
45
|
end
|
|
41
46
|
|
|
47
|
+
# The approval mode new sessions get, translated to the canonical
|
|
48
|
+
# vocabulary (:off, :require, :auto).
|
|
49
|
+
def approval_mode
|
|
50
|
+
translate_permission_mode(@permission_mode)
|
|
51
|
+
end
|
|
52
|
+
|
|
42
53
|
# Create a new session.
|
|
43
54
|
# Returns the session ID.
|
|
55
|
+
#
|
|
56
|
+
# @param mode [String, Symbol, nil] "on_request"/"build"/"edit"
|
|
57
|
+
# (approvals required for blocked tools), "auto", "never"/"off"/"yolo"
|
|
58
|
+
# (no approvals), "plan" (plan mode, read-only until approved); nil
|
|
59
|
+
# falls back to the manager's permission_mode
|
|
44
60
|
def create_session(workspace_path: nil, mode: nil, model: nil, tools: nil, system_prompt: nil)
|
|
45
|
-
|
|
61
|
+
approval_opts, plan_mode = resolve_approval(mode)
|
|
46
62
|
|
|
47
63
|
# Extract provider prefix from model string (e.g., "opencode_go/deepseek-v4-flash")
|
|
48
64
|
model_id, model_provider = parse_model_string(model || DEFAULT_MODEL)
|
|
49
65
|
model_for_agent = model_provider ? model_id : (model || DEFAULT_MODEL)
|
|
50
66
|
|
|
51
|
-
# Create the permission handler if needed
|
|
52
|
-
permission_handler = if permission_mode == :on_request
|
|
53
|
-
handler_opts = { mode: :on_request }
|
|
54
|
-
handler_opts[:blocked_tools] = @blocked_tools if @blocked_tools
|
|
55
|
-
handler_opts[:timeout] = @permission_timeout if @permission_timeout
|
|
56
|
-
|
|
57
|
-
handler = PermissionHandler.new(**handler_opts)
|
|
58
|
-
@permission_handlers[session_id_cache] = handler if session_id_cache
|
|
59
|
-
handler
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
hooks = {}
|
|
63
|
-
hooks[:before_tool] = [permission_handler] if permission_handler
|
|
64
|
-
|
|
65
67
|
adapter = AgentAdapter.new(
|
|
66
68
|
model: model_for_agent,
|
|
67
69
|
tools: tools || DEFAULT_TOOLS,
|
|
68
70
|
system_prompt: system_prompt || build_default_system_prompt(workspace_path),
|
|
69
71
|
agent_dir: workspace_path,
|
|
70
|
-
|
|
72
|
+
approval: approval_opts[:mode],
|
|
73
|
+
require_approval: approval_opts[:require_approval],
|
|
74
|
+
plan_mode: plan_mode
|
|
71
75
|
)
|
|
72
76
|
|
|
73
77
|
session_id = adapter.start_session
|
|
74
78
|
@store.add(session_id, adapter)
|
|
75
79
|
|
|
76
|
-
#
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
80
|
+
# Attach observers after registration so they see a settled store
|
|
81
|
+
# (e.g. the pane reporter's session-count metadata), then replay
|
|
82
|
+
# the session.created event the adapter buffered during start.
|
|
83
|
+
adapter.on_event { |event| notify_session_event(adapter.session_id, event) }
|
|
84
|
+
created = adapter.pending_events.find { |e| e.type == "session.created" }
|
|
85
|
+
notify_session_event(session_id, created) if created
|
|
81
86
|
|
|
82
|
-
@logger.info("Created session #{session_id} (model=#{model || DEFAULT_MODEL},
|
|
87
|
+
@logger.info("Created session #{session_id} (model=#{model || DEFAULT_MODEL}, approval=#{approval_opts[:mode]})")
|
|
83
88
|
|
|
84
89
|
session_id
|
|
85
90
|
end
|
|
86
91
|
|
|
87
|
-
# Get the permission handler for a session, if any.
|
|
88
|
-
def permission_handler_for(session_id)
|
|
89
|
-
@permission_handlers[session_id]
|
|
90
|
-
end
|
|
91
|
-
|
|
92
92
|
# Remove a session.
|
|
93
93
|
def destroy_session(session_id)
|
|
94
94
|
adapter = @store.get(session_id)
|
|
95
95
|
return false unless adapter
|
|
96
96
|
|
|
97
|
-
# Cancel any pending permission requests
|
|
98
|
-
handler = @permission_handlers.delete(session_id)
|
|
99
|
-
handler&.cancel_all!
|
|
100
|
-
|
|
101
97
|
adapter.abort_turn! if adapter.running
|
|
98
|
+
adapter.close!
|
|
102
99
|
@store.remove(session_id)
|
|
103
100
|
@logger.info("Destroyed session #{session_id}")
|
|
104
101
|
true
|
|
105
102
|
end
|
|
106
103
|
|
|
104
|
+
# Close a session (protocol alias of destroy_session).
|
|
105
|
+
def close_session(session_id)
|
|
106
|
+
destroy_session(session_id)
|
|
107
|
+
end
|
|
108
|
+
|
|
107
109
|
# Get an adapter by session ID.
|
|
108
110
|
def get(session_id)
|
|
109
111
|
@store.get(session_id)
|
|
@@ -114,28 +116,97 @@ module Ask
|
|
|
114
116
|
@store.list(limit: limit)
|
|
115
117
|
end
|
|
116
118
|
|
|
117
|
-
# Send a message to a session
|
|
118
|
-
|
|
119
|
+
# Send a message to a session (prompt when idle, mid-execution
|
|
120
|
+
# injection when running).
|
|
121
|
+
#
|
|
122
|
+
# @return [Hash] { accepted: Boolean, status: "steered"|"queued"|"stale", turnId: }
|
|
123
|
+
def send_message(session_id, content, expected_turn_id: nil)
|
|
119
124
|
adapter = @store.get(session_id)
|
|
120
125
|
raise Ask::AppServer::SessionNotFound, "Session #{session_id} not found" unless adapter
|
|
121
126
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
127
|
+
result = adapter.send_message(content, expected_turn_id: expected_turn_id)
|
|
128
|
+
{
|
|
129
|
+
accepted: result[:status] != "stale",
|
|
130
|
+
status: result[:status],
|
|
131
|
+
turnId: result[:turn_id]
|
|
132
|
+
}
|
|
133
|
+
end
|
|
128
134
|
|
|
129
|
-
|
|
135
|
+
# ── Interactions (approvals) ───────────────────────────────────────
|
|
136
|
+
|
|
137
|
+
# Pending approval interactions for a session.
|
|
138
|
+
def pending_interactions(session_id)
|
|
139
|
+
adapter = @store.get(session_id)
|
|
140
|
+
raise Ask::AppServer::SessionNotFound, "Session #{session_id} not found" unless adapter
|
|
141
|
+
|
|
142
|
+
adapter.pending_interactions
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Approve a pending approval interaction by canonical id.
|
|
146
|
+
# @return [Boolean]
|
|
147
|
+
def approve_interaction(session_id, interaction_id)
|
|
148
|
+
adapter = @store.get(session_id)
|
|
149
|
+
raise Ask::AppServer::SessionNotFound, "Session #{session_id} not found" unless adapter
|
|
150
|
+
|
|
151
|
+
adapter.approve_interaction(interaction_id)
|
|
130
152
|
end
|
|
131
153
|
|
|
154
|
+
# Reject a pending approval interaction by canonical id.
|
|
155
|
+
# @return [Boolean]
|
|
156
|
+
def reject_interaction(session_id, interaction_id)
|
|
157
|
+
adapter = @store.get(session_id)
|
|
158
|
+
raise Ask::AppServer::SessionNotFound, "Session #{session_id} not found" unless adapter
|
|
159
|
+
|
|
160
|
+
adapter.reject_interaction(interaction_id)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Approve every pending approval interaction.
|
|
164
|
+
# @return [Integer] number approved
|
|
165
|
+
def approve_all_interactions(session_id)
|
|
166
|
+
adapter = @store.get(session_id)
|
|
167
|
+
raise Ask::AppServer::SessionNotFound, "Session #{session_id} not found" unless adapter
|
|
168
|
+
|
|
169
|
+
adapter.approve_all_interactions
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Reject every pending approval interaction.
|
|
173
|
+
# @return [Integer] number rejected
|
|
174
|
+
def reject_all_interactions(session_id)
|
|
175
|
+
adapter = @store.get(session_id)
|
|
176
|
+
raise Ask::AppServer::SessionNotFound, "Session #{session_id} not found" unless adapter
|
|
177
|
+
|
|
178
|
+
adapter.reject_all_interactions
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# ── Plan mode ───────────────────────────────────────────────────────
|
|
182
|
+
|
|
183
|
+
# Approve the pending plan proposal.
|
|
184
|
+
# @return [Boolean]
|
|
185
|
+
def plan_approve(session_id)
|
|
186
|
+
adapter = @store.get(session_id)
|
|
187
|
+
raise Ask::AppServer::SessionNotFound, "Session #{session_id} not found" unless adapter
|
|
188
|
+
|
|
189
|
+
adapter.plan_approve
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Reject the pending plan proposal; the agent stays in plan mode.
|
|
193
|
+
# @return [Boolean]
|
|
194
|
+
def plan_reject(session_id)
|
|
195
|
+
adapter = @store.get(session_id)
|
|
196
|
+
raise Ask::AppServer::SessionNotFound, "Session #{session_id} not found" unless adapter
|
|
197
|
+
|
|
198
|
+
adapter.plan_reject
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# ── Events and workspace ───────────────────────────────────────────
|
|
202
|
+
|
|
132
203
|
# Subscribe to a session's events.
|
|
133
|
-
def subscribe(session_id, delivery_kind: "
|
|
204
|
+
def subscribe(session_id, delivery_kind: "replay")
|
|
134
205
|
adapter = @store.get(session_id)
|
|
135
206
|
raise Ask::AppServer::SessionNotFound, "Session #{session_id} not found" unless adapter
|
|
136
207
|
|
|
137
208
|
@store.subscribe(session_id, delivery_kind: delivery_kind)
|
|
138
|
-
{
|
|
209
|
+
{ subscription: { sessionId: session_id, deliveryKind: delivery_kind } }
|
|
139
210
|
end
|
|
140
211
|
|
|
141
212
|
# Get events for a session after a sequence number.
|
|
@@ -155,6 +226,21 @@ module Ask
|
|
|
155
226
|
|
|
156
227
|
# Read workspace state.
|
|
157
228
|
def read_workspace_state(session_id = nil)
|
|
229
|
+
workspace = {
|
|
230
|
+
path: nil,
|
|
231
|
+
name: nil,
|
|
232
|
+
gitBranch: nil,
|
|
233
|
+
mode: approval_mode.to_s
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if session_id
|
|
237
|
+
adapter = @store.get(session_id)
|
|
238
|
+
if adapter
|
|
239
|
+
workspace[:sessionId] = session_id
|
|
240
|
+
workspace[:running] = adapter.running
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
|
|
158
244
|
settings = {
|
|
159
245
|
model: {
|
|
160
246
|
current: {
|
|
@@ -167,15 +253,7 @@ module Ask
|
|
|
167
253
|
}
|
|
168
254
|
}
|
|
169
255
|
|
|
170
|
-
|
|
171
|
-
adapter = @store.get(session_id)
|
|
172
|
-
if adapter
|
|
173
|
-
settings[:sessionId] = session_id
|
|
174
|
-
settings[:running] = adapter.running
|
|
175
|
-
end
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
{ settings: settings }
|
|
256
|
+
{ workspace: workspace, settings: settings }
|
|
179
257
|
end
|
|
180
258
|
|
|
181
259
|
# Check if a session is subscribed.
|
|
@@ -183,22 +261,37 @@ module Ask
|
|
|
183
261
|
@store.subscribed?(session_id)
|
|
184
262
|
end
|
|
185
263
|
|
|
186
|
-
|
|
187
|
-
def pending_notifications(session_id)
|
|
188
|
-
adapter = @store.get(session_id)
|
|
189
|
-
return [] unless adapter && subscribed?(session_id)
|
|
264
|
+
private
|
|
190
265
|
|
|
191
|
-
|
|
266
|
+
# Resolve the session mode into approval options + plan mode.
|
|
267
|
+
#
|
|
268
|
+
# @return [Array(Hash, Boolean)] [{ mode:, require_approval: }, plan_mode]
|
|
269
|
+
def resolve_approval(mode)
|
|
270
|
+
plan_mode = false
|
|
271
|
+
approval_mode =
|
|
272
|
+
case mode.to_s
|
|
273
|
+
when "plan" then plan_mode = true; :off
|
|
274
|
+
when "yolo", "never", "off" then :off
|
|
275
|
+
when "build", "edit", "on_request" then :require
|
|
276
|
+
when "auto" then :auto
|
|
277
|
+
else translate_permission_mode(@permission_mode)
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
opts = { mode: approval_mode }
|
|
281
|
+
opts[:require_approval] = @blocked_tools if approval_mode == :require
|
|
282
|
+
[opts, plan_mode]
|
|
192
283
|
end
|
|
193
284
|
|
|
194
|
-
|
|
285
|
+
def translate_permission_mode(mode)
|
|
286
|
+
case mode.to_s
|
|
287
|
+
when "never", "off", "yolo" then :off
|
|
288
|
+
when "auto" then :auto
|
|
289
|
+
else :require
|
|
290
|
+
end
|
|
291
|
+
end
|
|
195
292
|
|
|
196
|
-
def
|
|
197
|
-
|
|
198
|
-
return :never if mode.to_s == "plan"
|
|
199
|
-
return :on_request if mode.to_s == "build"
|
|
200
|
-
return :on_request if mode.to_s == "edit"
|
|
201
|
-
@permission_mode
|
|
293
|
+
def notify_session_event(session_id, event)
|
|
294
|
+
@session_event_observers.each { |observer| observer.call(session_id, event) }
|
|
202
295
|
end
|
|
203
296
|
|
|
204
297
|
def build_default_system_prompt(workspace_path)
|
|
@@ -217,13 +310,6 @@ module Ask
|
|
|
217
310
|
end
|
|
218
311
|
end
|
|
219
312
|
|
|
220
|
-
# Temporary cache for session ID before it's created
|
|
221
|
-
def session_id_cache
|
|
222
|
-
@_sid_counter ||= 0
|
|
223
|
-
@_sid_counter += 1
|
|
224
|
-
"_pending_#{@_sid_counter}"
|
|
225
|
-
end
|
|
226
|
-
|
|
227
313
|
# Parse a model string that may include a provider prefix.
|
|
228
314
|
# "opencode_go/deepseek-v4-flash" => ["deepseek-v4-flash", "opencode_go"]
|
|
229
315
|
# "gpt-4o" => ["gpt-4o", nil]
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "socket"
|
|
5
|
+
require "fileutils"
|
|
6
|
+
|
|
7
|
+
module Ask
|
|
8
|
+
module AppServer
|
|
9
|
+
# Unix-socket transport for the session host: any number of clients can
|
|
10
|
+
# attach to the same sessions (terminal TUI, web console, bots) by
|
|
11
|
+
# connecting to the socket and speaking the canonical session protocol
|
|
12
|
+
# as NDJSON lines.
|
|
13
|
+
#
|
|
14
|
+
# Each accepted connection gets its own reader thread and a Connection
|
|
15
|
+
# with per-session delivery cursors; the shared protocol engine
|
|
16
|
+
# ({Server}) routes responses back to the requesting connection and
|
|
17
|
+
# fans out session/event notifications to every subscribed connection.
|
|
18
|
+
#
|
|
19
|
+
# The socket file is removed on start (stale cleanup) and on stop.
|
|
20
|
+
class SocketServer
|
|
21
|
+
# Default socket path: ASK_APP_SERVER_SOCKET env var or
|
|
22
|
+
# ~/.ask-app-server/app-server.sock
|
|
23
|
+
def self.default_socket_path
|
|
24
|
+
ENV["ASK_APP_SERVER_SOCKET"] ||
|
|
25
|
+
File.expand_path("~/.ask-app-server/app-server.sock")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
attr_reader :socket_path, :protocol
|
|
29
|
+
|
|
30
|
+
# @param session_manager [SessionManager, nil] shared session manager
|
|
31
|
+
# @param socket_path [String, nil] unix socket path
|
|
32
|
+
def initialize(session_manager: nil, socket_path: nil)
|
|
33
|
+
@session_manager = session_manager || SessionManager.new
|
|
34
|
+
@protocol = Server.new(session_manager: @session_manager)
|
|
35
|
+
@socket_path = socket_path || self.class.default_socket_path
|
|
36
|
+
@running = false
|
|
37
|
+
@listener = nil
|
|
38
|
+
@acceptor = nil
|
|
39
|
+
@pusher = nil
|
|
40
|
+
@logger = Logger.new($stdout, level: ENV["DEBUG"] ? Logger::DEBUG : Logger::WARN)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Bind the socket and start accepting connections. Returns
|
|
44
|
+
# immediately (accept and push run in background threads).
|
|
45
|
+
def start
|
|
46
|
+
@running = true
|
|
47
|
+
FileUtils.mkdir_p(File.dirname(@socket_path))
|
|
48
|
+
FileUtils.rm_f(@socket_path) # stale socket from a previous run
|
|
49
|
+
@listener = UNIXServer.new(@socket_path)
|
|
50
|
+
@acceptor = Thread.new { accept_loop }
|
|
51
|
+
@pusher = Thread.new { pusher_loop }
|
|
52
|
+
@logger.debug("Socket server listening on #{@socket_path}")
|
|
53
|
+
self
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Stop accepting, close all connections, and remove the socket file.
|
|
57
|
+
def stop
|
|
58
|
+
@running = false
|
|
59
|
+
@acceptor&.kill rescue nil
|
|
60
|
+
@pusher&.kill rescue nil
|
|
61
|
+
@protocol.connections.each { |conn| @protocol.remove_connection(conn) }
|
|
62
|
+
@listener&.close rescue nil
|
|
63
|
+
FileUtils.rm_f(@socket_path) rescue nil
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Whether the server is running.
|
|
67
|
+
def running?
|
|
68
|
+
@running
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
def accept_loop
|
|
74
|
+
while @running
|
|
75
|
+
socket = @listener.accept
|
|
76
|
+
connection = @protocol.add_connection(Connection.new(socket, socket))
|
|
77
|
+
reader = Thread.new { reader_loop(connection, socket) }
|
|
78
|
+
reader.name = "ask-socket-client" if reader.respond_to?(:name=)
|
|
79
|
+
end
|
|
80
|
+
rescue IOError, Errno::EBADF, Errno::EINVAL
|
|
81
|
+
# Listener closed during shutdown.
|
|
82
|
+
rescue => e
|
|
83
|
+
@logger.error("Accept error: #{e.message}")
|
|
84
|
+
sleep 0.1
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def reader_loop(connection, socket)
|
|
88
|
+
while @running
|
|
89
|
+
line = connection.read_line
|
|
90
|
+
break unless line
|
|
91
|
+
line = line.strip
|
|
92
|
+
next if line.empty?
|
|
93
|
+
|
|
94
|
+
begin
|
|
95
|
+
@protocol.dispatch(JSON.parse(line), connection)
|
|
96
|
+
rescue JSON::ParserError => e
|
|
97
|
+
@protocol.send(:send_error, nil, -32700, "Parse error: #{e.message}", connection)
|
|
98
|
+
rescue => e
|
|
99
|
+
@logger.error("Socket reader error: #{e.message}")
|
|
100
|
+
break
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
ensure
|
|
104
|
+
@protocol.remove_connection(connection)
|
|
105
|
+
socket.close rescue nil
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def pusher_loop
|
|
109
|
+
while @running
|
|
110
|
+
@protocol.push_pending
|
|
111
|
+
sleep 0.1 # 100ms poll interval
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
data/lib/ask/app_server.rb
CHANGED
|
@@ -7,6 +7,8 @@ module Ask
|
|
|
7
7
|
class SessionNotFound < Error; end
|
|
8
8
|
class SessionAlreadyExists < Error; end
|
|
9
9
|
class SessionNotSubscribed < Error; end
|
|
10
|
+
class InteractionNotFound < Error; end
|
|
11
|
+
class PlanNotFound < Error; end
|
|
10
12
|
class InvalidRequest < Error; end
|
|
11
13
|
class TimeoutError < Error; end
|
|
12
14
|
end
|
data/lib/ask-app-server.rb
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "ask/agent"
|
|
4
|
+
require "ask-session-protocol"
|
|
4
5
|
require "ask-tools-shell"
|
|
5
6
|
|
|
6
7
|
require_relative "ask/app_server/version"
|
|
7
8
|
require_relative "ask/app_server"
|
|
8
9
|
require_relative "ask/app_server/config"
|
|
9
10
|
require_relative "ask/app_server/event_translator"
|
|
11
|
+
require_relative "ask/app_server/connection"
|
|
10
12
|
require_relative "ask/app_server/agent_adapter"
|
|
11
13
|
require_relative "ask/app_server/permission_handler"
|
|
12
14
|
require_relative "ask/app_server/session_store"
|
|
13
15
|
require_relative "ask/app_server/session_manager"
|
|
14
16
|
require_relative "ask/app_server/server"
|
|
17
|
+
require_relative "ask/app_server/socket_server"
|
|
18
|
+
require_relative "ask/app_server/herdr_reporter"
|
|
15
19
|
require_relative "ask/app_server/cli"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ask-app-server
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kaka Ruto
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version:
|
|
18
|
+
version: 0.40.1
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version:
|
|
25
|
+
version: 0.40.1
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: ask-core
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -38,7 +38,7 @@ dependencies:
|
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
39
|
version: '0.1'
|
|
40
40
|
- !ruby/object:Gem::Dependency
|
|
41
|
-
name: ask-
|
|
41
|
+
name: ask-session-protocol
|
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
|
43
43
|
requirements:
|
|
44
44
|
- - ">="
|
|
@@ -51,6 +51,20 @@ dependencies:
|
|
|
51
51
|
- - ">="
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
53
|
version: '0.1'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: ask-tools
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: 0.6.1
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: 0.6.1
|
|
54
68
|
- !ruby/object:Gem::Dependency
|
|
55
69
|
name: ask-tools-shell
|
|
56
70
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -168,11 +182,14 @@ files:
|
|
|
168
182
|
- lib/ask/app_server/agent_adapter.rb
|
|
169
183
|
- lib/ask/app_server/cli.rb
|
|
170
184
|
- lib/ask/app_server/config.rb
|
|
185
|
+
- lib/ask/app_server/connection.rb
|
|
171
186
|
- lib/ask/app_server/event_translator.rb
|
|
187
|
+
- lib/ask/app_server/herdr_reporter.rb
|
|
172
188
|
- lib/ask/app_server/permission_handler.rb
|
|
173
189
|
- lib/ask/app_server/server.rb
|
|
174
190
|
- lib/ask/app_server/session_manager.rb
|
|
175
191
|
- lib/ask/app_server/session_store.rb
|
|
192
|
+
- lib/ask/app_server/socket_server.rb
|
|
176
193
|
- lib/ask/app_server/version.rb
|
|
177
194
|
homepage: https://github.com/ask-rb/ask-app-server
|
|
178
195
|
licenses:
|
|
@@ -195,7 +212,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
195
212
|
- !ruby/object:Gem::Version
|
|
196
213
|
version: '0'
|
|
197
214
|
requirements: []
|
|
198
|
-
rubygems_version: 4.0.
|
|
215
|
+
rubygems_version: 4.0.18
|
|
199
216
|
specification_version: 4
|
|
200
217
|
summary: JSON-RPC app-server for ask-rb agents
|
|
201
218
|
test_files: []
|