ask-rails 0.3.0 → 0.11.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 +129 -0
- data/app/controllers/ask/rails/chat_controller.rb +145 -22
- data/app/models/ask/rails/session.rb +23 -0
- data/app/views/layouts/ask/rails/application.html.erb +364 -63
- data/config/routes.rb +3 -1
- data/lib/ask/rails/audit_log.rb +181 -0
- data/lib/ask/rails/configuration.rb +53 -1
- data/lib/ask/rails/environment_permissions.rb +39 -0
- data/lib/ask/rails/persistence.rb +18 -5
- data/lib/ask/rails/railtie.rb +5 -1
- data/lib/ask/rails/tool.rb +29 -0
- data/lib/ask/rails/tools/read_file.rb +1 -4
- data/lib/ask/rails/tools/read_routes.rb +1 -4
- data/lib/ask/rails/tools/route_inspector.rb +69 -0
- data/lib/ask/rails/tools/run_command.rb +39 -1
- data/lib/ask/rails/tools/schema_graph.rb +174 -0
- data/lib/ask/rails/tools/search_codebase.rb +6 -7
- data/lib/ask/rails/version.rb +1 -1
- data/lib/ask/rails.rb +105 -3
- data/lib/generators/ask/rails/install/install_generator.rb +5 -0
- data/lib/generators/ask/rails/install/templates/audit_log_migration.rb +22 -0
- data/lib/generators/ask/rails/install/templates/initializer.rb +1 -1
- metadata +7 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 862c3a8f40c0b2a6d720ab247ce87caecdfc9e5c9242c1daa386eb09d23fe300
|
|
4
|
+
data.tar.gz: 942cfdfd66cbc0e36ed56e1bfd16009226d12244cfa558e75facc99ace1c3781
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0e769e86dec70f2a4533f0de007c00e32f63c8d270d2929b29e503c22fa5b4bf780c2464be5b65d30bbb0596a2967d075cf4b122d162169178acc62dece6bf54
|
|
7
|
+
data.tar.gz: eccd169faf6f16f3e949fe2562e07f46eeaa99d4118bb4c7952526daeeacc6d9337c804640087d1850a6d0ee34efd79aa05e7060889fec2d7b455a62a296b0e5
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,121 @@
|
|
|
1
|
+
## [0.10.0] — 2026-07-23
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
|
|
5
|
+
- **Session cleanup** — Auto-prune old or excess sessions via `max_session_age` and `max_sessions` config options. Prunes both `ask_sessions` and `ask_audit_logs` tables.
|
|
6
|
+
- `config.max_session_age = 7.days` — deletes sessions older than the threshold
|
|
7
|
+
- `config.max_sessions = 1000` — keeps at most N sessions, deletes oldest
|
|
8
|
+
- Auto-prunes on every `agent_session` call when configured
|
|
9
|
+
- Also callable manually: `Ask::Rails.cleanup!`
|
|
10
|
+
- Rake task: `rails ask_rails:cleanup`
|
|
11
|
+
|
|
12
|
+
## [0.9.0] — 2026-07-23
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- **ReadFile, ReadRoutes, SearchCodebase** — Fixed `ArgumentError: unknown keyword: :data` crash on Ruby 4.0. Tools now return plain Hashes on success instead of using the incompatible `Ask::Result.success` API.
|
|
17
|
+
- **SearchCodebase** — Marked `path` param as optional (was incorrectly required). Added shell escaping for grep patterns.
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- **23 new execution tests** — ReadFile (3), SearchCodebase (3), ReadRoutes (2), ReadModel (6), plus 6 integration tests. Each tool now has real execution tests beyond just param checks.
|
|
22
|
+
- **160 total tests** — 0 failures, 0 errors.
|
|
23
|
+
|
|
24
|
+
## [0.8.0] — 2026-07-23
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- **SchemaGraph tool** — Full application schema introspection in one call. Returns every model, table, column (with types and nullability), association (belongs_to, has_many, has_one, HABTM, through), validation, index, and polymorphic relationship. The agent gets a complete mental model of the app's data layer — no more reading models one at a time.
|
|
29
|
+
- `schema_graph(detail: "all")` — everything
|
|
30
|
+
- `schema_graph(detail: "models")` — just models and columns
|
|
31
|
+
- `schema_graph(detail: "associations")` — just the association graph
|
|
32
|
+
- `schema_graph(detail: "tables")` — just tables, columns, and indexes
|
|
33
|
+
|
|
34
|
+
- **RouteInspector tool** — Parsed Rails route table (not raw routes.rb). Returns every route with HTTP verb, path, controller, action, and name. Supports filtering by controller and path pattern. Replaces the old ReadRoutes which returned raw file content.
|
|
35
|
+
- `route_inspector` — all routes
|
|
36
|
+
- `route_inspector(controller: "users")` — routes for a specific controller
|
|
37
|
+
- `route_inspector(pattern: "admin")` — routes matching a path pattern
|
|
38
|
+
|
|
39
|
+
- **Core tools auto-discovery** — The 9 built-in Rails tools (ReadFile, RunCommand, SearchCodebase, ReadRoutes, QueryDatabase, ReadModel, ReadLog, SchemaGraph, RouteInspector) are now automatically included in every agent session. Previously they were loaded but not registered.
|
|
40
|
+
|
|
41
|
+
### Changed
|
|
42
|
+
|
|
43
|
+
- `discover_tools!` now includes `CORE_RAILS_TOOLS` — all 9 Rails tools are automatically available to the agent.
|
|
44
|
+
|
|
45
|
+
## [0.7.0] — 2026-07-23
|
|
46
|
+
|
|
47
|
+
### Added
|
|
48
|
+
|
|
49
|
+
- **Tool execution visualization** — The chat UI now shows live tool execution cards as the agent runs. Each tool call displays as an expandable card with name, args, and real-time elapsed time. Cards show ✓ for success or ✗ for failure with duration.
|
|
50
|
+
- **Activity panel** — New "Activity" sidebar tab shows the audit log in real-time. Browse recent tool calls across all sessions with status, duration, and timestamp.
|
|
51
|
+
- **Per-session audit view** — Clicking a session loads its audit trail. New `GET /ask/sessions/:session_id/audit` endpoint.
|
|
52
|
+
- **Individual session deletion** — Delete sessions one at a time via the ✕ button in the sidebar. New `DELETE /ask/sessions/:id` route.
|
|
53
|
+
- **Sidebar search** — Filter sessions by text in the sidebar search box.
|
|
54
|
+
- **Session previews** — Sidebar shows the first message as a preview instead of just the session ID.
|
|
55
|
+
- **Keyboard shortcuts** — Ctrl+K / Cmd+K focuses the input. Enter sends (Shift+Enter for newline).
|
|
56
|
+
- **Environment badge** — Current Rails.env shown in both the sidebar footer and chat header.
|
|
57
|
+
- **`destroy_session` action** — New controller action for deleting individual sessions.
|
|
58
|
+
|
|
59
|
+
### Changed
|
|
60
|
+
|
|
61
|
+
- **Chat layout** — Complete visual overhaul: darker theme, improved spacing, tool cards, sidebar tabs (Sessions/Activity), session search, and streaming indicator.
|
|
62
|
+
- **`message` SSE stream** — Now emits `tool_start`, `tool_end`, and `tool_update` events alongside `delta` and `thinking` for real-time tool execution visibility.
|
|
63
|
+
- **Controller actions** — `destroy` renamed to `destroy_all` for clarity.
|
|
64
|
+
|
|
65
|
+
## [0.6.0] — 2026-07-23
|
|
66
|
+
|
|
67
|
+
### Added
|
|
68
|
+
|
|
69
|
+
- **Per-environment permissions** — Configure access modes and command allowlists per Rails environment:
|
|
70
|
+
```ruby
|
|
71
|
+
Ask::Rails.configure do |config|
|
|
72
|
+
config.environment :production do |env|
|
|
73
|
+
env.mode = :read_only
|
|
74
|
+
env.allowed_commands = [/^rails routes/]
|
|
75
|
+
env.denied_commands = [/rm/, /dropdb/]
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
config.environment :development do |env|
|
|
79
|
+
env.mode = :full_access
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
```
|
|
83
|
+
- **`Ask::Rails::EnvironmentPermissions`** — New config class holding `mode`, `allowed_commands`, and `denied_commands` per environment.
|
|
84
|
+
- **Automatic Permissions wiring** — When an environment `mode` is set, `agent_session` automatically creates an `Ask::Agent::Extensions::Permissions` extension and wires it into the session hooks.
|
|
85
|
+
- **Effective rule resolution** — `Configuration#effective_allowed_commands`, `#effective_denied_commands`, and `#effective_mode` resolve per-environment rules or fall back to global config.
|
|
86
|
+
|
|
87
|
+
## [0.5.0] — 2026-07-22
|
|
88
|
+
|
|
89
|
+
### Added
|
|
90
|
+
|
|
91
|
+
- **Command Allowlist** — `RunCommand` now checks `allowed_commands` and `denied_commands` before executing. Configure with regex patterns:
|
|
92
|
+
- `Ask::Rails.configuration.allowed_commands = [/^rails /, /^git status/]`
|
|
93
|
+
- `Ask::Rails.configuration.denied_commands = [/rm /, /dropdb/]`
|
|
94
|
+
- `denied_commands` takes precedence over `allowed_commands`
|
|
95
|
+
- When `allowed_commands` is nil (default), all commands pass through (except those in `denied_commands`)
|
|
96
|
+
- Blocked commands return `Ask::Result.error` with a descriptive message and are recorded in the audit log
|
|
97
|
+
|
|
98
|
+
- **`allowed_commands` and `denied_commands` configuration options** — Two new arrays of regex patterns on the `Configuration` object.
|
|
99
|
+
|
|
100
|
+
## [0.4.0] — 2026-07-22
|
|
101
|
+
|
|
102
|
+
### Added
|
|
103
|
+
|
|
104
|
+
- **Audit Log** — Every tool call is now recorded in the `ask_audit_logs` table with the intent (sanitized params), outcome (status/timing), and user context. Append-only, never modified. Provides a trustworthy record of what the agent did without storing sensitive data or full results.
|
|
105
|
+
- `Ask::Rails::AuditLog.log` — logs a tool execution event
|
|
106
|
+
- Sensitive params (keys matching `password`, `secret`, `token`, `api_key`, `key`) are automatically redacted as `[REDACTED]`
|
|
107
|
+
- Fires `audit_log.ask_rails` ActiveSupport notification for host app alerting
|
|
108
|
+
- Results are summarized (row count for queries, exit status for commands, etc.) — full data never stored
|
|
109
|
+
- Generator creates the `create_ask_audit_logs` migration
|
|
110
|
+
|
|
111
|
+
- **`current_user` configuration** — `Ask::Rails.configure { |c| c.current_user = -> { Current.user ? { id: Current.user.id, email: Current.user.email } : nil } }` attaches user context to every audit log entry.
|
|
112
|
+
|
|
113
|
+
- **`Ask::Rails::Tool.session_id`** — Thread-local accessor so tool calls are correlated with their agent session in the audit log.
|
|
114
|
+
|
|
115
|
+
### Changed
|
|
116
|
+
|
|
117
|
+
- **Tool base class** — `Ask::Rails::Tool#call` is now instrumented to automatically log every invocation to the audit log. All 7 Rails tools inherit this behavior.
|
|
118
|
+
|
|
1
119
|
## [0.3.0] — 2026-07-21
|
|
2
120
|
|
|
3
121
|
### Added
|
|
@@ -61,3 +179,14 @@
|
|
|
61
179
|
- Rails Tools — `ReadFile`, `RunCommand`, `SearchCodebase`, `ReadRoutes` (Rails.root-aware)
|
|
62
180
|
- Generators — `rails generate ask_rails:install` creates migration, initializer, `app/tools/`
|
|
63
181
|
- Dependencies: rails >= 7.1, ask-tools, ask-tools-shell, ask-agent, ask-auth
|
|
182
|
+
|
|
183
|
+
## [0.11.0] — 2026-07-23
|
|
184
|
+
|
|
185
|
+
### Changed
|
|
186
|
+
|
|
187
|
+
- **`Persistence` adapter now speaks `State::Adapter` contract** — Added `set(key, value)` and `get(key)` methods delegating to `save`/`load`. This makes it compatible with the new `state:` keyword on `Ask::Agent::Session`. Old `save`/`load` interface preserved for backward compatibility.
|
|
188
|
+
- **`agent_session` uses `state:` keyword** — Changed from `persistence:` to `state:`.
|
|
189
|
+
|
|
190
|
+
### Added
|
|
191
|
+
|
|
192
|
+
- **Persistence tests** — New `FakeModel` fixture with 6 real roundtrip tests covering set/get, missing key, delete, list, and old save/load interface.
|
|
@@ -8,12 +8,13 @@ module Ask
|
|
|
8
8
|
layout "ask/rails/application"
|
|
9
9
|
|
|
10
10
|
before_action :authenticate!, unless: -> { Ask::Rails::Auth.check.nil? }
|
|
11
|
-
before_action :set_session, only: [:message, :stream, :history, :show]
|
|
11
|
+
before_action :set_session, only: [:message, :stream, :history, :show, :audit]
|
|
12
12
|
|
|
13
13
|
# GET /ask — main chat interface
|
|
14
14
|
def index
|
|
15
15
|
@sessions = load_sessions
|
|
16
16
|
@active_session = @sessions.first
|
|
17
|
+
@audit_logs = load_recent_audit_logs
|
|
17
18
|
render :index
|
|
18
19
|
end
|
|
19
20
|
|
|
@@ -22,10 +23,8 @@ module Ask
|
|
|
22
23
|
session_id = SecureRandom.uuid
|
|
23
24
|
|
|
24
25
|
agent = build_agent_session
|
|
25
|
-
agent.run("Hello") # warm up
|
|
26
26
|
agent.save
|
|
27
27
|
|
|
28
|
-
# Store minimal session metadata
|
|
29
28
|
store_session_metadata(session_id, agent)
|
|
30
29
|
|
|
31
30
|
redirect_to ask_rails.root_path
|
|
@@ -34,7 +33,8 @@ module Ask
|
|
|
34
33
|
# GET /ask/sessions/:id — show a specific session
|
|
35
34
|
def show
|
|
36
35
|
@messages = load_session_messages(@session_id)
|
|
37
|
-
|
|
36
|
+
@audit_logs = load_session_audit_logs(@session_id)
|
|
37
|
+
render json: { id: @session_id, messages: @messages, audit_logs: @audit_logs }
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
# GET /ask/sessions — list all sessions
|
|
@@ -51,28 +51,41 @@ module Ask
|
|
|
51
51
|
response.headers["Content-Type"] = "text/event-stream"
|
|
52
52
|
response.headers["Cache-Control"] = "no-cache"
|
|
53
53
|
response.headers["X-Accel-Buffering"] = "no"
|
|
54
|
-
|
|
55
|
-
# Disable buffering for streaming
|
|
56
54
|
response.headers["Last-Modified"] = Time.now.httpdate
|
|
57
55
|
|
|
58
56
|
self.response_body = Enumerator.new do |yielder|
|
|
59
57
|
agent = resume_or_create_session(@session_id)
|
|
60
58
|
|
|
59
|
+
# Subscribe to tool execution events
|
|
60
|
+
tool_sub = subscribe_tool_events(agent, yielder)
|
|
61
|
+
|
|
61
62
|
yielder << "data: #{JSON.generate(type: 'start', session_id: @session_id)}\n\n"
|
|
62
63
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
begin
|
|
65
|
+
agent.run(prompt) do |chunk|
|
|
66
|
+
if chunk.content&.length&.> 0
|
|
67
|
+
yielder << "data: #{JSON.generate(type: 'delta', content: chunk.content)}\n\n"
|
|
68
|
+
end
|
|
69
|
+
if chunk.thinking&.length&.> 0
|
|
70
|
+
yielder << "data: #{JSON.generate(type: 'thinking', content: chunk.thinking)}\n\n"
|
|
71
|
+
end
|
|
66
72
|
end
|
|
67
|
-
if chunk.thinking&.length&.> 0
|
|
68
|
-
yielder << "data: #{JSON.generate(type: 'thinking', content: chunk.thinking)}\n\n"
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
73
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
agent.save
|
|
75
|
+
@session = agent
|
|
76
|
+
|
|
77
|
+
yielder << "data: #{JSON.generate(type: 'done', session_id: @session_id)}\n\n"
|
|
78
|
+
rescue Ask::Agent::MaxTurnsExceeded => e
|
|
79
|
+
yielder << "data: #{JSON.generate(type: 'error', message: "Agent hit the turn limit (#{e.message})")}\n\n"
|
|
80
|
+
rescue Ask::Auth::MissingCredential => e
|
|
81
|
+
yielder << "data: #{JSON.generate(type: 'error', message: "Missing API key: #{e.message}. Check your provider configuration.")}\n\n"
|
|
82
|
+
rescue Ask::Auth::InvalidCredential => e
|
|
83
|
+
yielder << "data: #{JSON.generate(type: 'error', message: "Invalid API key: #{e.message}. Update your credentials.")}\n\n"
|
|
84
|
+
rescue StandardError => e
|
|
85
|
+
yielder << "data: #{JSON.generate(type: 'error', message: "Agent error: #{e.message}")}\n\n"
|
|
86
|
+
end
|
|
87
|
+
ensure
|
|
88
|
+
agent&.remove_event_subscriber(tool_sub) if tool_sub
|
|
76
89
|
end
|
|
77
90
|
end
|
|
78
91
|
|
|
@@ -84,9 +97,9 @@ module Ask
|
|
|
84
97
|
|
|
85
98
|
self.response_body = Enumerator.new do |yielder|
|
|
86
99
|
messages = load_session_messages(@session_id)
|
|
87
|
-
|
|
100
|
+
audit_logs = load_session_audit_logs(@session_id)
|
|
101
|
+
yielder << "data: #{JSON.generate(type: 'history', messages: messages, audit_logs: audit_logs)}\n\n"
|
|
88
102
|
|
|
89
|
-
# Keep connection open briefly for potential updates
|
|
90
103
|
sleep 30
|
|
91
104
|
yielder << "data: #{JSON.generate(type: 'keepalive')}\n\n"
|
|
92
105
|
end
|
|
@@ -98,8 +111,14 @@ module Ask
|
|
|
98
111
|
render json: messages
|
|
99
112
|
end
|
|
100
113
|
|
|
114
|
+
# GET /ask/sessions/:session_id/audit — get audit logs for a session
|
|
115
|
+
def audit
|
|
116
|
+
logs = load_session_audit_logs(@session_id)
|
|
117
|
+
render json: logs
|
|
118
|
+
end
|
|
119
|
+
|
|
101
120
|
# DELETE /ask/sessions — destroy all sessions
|
|
102
|
-
def
|
|
121
|
+
def destroy_all
|
|
103
122
|
persistence = Ask::Rails.configuration.persistence_adapter
|
|
104
123
|
if persistence
|
|
105
124
|
persistence.list.each { |id| persistence.delete(id) }
|
|
@@ -107,6 +126,13 @@ module Ask
|
|
|
107
126
|
redirect_to ask_rails.root_path
|
|
108
127
|
end
|
|
109
128
|
|
|
129
|
+
# DELETE /ask/sessions/:session_id — destroy a specific session
|
|
130
|
+
def destroy_session
|
|
131
|
+
persistence = Ask::Rails.configuration.persistence_adapter
|
|
132
|
+
persistence&.delete(params[:session_id])
|
|
133
|
+
redirect_to ask_rails.root_path
|
|
134
|
+
end
|
|
135
|
+
|
|
110
136
|
private
|
|
111
137
|
|
|
112
138
|
def authenticate!
|
|
@@ -122,6 +148,46 @@ module Ask
|
|
|
122
148
|
Ask::Rails.agent_session(**extra)
|
|
123
149
|
end
|
|
124
150
|
|
|
151
|
+
def subscribe_tool_events(agent, yielder)
|
|
152
|
+
return nil unless agent.respond_to?(:on_event)
|
|
153
|
+
|
|
154
|
+
agent.on_event do |event|
|
|
155
|
+
case event
|
|
156
|
+
when Ask::Agent::Events::ToolExecutionStart
|
|
157
|
+
yielder << "data: #{JSON.generate(
|
|
158
|
+
type: 'tool_start',
|
|
159
|
+
name: event.name,
|
|
160
|
+
id: event.id,
|
|
161
|
+
args: safe_tool_args(event.arguments)
|
|
162
|
+
)}\n\n"
|
|
163
|
+
when Ask::Agent::Events::ToolExecutionEnd
|
|
164
|
+
yielder << "data: #{JSON.generate(
|
|
165
|
+
type: 'tool_end',
|
|
166
|
+
name: event.name,
|
|
167
|
+
id: event.id,
|
|
168
|
+
duration_ms: event.duration_ms,
|
|
169
|
+
is_error: event.is_error
|
|
170
|
+
)}\n\n"
|
|
171
|
+
when Ask::Agent::Events::ToolExecutionUpdate
|
|
172
|
+
yielder << "data: #{JSON.generate(
|
|
173
|
+
type: 'tool_update',
|
|
174
|
+
id: event.id,
|
|
175
|
+
content: event.partial_result.to_s.truncate(200)
|
|
176
|
+
)}\n\n"
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def safe_tool_args(args)
|
|
182
|
+
return {} unless args.is_a?(Hash)
|
|
183
|
+
|
|
184
|
+
safe = args.dup
|
|
185
|
+
%w[password secret token api_key key auth_token access_token sql command].each do |sensitive|
|
|
186
|
+
safe[sensitive] = "[REDACTED]" if safe.key?(sensitive)
|
|
187
|
+
end
|
|
188
|
+
safe
|
|
189
|
+
end
|
|
190
|
+
|
|
125
191
|
def resume_or_create_session(session_id)
|
|
126
192
|
persistence = Ask::Rails.configuration.persistence_adapter
|
|
127
193
|
|
|
@@ -144,7 +210,8 @@ module Ask
|
|
|
144
210
|
|
|
145
211
|
persistence.list.map do |id|
|
|
146
212
|
data = persistence.load(id) || {}
|
|
147
|
-
|
|
213
|
+
messages = data[:messages] || []
|
|
214
|
+
{ id: id, created_at: data.dig(:metadata, :created_at), message_count: messages.length, preview: messages.first&.dig(:content).to_s.truncate(60) }
|
|
148
215
|
end.sort_by { |s| s[:created_at] || "" }.reverse
|
|
149
216
|
end
|
|
150
217
|
|
|
@@ -156,10 +223,62 @@ module Ask
|
|
|
156
223
|
return [] unless data
|
|
157
224
|
|
|
158
225
|
(data[:messages] || []).map { |m|
|
|
159
|
-
{ role: m[:role], content: m[:content].to_s
|
|
226
|
+
{ role: m[:role], content: m[:content].to_s }
|
|
160
227
|
}
|
|
161
228
|
end
|
|
162
229
|
|
|
230
|
+
def load_session_audit_logs(session_id)
|
|
231
|
+
return [] unless defined?(ActiveRecord::Base)
|
|
232
|
+
|
|
233
|
+
ActiveRecord::Base.connection.execute(
|
|
234
|
+
"SELECT tool_name, status, duration_ms, environment, recorded_at, " \
|
|
235
|
+
"params, result_summary, error_message, user_context " \
|
|
236
|
+
"FROM ask_audit_logs WHERE session_id = #{ActiveRecord::Base.connection.quote(session_id.to_s)} " \
|
|
237
|
+
"ORDER BY recorded_at ASC"
|
|
238
|
+
).map do |row|
|
|
239
|
+
{
|
|
240
|
+
tool_name: row["tool_name"],
|
|
241
|
+
status: row["status"],
|
|
242
|
+
duration_ms: row["duration_ms"],
|
|
243
|
+
environment: row["environment"],
|
|
244
|
+
recorded_at: row["recorded_at"],
|
|
245
|
+
params: parse_json_field(row["params"]),
|
|
246
|
+
result_summary: parse_json_field(row["result_summary"]),
|
|
247
|
+
error_message: row["error_message"],
|
|
248
|
+
user_context: parse_json_field(row["user_context"])
|
|
249
|
+
}
|
|
250
|
+
end
|
|
251
|
+
rescue StandardError
|
|
252
|
+
[]
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def load_recent_audit_logs
|
|
256
|
+
return [] unless defined?(ActiveRecord::Base)
|
|
257
|
+
|
|
258
|
+
ActiveRecord::Base.connection.execute(
|
|
259
|
+
"SELECT session_id, tool_name, status, duration_ms, recorded_at " \
|
|
260
|
+
"FROM ask_audit_logs ORDER BY recorded_at DESC LIMIT 100"
|
|
261
|
+
).map do |row|
|
|
262
|
+
{
|
|
263
|
+
session_id: row["session_id"].to_s[0..7],
|
|
264
|
+
tool_name: row["tool_name"],
|
|
265
|
+
status: row["status"],
|
|
266
|
+
duration_ms: row["duration_ms"],
|
|
267
|
+
recorded_at: row["recorded_at"]
|
|
268
|
+
}
|
|
269
|
+
end
|
|
270
|
+
rescue StandardError
|
|
271
|
+
[]
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def parse_json_field(value)
|
|
275
|
+
return nil unless value
|
|
276
|
+
return value if value.is_a?(Hash) || value.is_a?(Array)
|
|
277
|
+
JSON.parse(value)
|
|
278
|
+
rescue JSON::ParserError
|
|
279
|
+
value
|
|
280
|
+
end
|
|
281
|
+
|
|
163
282
|
def store_session_metadata(session_id, agent)
|
|
164
283
|
persistence = Ask::Rails.configuration.persistence_adapter
|
|
165
284
|
return unless persistence
|
|
@@ -180,6 +299,10 @@ module Ask
|
|
|
180
299
|
def ask_rails_model
|
|
181
300
|
Ask::Rails.configuration.default_model
|
|
182
301
|
end
|
|
302
|
+
|
|
303
|
+
def tool_events_supported?
|
|
304
|
+
defined?(Ask::Agent::Events::ToolExecutionStart)
|
|
305
|
+
end
|
|
183
306
|
end
|
|
184
307
|
end
|
|
185
308
|
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ask
|
|
4
|
+
module Rails
|
|
5
|
+
# ActiveRecord model for persisting agent sessions.
|
|
6
|
+
#
|
|
7
|
+
# Created by the +ask_rails:install+ generator. The +ask_sessions+ table
|
|
8
|
+
# stores session state in a JSONB +data+ column, keyed by a unique
|
|
9
|
+
# +session_id+ string.
|
|
10
|
+
#
|
|
11
|
+
# Used automatically when +persistence_adapter+ is configured with the
|
|
12
|
+
# default model class:
|
|
13
|
+
#
|
|
14
|
+
# Ask::Rails.configure do |config|
|
|
15
|
+
# config.persistence_adapter = Ask::Rails::Persistence.new
|
|
16
|
+
# end
|
|
17
|
+
#
|
|
18
|
+
# Without +model_class:+, +Persistence+ defaults to +Ask::Rails::Session+.
|
|
19
|
+
class Session < ActiveRecord::Base
|
|
20
|
+
self.table_name = "ask_sessions"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|