ruby-claw 0.1.2 → 0.2.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.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +94 -0
  3. data/README.md +214 -10
  4. data/exe/claw +42 -1
  5. data/lib/claw/auto_forge.rb +66 -0
  6. data/lib/claw/benchmark/benchmark.rb +79 -0
  7. data/lib/claw/benchmark/diff.rb +69 -0
  8. data/lib/claw/benchmark/report.rb +87 -0
  9. data/lib/claw/benchmark/runner.rb +91 -0
  10. data/lib/claw/benchmark/scorer.rb +69 -0
  11. data/lib/claw/benchmark/task.rb +63 -0
  12. data/lib/claw/benchmark/tasks/claw_remember.rb +20 -0
  13. data/lib/claw/benchmark/tasks/claw_session.rb +18 -0
  14. data/lib/claw/benchmark/tasks/evolution_trace.rb +18 -0
  15. data/lib/claw/benchmark/tasks/mana_call_func.rb +21 -0
  16. data/lib/claw/benchmark/tasks/mana_eval.rb +18 -0
  17. data/lib/claw/benchmark/tasks/mana_knowledge.rb +19 -0
  18. data/lib/claw/benchmark/tasks/mana_var_readwrite.rb +18 -0
  19. data/lib/claw/benchmark/tasks/runtime_fork.rb +18 -0
  20. data/lib/claw/benchmark/tasks/runtime_snapshot.rb +18 -0
  21. data/lib/claw/benchmark/trigger.rb +68 -0
  22. data/lib/claw/chat.rb +119 -6
  23. data/lib/claw/child_runtime.rb +196 -0
  24. data/lib/claw/cli.rb +177 -0
  25. data/lib/claw/commands.rb +131 -0
  26. data/lib/claw/config.rb +5 -1
  27. data/lib/claw/console/event_logger.rb +69 -0
  28. data/lib/claw/console/public/app.js +264 -0
  29. data/lib/claw/console/public/style.css +330 -0
  30. data/lib/claw/console/server.rb +253 -0
  31. data/lib/claw/console/sse.rb +28 -0
  32. data/lib/claw/console/views/experiments.erb +8 -0
  33. data/lib/claw/console/views/index.erb +27 -0
  34. data/lib/claw/console/views/layout.erb +29 -0
  35. data/lib/claw/console/views/memory.erb +13 -0
  36. data/lib/claw/console/views/monitor.erb +15 -0
  37. data/lib/claw/console/views/prompt.erb +15 -0
  38. data/lib/claw/console/views/snapshots.erb +12 -0
  39. data/lib/claw/console/views/tools.erb +13 -0
  40. data/lib/claw/console/views/traces.erb +9 -0
  41. data/lib/claw/console.rb +5 -0
  42. data/lib/claw/evolution.rb +227 -0
  43. data/lib/claw/forge.rb +144 -0
  44. data/lib/claw/hub.rb +67 -0
  45. data/lib/claw/init.rb +199 -0
  46. data/lib/claw/knowledge.rb +36 -2
  47. data/lib/claw/memory_store.rb +2 -2
  48. data/lib/claw/plan_mode.rb +110 -0
  49. data/lib/claw/resource.rb +35 -0
  50. data/lib/claw/resources/binding_resource.rb +128 -0
  51. data/lib/claw/resources/context_resource.rb +73 -0
  52. data/lib/claw/resources/filesystem_resource.rb +107 -0
  53. data/lib/claw/resources/memory_resource.rb +74 -0
  54. data/lib/claw/resources/worktree_resource.rb +133 -0
  55. data/lib/claw/roles.rb +56 -0
  56. data/lib/claw/runtime.rb +189 -0
  57. data/lib/claw/serializer.rb +10 -7
  58. data/lib/claw/tool.rb +99 -0
  59. data/lib/claw/tool_index.rb +84 -0
  60. data/lib/claw/tool_registry.rb +100 -0
  61. data/lib/claw/trace.rb +86 -0
  62. data/lib/claw/tui/agent_executor.rb +92 -0
  63. data/lib/claw/tui/chat_panel.rb +81 -0
  64. data/lib/claw/tui/command_bar.rb +22 -0
  65. data/lib/claw/tui/file_card.rb +88 -0
  66. data/lib/claw/tui/folding.rb +80 -0
  67. data/lib/claw/tui/input_handler.rb +73 -0
  68. data/lib/claw/tui/layout.rb +34 -0
  69. data/lib/claw/tui/messages.rb +31 -0
  70. data/lib/claw/tui/model.rb +411 -0
  71. data/lib/claw/tui/object_explorer.rb +136 -0
  72. data/lib/claw/tui/status_bar.rb +30 -0
  73. data/lib/claw/tui/status_panel.rb +133 -0
  74. data/lib/claw/tui/styles.rb +58 -0
  75. data/lib/claw/tui/tui.rb +54 -0
  76. data/lib/claw/version.rb +1 -1
  77. data/lib/claw.rb +99 -1
  78. metadata +223 -7
data/lib/claw/config.rb CHANGED
@@ -5,7 +5,8 @@ module Claw
5
5
  # Set via Claw.configure { |c| ... }.
6
6
  class Config
7
7
  attr_accessor :memory_pressure, :memory_keep_recent, :compact_model,
8
- :on_compact, :persist_session, :memory_top_k
8
+ :on_compact, :persist_session, :memory_top_k,
9
+ :tools_dir, :hub_url, :console_port
9
10
 
10
11
  def initialize
11
12
  @memory_pressure = 0.7
@@ -14,6 +15,9 @@ module Claw
14
15
  @on_compact = nil
15
16
  @persist_session = true
16
17
  @memory_top_k = 10
18
+ @tools_dir = nil # auto-detect: .ruby-claw/tools/
19
+ @hub_url = nil # disabled by default
20
+ @console_port = 4567
17
21
  end
18
22
  end
19
23
  end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "fileutils"
5
+
6
+ module Claw
7
+ module Console
8
+ # Writes structured JSONL events to .ruby-claw/log/events.jsonl.
9
+ # Used by the console for real-time observability.
10
+ # Events are opt-in — silently dropped if log directory doesn't exist.
11
+ class EventLogger
12
+ attr_reader :log_path
13
+
14
+ def initialize(log_dir)
15
+ @log_dir = log_dir
16
+ @log_path = File.join(log_dir, "events.jsonl") if log_dir
17
+ @mutex = Mutex.new
18
+ end
19
+
20
+ # Emit a structured event.
21
+ #
22
+ # @param type [String] event type (e.g., "llm_call_start", "tool_call")
23
+ # @param data [Hash] event-specific data
24
+ def emit(type:, data: {})
25
+ return unless @log_dir && Dir.exist?(@log_dir)
26
+
27
+ event = {
28
+ timestamp: Time.now.iso8601(3),
29
+ type: type,
30
+ data: data
31
+ }
32
+
33
+ @mutex.synchronize do
34
+ File.open(@log_path, "a") { |f| f.puts(JSON.generate(event)) }
35
+ end
36
+ rescue => e
37
+ # Silently drop events on error
38
+ end
39
+
40
+ # Read events since a given timestamp.
41
+ #
42
+ # @param since [String, nil] ISO 8601 timestamp (nil = all events)
43
+ # @return [Array<Hash>] events
44
+ def tail(since: nil)
45
+ return [] unless @log_path && File.exist?(@log_path)
46
+
47
+ events = []
48
+ File.foreach(@log_path) do |line|
49
+ event = JSON.parse(line.strip, symbolize_names: true) rescue next
50
+ if since.nil? || event[:timestamp] > since
51
+ events << event
52
+ end
53
+ end
54
+ events
55
+ end
56
+
57
+ # Total event count.
58
+ def count
59
+ return 0 unless @log_path && File.exist?(@log_path)
60
+ File.foreach(@log_path).count
61
+ end
62
+
63
+ # Clear all events.
64
+ def clear!
65
+ File.write(@log_path, "") if @log_path && File.exist?(@log_path)
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,264 @@
1
+ // Claw Console — Client-side application
2
+ (function() {
3
+ "use strict";
4
+
5
+ // --- Utilities ---
6
+
7
+ function api(method, path, body) {
8
+ var opts = { method: method, headers: {} };
9
+ if (body) {
10
+ opts.headers["Content-Type"] = "application/json";
11
+ opts.body = JSON.stringify(body);
12
+ }
13
+ return fetch(path, opts).then(function(r) { return r.json(); });
14
+ }
15
+
16
+ function esc(str) {
17
+ var d = document.createElement("div");
18
+ d.textContent = str;
19
+ return d.innerHTML;
20
+ }
21
+
22
+ function timeStr(iso) {
23
+ if (!iso) return "—";
24
+ var d = new Date(iso);
25
+ return d.toLocaleTimeString();
26
+ }
27
+
28
+ // --- Highlight active nav ---
29
+
30
+ var path = window.location.pathname;
31
+ document.querySelectorAll(".nav-link").forEach(function(a) {
32
+ if (a.getAttribute("href") === path) a.classList.add("active");
33
+ });
34
+
35
+ // --- Dashboard ---
36
+
37
+ var statVersion = document.getElementById("stat-version");
38
+ if (statVersion) {
39
+ api("GET", "/api/status").then(function(d) {
40
+ document.getElementById("stat-version").textContent = "v" + d.version;
41
+ document.getElementById("stat-tools").textContent = d.tool_count;
42
+ document.getElementById("stat-memories").textContent = d.memory_count;
43
+ document.getElementById("stat-snapshots").textContent = d.snapshot_count;
44
+ document.getElementById("stat-events").textContent = d.event_count;
45
+ });
46
+ }
47
+
48
+ // --- Header status ---
49
+
50
+ var headerStatus = document.getElementById("header-status");
51
+ if (headerStatus) {
52
+ api("GET", "/api/status").then(function(d) {
53
+ headerStatus.textContent = "v" + d.version;
54
+ });
55
+ }
56
+
57
+ // --- Prompt Inspector ---
58
+
59
+ var promptArea = document.getElementById("prompt-template");
60
+ if (promptArea) {
61
+ api("GET", "/api/prompt").then(function(d) {
62
+ promptArea.value = d.template || "";
63
+ var container = document.getElementById("prompt-sections");
64
+ if (d.sections && d.sections.length) {
65
+ container.innerHTML = d.sections.map(function(s) {
66
+ return '<div class="section-item">' + esc(s) + '</div>';
67
+ }).join("");
68
+ } else {
69
+ container.innerHTML = '<p class="muted">No dynamic sections.</p>';
70
+ }
71
+ });
72
+ }
73
+
74
+ window.savePrompt = function() {
75
+ var content = document.getElementById("prompt-template").value;
76
+ api("POST", "/api/prompt", { content: content }).then(function() {
77
+ document.getElementById("prompt-status").textContent = "Saved!";
78
+ setTimeout(function() {
79
+ document.getElementById("prompt-status").textContent = "";
80
+ }, 2000);
81
+ });
82
+ };
83
+
84
+ // --- Monitor (SSE) ---
85
+
86
+ var eventStream = document.getElementById("event-stream");
87
+ if (eventStream) {
88
+ var eventCount = 0;
89
+ var source = new EventSource("/api/events");
90
+
91
+ source.onmessage = function(e) {
92
+ var evt = JSON.parse(e.data);
93
+ var filterEl = document.getElementById("event-filter");
94
+ var filter = filterEl ? filterEl.value : "";
95
+ if (filter && !evt.type.startsWith(filter)) return;
96
+
97
+ eventCount++;
98
+ var countEl = document.getElementById("event-count");
99
+ if (countEl) countEl.textContent = eventCount + " events";
100
+
101
+ if (eventCount === 1) eventStream.innerHTML = "";
102
+
103
+ var div = document.createElement("div");
104
+ div.className = "event-item";
105
+ div.innerHTML =
106
+ '<span class="event-time">' + timeStr(evt.timestamp) + '</span>' +
107
+ '<span class="event-type ' + esc(evt.type) + '">' + esc(evt.type) + '</span>' +
108
+ '<span class="event-data">' + esc(JSON.stringify(evt.data || {})) + '</span>';
109
+ eventStream.appendChild(div);
110
+
111
+ var autoScroll = document.getElementById("auto-scroll");
112
+ if (autoScroll && autoScroll.checked) {
113
+ eventStream.scrollTop = eventStream.scrollHeight;
114
+ }
115
+ };
116
+
117
+ source.onerror = function() {
118
+ if (eventCount === 0) {
119
+ eventStream.innerHTML = '<p class="muted">Event stream disconnected. Retrying...</p>';
120
+ }
121
+ };
122
+ }
123
+
124
+ // --- Traces ---
125
+
126
+ var traceList = document.getElementById("trace-list");
127
+ if (traceList) {
128
+ api("GET", "/api/traces").then(function(traces) {
129
+ if (!traces.length) {
130
+ traceList.innerHTML = '<p class="muted" style="padding:0.75rem">No traces found.</p>';
131
+ return;
132
+ }
133
+ traceList.innerHTML = traces.map(function(t) {
134
+ return '<div class="trace-entry" data-id="' + esc(t.id) + '">' +
135
+ '<div class="trace-entry-id">' + esc(t.id) + '</div>' +
136
+ '<div class="trace-entry-meta">' + esc(t.modified) + '</div>' +
137
+ '</div>';
138
+ }).join("");
139
+
140
+ traceList.querySelectorAll(".trace-entry").forEach(function(el) {
141
+ el.addEventListener("click", function() {
142
+ traceList.querySelectorAll(".trace-entry").forEach(function(e) { e.classList.remove("active"); });
143
+ el.classList.add("active");
144
+ loadTrace(el.dataset.id);
145
+ });
146
+ });
147
+ });
148
+ }
149
+
150
+ function loadTrace(id) {
151
+ var detail = document.getElementById("trace-detail");
152
+ detail.innerHTML = '<p class="muted">Loading...</p>';
153
+ api("GET", "/api/traces/" + encodeURIComponent(id)).then(function(d) {
154
+ detail.textContent = d.content || "Empty trace.";
155
+ });
156
+ }
157
+
158
+ // --- Memory ---
159
+
160
+ var memoryBody = document.getElementById("memory-body");
161
+ if (memoryBody) {
162
+ loadMemories();
163
+ }
164
+
165
+ function loadMemories() {
166
+ api("GET", "/api/memory").then(function(mems) {
167
+ var body = document.getElementById("memory-body");
168
+ if (!mems || !mems.length) {
169
+ body.innerHTML = '<tr><td colspan="4" class="muted">No memories stored.</td></tr>';
170
+ return;
171
+ }
172
+ body.innerHTML = mems.map(function(m) {
173
+ return '<tr>' +
174
+ '<td>' + esc(String(m.id)) + '</td>' +
175
+ '<td>' + esc(m.content) + '</td>' +
176
+ '<td>' + esc(m.created_at || "—") + '</td>' +
177
+ '<td><button class="btn btn-sm btn-danger" onclick="forgetMemory(' + m.id + ')">Forget</button></td>' +
178
+ '</tr>';
179
+ }).join("");
180
+ });
181
+ }
182
+
183
+ window.addMemory = function() {
184
+ var input = document.getElementById("memory-input");
185
+ var content = input.value.trim();
186
+ if (!content) return;
187
+ api("POST", "/api/memory", { content: content }).then(function() {
188
+ input.value = "";
189
+ loadMemories();
190
+ });
191
+ };
192
+
193
+ window.forgetMemory = function(id) {
194
+ api("DELETE", "/api/memory/" + id).then(function() { loadMemories(); });
195
+ };
196
+
197
+ // --- Tools ---
198
+
199
+ var coreTools = document.getElementById("core-tools");
200
+ if (coreTools) {
201
+ api("GET", "/api/tools").then(function(d) {
202
+ coreTools.innerHTML = (d.core || []).map(function(t) {
203
+ return '<div class="tool-card"><div><div class="tool-name">' + esc(t.name) + '</div>' +
204
+ '<div class="tool-desc">' + esc(t.description || "") + '</div></div>' +
205
+ '<span class="tool-badge loaded">core</span></div>';
206
+ }).join("") || '<p class="muted">No core tools.</p>';
207
+
208
+ var projectTools = document.getElementById("project-tools");
209
+ projectTools.innerHTML = (d.project || []).map(function(t) {
210
+ var badge = t.loaded
211
+ ? '<span class="tool-badge loaded">loaded</span>'
212
+ : '<button class="btn btn-sm" onclick="loadTool(\'' + esc(t.name) + '\')">Load</button>';
213
+ return '<div class="tool-card"><div><div class="tool-name">' + esc(t.name) + '</div>' +
214
+ '<div class="tool-desc">' + esc(t.description || "") + '</div></div>' + badge + '</div>';
215
+ }).join("") || '<p class="muted">No project tools. Create tools in .ruby-claw/tools/</p>';
216
+ });
217
+ }
218
+
219
+ window.loadTool = function(name) {
220
+ api("POST", "/api/tools/load", { name: name }).then(function() {
221
+ window.location.reload();
222
+ });
223
+ };
224
+
225
+ // --- Snapshots ---
226
+
227
+ var snapshotBody = document.getElementById("snapshot-body");
228
+ if (snapshotBody) {
229
+ loadSnapshots();
230
+ }
231
+
232
+ function loadSnapshots() {
233
+ api("GET", "/api/snapshots").then(function(snaps) {
234
+ var body = document.getElementById("snapshot-body");
235
+ if (!snaps || !snaps.length) {
236
+ body.innerHTML = '<tr><td colspan="4" class="muted">No snapshots.</td></tr>';
237
+ return;
238
+ }
239
+ body.innerHTML = snaps.map(function(s) {
240
+ return '<tr>' +
241
+ '<td>#' + esc(String(s.id)) + '</td>' +
242
+ '<td>' + esc(s.label || "(unlabeled)") + '</td>' +
243
+ '<td>' + esc(s.timestamp || "—") + '</td>' +
244
+ '<td><button class="btn btn-sm btn-secondary" onclick="rollbackSnapshot(' + s.id + ')">Rollback</button></td>' +
245
+ '</tr>';
246
+ }).join("");
247
+ });
248
+ }
249
+
250
+ window.createSnapshot = function() {
251
+ api("POST", "/api/snapshots").then(function() { loadSnapshots(); });
252
+ };
253
+
254
+ window.rollbackSnapshot = function(id) {
255
+ if (!confirm("Rollback to snapshot #" + id + "?")) return;
256
+ api("POST", "/api/snapshots/" + id + "/rollback").then(function() { loadSnapshots(); });
257
+ };
258
+
259
+ // --- Experiments (placeholder) ---
260
+
261
+ window.newExperiment = function() {
262
+ alert("Experiment platform coming in Sprint 15.");
263
+ };
264
+ })();
@@ -0,0 +1,330 @@
1
+ /* Claw Console — Dark monospace theme */
2
+ :root {
3
+ --bg: #0d1117;
4
+ --bg-card: #161b22;
5
+ --bg-input: #0d1117;
6
+ --fg: #c9d1d9;
7
+ --fg-muted: #8b949e;
8
+ --accent: #58a6ff;
9
+ --green: #3fb950;
10
+ --red: #f85149;
11
+ --orange: #d29922;
12
+ --border: #30363d;
13
+ --radius: 6px;
14
+ }
15
+
16
+ * { box-sizing: border-box; margin: 0; padding: 0; }
17
+
18
+ body {
19
+ font-family: "SF Mono", "Fira Code", "Cascadia Code", monospace;
20
+ font-size: 13px;
21
+ background: var(--bg);
22
+ color: var(--fg);
23
+ line-height: 1.5;
24
+ }
25
+
26
+ /* Header */
27
+ header {
28
+ display: flex;
29
+ align-items: center;
30
+ gap: 1.5rem;
31
+ padding: 0.75rem 1.5rem;
32
+ border-bottom: 1px solid var(--border);
33
+ background: var(--bg-card);
34
+ }
35
+
36
+ .logo {
37
+ font-weight: 700;
38
+ font-size: 16px;
39
+ color: var(--accent);
40
+ letter-spacing: 1px;
41
+ }
42
+
43
+ nav { display: flex; gap: 0.25rem; }
44
+
45
+ .nav-link {
46
+ color: var(--fg-muted);
47
+ text-decoration: none;
48
+ padding: 0.35rem 0.65rem;
49
+ border-radius: var(--radius);
50
+ transition: background 0.15s, color 0.15s;
51
+ }
52
+
53
+ .nav-link:hover, .nav-link.active {
54
+ color: var(--fg);
55
+ background: var(--border);
56
+ }
57
+
58
+ .header-status {
59
+ margin-left: auto;
60
+ color: var(--fg-muted);
61
+ font-size: 11px;
62
+ }
63
+
64
+ /* Main */
65
+ main {
66
+ max-width: 1200px;
67
+ margin: 0 auto;
68
+ padding: 1.5rem;
69
+ }
70
+
71
+ h1 { font-size: 20px; margin-bottom: 1rem; color: var(--fg); }
72
+ h2 { font-size: 15px; margin: 1.25rem 0 0.5rem; color: var(--fg-muted); }
73
+
74
+ .muted { color: var(--fg-muted); }
75
+
76
+ /* Cards */
77
+ .cards {
78
+ display: grid;
79
+ grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
80
+ gap: 0.75rem;
81
+ margin-bottom: 1.5rem;
82
+ }
83
+
84
+ .card {
85
+ background: var(--bg-card);
86
+ border: 1px solid var(--border);
87
+ border-radius: var(--radius);
88
+ padding: 1rem;
89
+ }
90
+
91
+ .card-label {
92
+ font-size: 11px;
93
+ color: var(--fg-muted);
94
+ text-transform: uppercase;
95
+ letter-spacing: 0.5px;
96
+ }
97
+
98
+ .card-value {
99
+ font-size: 22px;
100
+ font-weight: 600;
101
+ margin-top: 0.25rem;
102
+ }
103
+
104
+ /* Tables */
105
+ .data-table {
106
+ width: 100%;
107
+ border-collapse: collapse;
108
+ margin-top: 0.5rem;
109
+ }
110
+
111
+ .data-table th, .data-table td {
112
+ text-align: left;
113
+ padding: 0.5rem 0.75rem;
114
+ border-bottom: 1px solid var(--border);
115
+ }
116
+
117
+ .data-table th {
118
+ font-size: 11px;
119
+ color: var(--fg-muted);
120
+ text-transform: uppercase;
121
+ }
122
+
123
+ .data-table tr:hover td { background: var(--bg-card); }
124
+
125
+ /* Buttons */
126
+ .btn {
127
+ background: var(--accent);
128
+ color: #000;
129
+ border: none;
130
+ padding: 0.4rem 0.85rem;
131
+ border-radius: var(--radius);
132
+ cursor: pointer;
133
+ font-family: inherit;
134
+ font-size: 12px;
135
+ font-weight: 600;
136
+ }
137
+
138
+ .btn:hover { opacity: 0.9; }
139
+
140
+ .btn-sm { padding: 0.25rem 0.5rem; font-size: 11px; }
141
+
142
+ .btn-danger { background: var(--red); }
143
+
144
+ .btn-secondary {
145
+ background: transparent;
146
+ color: var(--fg-muted);
147
+ border: 1px solid var(--border);
148
+ }
149
+
150
+ /* Inputs */
151
+ .text-input {
152
+ background: var(--bg-input);
153
+ color: var(--fg);
154
+ border: 1px solid var(--border);
155
+ padding: 0.4rem 0.65rem;
156
+ border-radius: var(--radius);
157
+ font-family: inherit;
158
+ font-size: 13px;
159
+ width: 300px;
160
+ }
161
+
162
+ .text-input:focus { outline: none; border-color: var(--accent); }
163
+
164
+ textarea {
165
+ background: var(--bg-input);
166
+ color: var(--fg);
167
+ border: 1px solid var(--border);
168
+ border-radius: var(--radius);
169
+ font-family: inherit;
170
+ font-size: 13px;
171
+ width: 100%;
172
+ padding: 0.75rem;
173
+ resize: vertical;
174
+ }
175
+
176
+ textarea:focus { outline: none; border-color: var(--accent); }
177
+
178
+ /* Prompt */
179
+ .prompt-section { margin-bottom: 1.5rem; }
180
+
181
+ .prompt-editor { display: flex; flex-direction: column; gap: 0.5rem; }
182
+
183
+ .section-list .section-item {
184
+ background: var(--bg-card);
185
+ border: 1px solid var(--border);
186
+ border-radius: var(--radius);
187
+ padding: 0.75rem;
188
+ margin-bottom: 0.5rem;
189
+ white-space: pre-wrap;
190
+ font-size: 12px;
191
+ }
192
+
193
+ /* Monitor */
194
+ .monitor-controls {
195
+ display: flex;
196
+ align-items: center;
197
+ gap: 1rem;
198
+ margin-bottom: 1rem;
199
+ padding: 0.5rem 0;
200
+ }
201
+
202
+ .monitor-controls select {
203
+ background: var(--bg-input);
204
+ color: var(--fg);
205
+ border: 1px solid var(--border);
206
+ padding: 0.3rem 0.5rem;
207
+ border-radius: var(--radius);
208
+ font-family: inherit;
209
+ font-size: 12px;
210
+ }
211
+
212
+ .event-stream, .event-feed {
213
+ background: var(--bg-card);
214
+ border: 1px solid var(--border);
215
+ border-radius: var(--radius);
216
+ padding: 0.5rem;
217
+ max-height: 600px;
218
+ overflow-y: auto;
219
+ }
220
+
221
+ .event-item {
222
+ padding: 0.35rem 0.5rem;
223
+ border-bottom: 1px solid var(--border);
224
+ font-size: 12px;
225
+ display: flex;
226
+ gap: 0.75rem;
227
+ }
228
+
229
+ .event-item:last-child { border-bottom: none; }
230
+
231
+ .event-time { color: var(--fg-muted); flex-shrink: 0; width: 85px; }
232
+
233
+ .event-type {
234
+ font-weight: 600;
235
+ flex-shrink: 0;
236
+ width: 120px;
237
+ }
238
+
239
+ .event-type.llm_call { color: var(--accent); }
240
+ .event-type.tool_call { color: var(--green); }
241
+ .event-type.snapshot { color: var(--orange); }
242
+ .event-type.trace { color: #bc8cff; }
243
+
244
+ .event-data { color: var(--fg-muted); overflow: hidden; text-overflow: ellipsis; }
245
+
246
+ /* Traces */
247
+ .trace-layout {
248
+ display: grid;
249
+ grid-template-columns: 280px 1fr;
250
+ gap: 1rem;
251
+ min-height: 500px;
252
+ }
253
+
254
+ .trace-list {
255
+ background: var(--bg-card);
256
+ border: 1px solid var(--border);
257
+ border-radius: var(--radius);
258
+ overflow-y: auto;
259
+ max-height: 600px;
260
+ }
261
+
262
+ .trace-entry {
263
+ padding: 0.5rem 0.75rem;
264
+ border-bottom: 1px solid var(--border);
265
+ cursor: pointer;
266
+ font-size: 12px;
267
+ }
268
+
269
+ .trace-entry:hover { background: var(--border); }
270
+ .trace-entry.active { background: var(--border); border-left: 2px solid var(--accent); }
271
+
272
+ .trace-entry-id { font-weight: 600; }
273
+ .trace-entry-meta { color: var(--fg-muted); font-size: 11px; }
274
+
275
+ .trace-detail {
276
+ background: var(--bg-card);
277
+ border: 1px solid var(--border);
278
+ border-radius: var(--radius);
279
+ padding: 1rem;
280
+ overflow-y: auto;
281
+ max-height: 600px;
282
+ white-space: pre-wrap;
283
+ font-size: 12px;
284
+ }
285
+
286
+ /* Tools */
287
+ .tools-section { margin-bottom: 1.5rem; }
288
+
289
+ .tool-grid {
290
+ display: grid;
291
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
292
+ gap: 0.5rem;
293
+ }
294
+
295
+ .tool-card {
296
+ background: var(--bg-card);
297
+ border: 1px solid var(--border);
298
+ border-radius: var(--radius);
299
+ padding: 0.75rem;
300
+ display: flex;
301
+ justify-content: space-between;
302
+ align-items: flex-start;
303
+ }
304
+
305
+ .tool-name { font-weight: 600; font-size: 13px; }
306
+ .tool-desc { color: var(--fg-muted); font-size: 11px; margin-top: 0.2rem; }
307
+
308
+ .tool-badge {
309
+ font-size: 10px;
310
+ padding: 0.15rem 0.4rem;
311
+ border-radius: 3px;
312
+ font-weight: 600;
313
+ }
314
+
315
+ .tool-badge.loaded { background: var(--green); color: #000; }
316
+ .tool-badge.available { background: var(--border); color: var(--fg-muted); }
317
+
318
+ /* Memory / Snapshot controls */
319
+ .memory-controls, .snapshot-controls, .experiment-controls {
320
+ display: flex;
321
+ gap: 0.5rem;
322
+ align-items: center;
323
+ margin-bottom: 1rem;
324
+ }
325
+
326
+ /* Scrollbar */
327
+ ::-webkit-scrollbar { width: 6px; }
328
+ ::-webkit-scrollbar-track { background: transparent; }
329
+ ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
330
+ ::-webkit-scrollbar-thumb:hover { background: var(--fg-muted); }