@jungjaehoon/mama-os 0.33.0 → 0.34.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.
- package/README.md +15 -17
- package/dist/agent/os-agent-capabilities.md +2 -2
- package/dist/api/graph-api.d.ts +4 -4
- package/dist/api/graph-api.d.ts.map +1 -1
- package/dist/api/graph-api.js +47 -78
- package/dist/api/graph-api.js.map +1 -1
- package/dist/api/index.d.ts +10 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +9 -2
- package/dist/api/index.js.map +1 -1
- package/dist/api/operator-handler.d.ts +1 -1
- package/dist/api/operator-handler.js +1 -1
- package/dist/api/runtime-status-handler.d.ts +41 -0
- package/dist/api/runtime-status-handler.d.ts.map +1 -0
- package/dist/api/runtime-status-handler.js +55 -0
- package/dist/api/runtime-status-handler.js.map +1 -0
- package/dist/cli/commands/start.js +1 -1
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/cli/runtime/api-routes-init.js +1 -1
- package/dist/cli/runtime/api-routes-init.js.map +1 -1
- package/dist/cli/runtime/api-server-init.d.ts +12 -1
- package/dist/cli/runtime/api-server-init.d.ts.map +1 -1
- package/dist/cli/runtime/api-server-init.js +54 -0
- package/dist/cli/runtime/api-server-init.js.map +1 -1
- package/dist/cli/runtime/utilities.d.ts +1 -1
- package/dist/cli/runtime/utilities.d.ts.map +1 -1
- package/dist/cli/runtime/utilities.js +3 -1
- package/dist/cli/runtime/utilities.js.map +1 -1
- package/dist/multi-agent/dashboard-agent-persona.js +2 -2
- package/dist/operator/board-slot-instructions.d.ts +1 -1
- package/dist/operator/board-slot-instructions.js +1 -1
- package/package.json +1 -2
- package/public/viewer/js/modules/system.js +183 -0
- package/public/viewer/js/utils/ui-commands.js +4 -9
- package/public/viewer/log-viewer.html +2 -192
- package/public/viewer/operator/operator.css +1 -0
- package/public/viewer/operator/operator.js +14830 -0
- package/public/viewer/src/modules/system.ts +242 -0
- package/public/viewer/src/utils/ui-commands.ts +4 -8
- package/public/viewer/sw.js +44 -10
- package/public/viewer/viewer.css +338 -331
- package/public/viewer/viewer.html +388 -1472
- package/public/ui/assets/index-BDXDGEV6.js +0 -62
- package/public/ui/assets/index-po2tN0xq.css +0 -1
- package/public/ui/index.html +0 -14
- package/public/ui/theme-init.js +0 -20
- package/public/viewer/js/modules/agents.js +0 -1153
- package/public/viewer/js/modules/chat.js +0 -1993
- package/public/viewer/js/modules/connector-feed.js +0 -352
- package/public/viewer/js/modules/dashboard.js +0 -456
- package/public/viewer/js/modules/entity-audit.js +0 -106
- package/public/viewer/js/modules/entity-review.js +0 -66
- package/public/viewer/js/modules/settings.js +0 -1257
- package/public/viewer/src/modules/agents.ts +0 -1302
- package/public/viewer/src/modules/chat.ts +0 -2426
- package/public/viewer/src/modules/connector-feed.ts +0 -391
- package/public/viewer/src/modules/dashboard.ts +0 -498
- package/public/viewer/src/modules/entity-audit.ts +0 -154
- package/public/viewer/src/modules/entity-review.ts +0 -175
- package/public/viewer/src/modules/settings.ts +0 -1547
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* System views: Runtime and Connectors.
|
|
3
|
+
* @module modules/system
|
|
4
|
+
*
|
|
5
|
+
* Read-only truth, one source. Both views render `GET /api/runtime/status` and
|
|
6
|
+
* nothing else: no config read, no client-side model registry, no credential
|
|
7
|
+
* ever reaching the DOM. Editing daemon configuration from the browser is not a
|
|
8
|
+
* capability of this surface; setup is a CLI action.
|
|
9
|
+
*/
|
|
10
|
+
/* eslint-env browser */
|
|
11
|
+
const RUNTIME_STATUS_ENDPOINT = '/api/runtime/status';
|
|
12
|
+
/** Shown when the daemon booted without a health service. Never a fake score. */
|
|
13
|
+
const HEALTH_UNAVAILABLE = 'unavailable';
|
|
14
|
+
export function escapeHtml(value) {
|
|
15
|
+
return String(value ?? '')
|
|
16
|
+
.replace(/&/g, '&')
|
|
17
|
+
.replace(/</g, '<')
|
|
18
|
+
.replace(/>/g, '>')
|
|
19
|
+
.replace(/"/g, '"')
|
|
20
|
+
.replace(/'/g, ''');
|
|
21
|
+
}
|
|
22
|
+
async function defaultFetchStatus() {
|
|
23
|
+
const response = await fetch(RUNTIME_STATUS_ENDPOINT);
|
|
24
|
+
if (!response.ok) {
|
|
25
|
+
throw new Error('HTTP ' + response.status);
|
|
26
|
+
}
|
|
27
|
+
return (await response.json());
|
|
28
|
+
}
|
|
29
|
+
function skeleton(rows) {
|
|
30
|
+
let html = '<div class="mama-system-skeleton" aria-busy="true" aria-live="polite">';
|
|
31
|
+
for (let i = 0; i < rows; i++) {
|
|
32
|
+
html += '<div class="mama-system-skeleton-row"></div>';
|
|
33
|
+
}
|
|
34
|
+
html += '</div>';
|
|
35
|
+
return html;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Failure names the endpoint that failed and offers a retry. A System view that
|
|
39
|
+
* silently shows nothing is indistinguishable from a healthy empty runtime.
|
|
40
|
+
*/
|
|
41
|
+
function errorCard(error) {
|
|
42
|
+
const detail = error instanceof Error ? error.message : String(error ?? 'Unknown error');
|
|
43
|
+
return ('<div class="mama-system-error" role="alert">' +
|
|
44
|
+
'<p class="mama-system-error-title">Could not read ' +
|
|
45
|
+
escapeHtml(RUNTIME_STATUS_ENDPOINT) +
|
|
46
|
+
'</p>' +
|
|
47
|
+
'<p class="mama-system-error-detail">' +
|
|
48
|
+
escapeHtml(detail) +
|
|
49
|
+
'</p>' +
|
|
50
|
+
'<button type="button" class="mama-system-retry" data-retry>Retry</button>' +
|
|
51
|
+
'</div>');
|
|
52
|
+
}
|
|
53
|
+
function formatStartedAt(startedAt) {
|
|
54
|
+
if (!Number.isFinite(startedAt) || startedAt <= 0)
|
|
55
|
+
return 'unknown';
|
|
56
|
+
return new Date(startedAt).toLocaleString();
|
|
57
|
+
}
|
|
58
|
+
function formatUptime(startedAt) {
|
|
59
|
+
if (!Number.isFinite(startedAt) || startedAt <= 0)
|
|
60
|
+
return 'unknown';
|
|
61
|
+
const seconds = Math.max(0, Math.floor((Date.now() - startedAt) / 1000));
|
|
62
|
+
const days = Math.floor(seconds / 86400);
|
|
63
|
+
const hours = Math.floor((seconds % 86400) / 3600);
|
|
64
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
65
|
+
if (days > 0)
|
|
66
|
+
return days + 'd ' + hours + 'h';
|
|
67
|
+
if (hours > 0)
|
|
68
|
+
return hours + 'h ' + minutes + 'm';
|
|
69
|
+
return minutes + 'm';
|
|
70
|
+
}
|
|
71
|
+
function statTile(label, value, tone = 'neutral') {
|
|
72
|
+
return ('<div class="mama-system-tile" data-tone="' +
|
|
73
|
+
escapeHtml(tone) +
|
|
74
|
+
'">' +
|
|
75
|
+
'<span class="mama-system-tile-label">' +
|
|
76
|
+
escapeHtml(label) +
|
|
77
|
+
'</span>' +
|
|
78
|
+
'<span class="mama-system-tile-value">' +
|
|
79
|
+
escapeHtml(value) +
|
|
80
|
+
'</span>' +
|
|
81
|
+
'</div>');
|
|
82
|
+
}
|
|
83
|
+
export function renderRuntimeSnapshot(snapshot) {
|
|
84
|
+
const health = snapshot.health;
|
|
85
|
+
const healthValue = health ? health.status + ' - ' + health.score : HEALTH_UNAVAILABLE;
|
|
86
|
+
const healthTone = health
|
|
87
|
+
? health.status === 'healthy'
|
|
88
|
+
? 'ok'
|
|
89
|
+
: health.status === 'degraded'
|
|
90
|
+
? 'warn'
|
|
91
|
+
: 'bad'
|
|
92
|
+
: 'muted';
|
|
93
|
+
return ('<section class="mama-system-panel">' +
|
|
94
|
+
'<h2 class="mama-system-heading">Runtime</h2>' +
|
|
95
|
+
'<p class="mama-system-note">Reported by the running daemon; some fields reflect boot-time state. Configuration is edited via the CLI or the config file.</p>' +
|
|
96
|
+
'<div class="mama-system-tiles">' +
|
|
97
|
+
statTile('Daemon', snapshot.running ? 'running' : 'stopped', snapshot.running ? 'ok' : 'bad') +
|
|
98
|
+
statTile('Backend', snapshot.backend) +
|
|
99
|
+
statTile('Model', snapshot.model) +
|
|
100
|
+
statTile('Health', healthValue, healthTone) +
|
|
101
|
+
statTile('Uptime', formatUptime(snapshot.startedAt)) +
|
|
102
|
+
statTile('Started', formatStartedAt(snapshot.startedAt)) +
|
|
103
|
+
'</div>' +
|
|
104
|
+
'</section>');
|
|
105
|
+
}
|
|
106
|
+
const CONNECTOR_STATE_TONE = {
|
|
107
|
+
connected: 'ok',
|
|
108
|
+
disconnected: 'muted',
|
|
109
|
+
unknown: 'warn',
|
|
110
|
+
};
|
|
111
|
+
/**
|
|
112
|
+
* What a connector state actually means. 'connected' is not a live probe: it
|
|
113
|
+
* says the connector registered when the daemon booted, so the chip carries the
|
|
114
|
+
* narrower claim rather than letting the word oversell itself.
|
|
115
|
+
*/
|
|
116
|
+
const CONNECTOR_STATE_TITLE = {
|
|
117
|
+
connected: 'Registered at daemon boot',
|
|
118
|
+
disconnected: 'Disabled in config',
|
|
119
|
+
unknown: 'Enabled in config but not registered at boot',
|
|
120
|
+
};
|
|
121
|
+
export function renderConnectorsSnapshot(snapshot) {
|
|
122
|
+
const connectors = snapshot.connectors ?? [];
|
|
123
|
+
if (connectors.length === 0) {
|
|
124
|
+
return ('<section class="mama-system-panel">' +
|
|
125
|
+
'<h2 class="mama-system-heading">Connectors</h2>' +
|
|
126
|
+
'<div class="mama-system-empty">' +
|
|
127
|
+
'<p class="mama-system-empty-title">No connectors configured</p>' +
|
|
128
|
+
'<p class="mama-system-empty-detail">Add one from a terminal:</p>' +
|
|
129
|
+
'<code class="mama-system-code">mama connector add <name></code>' +
|
|
130
|
+
'</div>' +
|
|
131
|
+
'</section>');
|
|
132
|
+
}
|
|
133
|
+
let rows = '';
|
|
134
|
+
for (const connector of connectors) {
|
|
135
|
+
rows +=
|
|
136
|
+
'<li class="mama-system-row" data-tone="' +
|
|
137
|
+
escapeHtml(CONNECTOR_STATE_TONE[connector.state] ?? 'muted') +
|
|
138
|
+
'">' +
|
|
139
|
+
'<span class="mama-system-dot"></span>' +
|
|
140
|
+
'<span class="mama-system-row-name">' +
|
|
141
|
+
escapeHtml(connector.name) +
|
|
142
|
+
'</span>' +
|
|
143
|
+
'<span class="mama-system-row-state" title="' +
|
|
144
|
+
escapeHtml(CONNECTOR_STATE_TITLE[connector.state] ?? 'State reported at daemon boot') +
|
|
145
|
+
'">' +
|
|
146
|
+
escapeHtml(connector.state) +
|
|
147
|
+
'</span>' +
|
|
148
|
+
(connector.enabled ? '' : '<span class="mama-system-badge">off</span>') +
|
|
149
|
+
'</li>';
|
|
150
|
+
}
|
|
151
|
+
return ('<section class="mama-system-panel">' +
|
|
152
|
+
'<h2 class="mama-system-heading">Connectors</h2>' +
|
|
153
|
+
'<p class="mama-system-note">Read-only. Add or remove connectors with <code>mama connector add</code>.</p>' +
|
|
154
|
+
'<ul class="mama-system-list">' +
|
|
155
|
+
rows +
|
|
156
|
+
'</ul>' +
|
|
157
|
+
'</section>');
|
|
158
|
+
}
|
|
159
|
+
async function renderView(container, options, toHtml, skeletonRows) {
|
|
160
|
+
if (!container)
|
|
161
|
+
return;
|
|
162
|
+
const fetchStatus = options.fetchStatus ?? defaultFetchStatus;
|
|
163
|
+
container.innerHTML = skeleton(skeletonRows);
|
|
164
|
+
try {
|
|
165
|
+
const snapshot = await fetchStatus();
|
|
166
|
+
container.innerHTML = toHtml(snapshot);
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
container.innerHTML = errorCard(error);
|
|
170
|
+
const retry = container.querySelector('[data-retry]');
|
|
171
|
+
if (retry) {
|
|
172
|
+
retry.addEventListener('click', () => {
|
|
173
|
+
void renderView(container, options, toHtml, skeletonRows);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
export function renderRuntimeView(container, options = {}) {
|
|
179
|
+
return renderView(container, options, renderRuntimeSnapshot, 3);
|
|
180
|
+
}
|
|
181
|
+
export function renderConnectorsView(container, options = {}) {
|
|
182
|
+
return renderView(container, options, renderConnectorsSnapshot, 5);
|
|
183
|
+
}
|
|
@@ -14,16 +14,11 @@ const VIEWER_FRONTDOOR_CHANNEL_ID = 'mama_os_main';
|
|
|
14
14
|
const PAGE_CONTEXT_REPUBLISH_MS = 5000;
|
|
15
15
|
let lastReportedPageContext = null;
|
|
16
16
|
let lastPageContextPublishAt = 0;
|
|
17
|
+
// The Viewer always reports into the one frontdoor channel. The per-chat
|
|
18
|
+
// session override this used to read is gone with the Chat tab that wrote it:
|
|
19
|
+
// nothing writes `mama_chat_session_id` any more, so reading it could only ever
|
|
20
|
+
// return a stale value.
|
|
17
21
|
function getViewerChannelId() {
|
|
18
|
-
try {
|
|
19
|
-
const sessionId = window.localStorage.getItem('mama_chat_session_id');
|
|
20
|
-
if (sessionId && !sessionId.startsWith('session_')) {
|
|
21
|
-
return sessionId;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
catch {
|
|
25
|
-
/* ignore */
|
|
26
|
-
}
|
|
27
22
|
return VIEWER_FRONTDOOR_CHANNEL_ID;
|
|
28
23
|
}
|
|
29
24
|
export function startUICommandPolling(switchTab) {
|
|
@@ -14,7 +14,6 @@ body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;backgrou
|
|
|
14
14
|
.status-dot.connected{background:var(--success);box-shadow:0 0 6px var(--success)}
|
|
15
15
|
.status-dot.disconnected{background:var(--error)}
|
|
16
16
|
.status-dot.connecting{background:var(--warn);animation:pulse 1s infinite}
|
|
17
|
-
.status-dot.ws-connected{background:#39d353;box-shadow:0 0 8px #39d353}
|
|
18
17
|
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.3}}
|
|
19
18
|
@keyframes fadeNew{from{background:rgba(59,185,80,.18)}to{background:transparent}}
|
|
20
19
|
.status-text{font-size:11px;color:var(--muted);min-width:100px}
|
|
@@ -24,7 +23,6 @@ body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;backgrou
|
|
|
24
23
|
.btn:hover{border-color:var(--accent);color:var(--accent)}
|
|
25
24
|
.btn.active{background:var(--accent);color:#fff;border-color:var(--accent)}
|
|
26
25
|
.btn.paused{background:var(--warn);border-color:var(--warn);color:#000}
|
|
27
|
-
.btn.ws-mode{background:#39d353;border-color:#39d353;color:#000}
|
|
28
26
|
.sel-ctrl{background:var(--bg);border:1px solid var(--border);border-radius:5px;padding:3px 5px;color:var(--text);font-size:11px;font-family:inherit;outline:none}
|
|
29
27
|
.toolbar{display:flex;align-items:center;gap:6px;padding:5px 14px;background:var(--surface);border-bottom:1px solid var(--border);flex-shrink:0;flex-wrap:wrap}
|
|
30
28
|
.search-box{flex:1;min-width:120px;display:flex;align-items:center;gap:5px;background:var(--bg);border:1px solid var(--border);border-radius:5px;padding:3px 7px}
|
|
@@ -104,8 +102,6 @@ body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;backgrou
|
|
|
104
102
|
.selection-bar .sel-count{font-weight:600}
|
|
105
103
|
.selection-bar button{padding:3px 10px;font-size:11px;border:1px solid rgba(255,255,255,.3);background:rgba(255,255,255,.1);color:#fff;border-radius:6px;cursor:pointer}
|
|
106
104
|
.selection-bar button:hover{background:rgba(255,255,255,.25)}
|
|
107
|
-
.selection-bar .send-btn{background:#fff;color:var(--accent);font-weight:600;border:none}
|
|
108
|
-
.selection-bar .send-btn:hover{background:#e6edf3}
|
|
109
105
|
|
|
110
106
|
/* Log area + minimap */
|
|
111
107
|
.log-wrap{flex:1;overflow:hidden;display:flex;flex-direction:column}
|
|
@@ -147,26 +143,6 @@ body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;backgrou
|
|
|
147
143
|
.empty .ico{font-size:36px;opacity:.25}
|
|
148
144
|
.empty p{font-size:11px}
|
|
149
145
|
.empty kbd{background:var(--surface);border:1px solid var(--border);border-radius:3px;padding:1px 5px;font-size:10px}
|
|
150
|
-
.prompt-panel{border-top:1px solid var(--border);background:var(--surface);flex-shrink:0}
|
|
151
|
-
.prompt-hdr{display:flex;align-items:center;justify-content:space-between;padding:6px 14px;cursor:pointer;user-select:none}
|
|
152
|
-
.prompt-hdr h3{font-size:11px;color:var(--accent);font-weight:600}
|
|
153
|
-
.prompt-hdr .arrow{font-size:10px;color:var(--muted);transition:transform .2s}
|
|
154
|
-
.prompt-hdr .arrow.open{transform:rotate(180deg)}
|
|
155
|
-
.prompt-body{padding:0 14px 8px;display:none}
|
|
156
|
-
.prompt-body.open{display:block}
|
|
157
|
-
.presets{display:flex;gap:3px;flex-wrap:wrap;margin-bottom:6px}
|
|
158
|
-
.preset{padding:2px 8px;font-size:10px;background:var(--bg);border:1px solid var(--border);border-radius:10px;color:var(--muted);cursor:pointer;transition:all .12s;font-family:inherit}
|
|
159
|
-
.preset:hover{border-color:var(--accent);color:var(--accent)}
|
|
160
|
-
.prompt-row{display:flex;gap:6px}
|
|
161
|
-
.prompt-input{flex:1;background:var(--bg);border:1px solid var(--border);border-radius:6px;padding:6px 10px;color:var(--text);font-size:11px;font-family:inherit;resize:none;outline:none;min-height:40px;max-height:80px}
|
|
162
|
-
.prompt-input:focus{border-color:var(--accent)}
|
|
163
|
-
.prompt-input::placeholder{color:var(--muted)}
|
|
164
|
-
.prompt-actions{display:flex;flex-direction:column;gap:3px}
|
|
165
|
-
.send-btn-prompt{padding:5px 12px;background:var(--accent);color:#fff;border:none;border-radius:5px;font-size:11px;font-weight:600;cursor:pointer;font-family:inherit}
|
|
166
|
-
.send-btn-prompt:hover{opacity:.9}
|
|
167
|
-
.copy-btn-prompt{padding:5px 12px;background:var(--bg);color:var(--muted);border:1px solid var(--border);border-radius:5px;font-size:11px;cursor:pointer;font-family:inherit}
|
|
168
|
-
.copy-btn-prompt:hover{color:var(--text);border-color:var(--muted)}
|
|
169
|
-
|
|
170
146
|
/* Export dropdown */
|
|
171
147
|
.export-dropdown{position:relative;display:inline-block}
|
|
172
148
|
.export-menu{display:none;position:absolute;top:100%;right:0;margin-top:4px;background:var(--surface);border:1px solid var(--border);border-radius:6px;padding:4px 0;z-index:100;min-width:160px;box-shadow:0 4px 12px rgba(0,0,0,.4)}
|
|
@@ -184,7 +160,6 @@ body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;backgrou
|
|
|
184
160
|
<span class="rate-badge" id="rateBadge" title="Lines per second">0 l/s</span>
|
|
185
161
|
<div class="hdr-right">
|
|
186
162
|
<button class="btn" id="liveBtn" title="Toggle live (L)">▶ Live</button>
|
|
187
|
-
<button class="btn" id="wsBtn" title="WebSocket mode (W)">WS</button>
|
|
188
163
|
<select class="sel-ctrl" id="intervalSel" title="Poll interval">
|
|
189
164
|
<option value="1000">1s</option>
|
|
190
165
|
<option value="2000" selected>2s</option>
|
|
@@ -279,7 +254,6 @@ body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;backgrou
|
|
|
279
254
|
<div style="flex:1"></div>
|
|
280
255
|
<button id="selPin" title="Pin selected lines">📌 Pin</button>
|
|
281
256
|
<button id="selCopy">Copy</button>
|
|
282
|
-
<button class="send-btn" id="selSend">Send to Chat</button>
|
|
283
257
|
<button id="selClear">Clear</button>
|
|
284
258
|
</div>
|
|
285
259
|
<div class="log-wrap">
|
|
@@ -288,7 +262,7 @@ body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;backgrou
|
|
|
288
262
|
<div class="empty" id="emptyState">
|
|
289
263
|
<div class="ico">📋</div>
|
|
290
264
|
<p>daemon.log</p>
|
|
291
|
-
<p><kbd>L</kbd> Live <kbd
|
|
265
|
+
<p><kbd>L</kbd> Live <kbd>/</kbd> Search <kbd>A</kbd> Auto-scroll <kbd>P</kbd> Pause <kbd>D</kbd> Stats</p>
|
|
292
266
|
</div>
|
|
293
267
|
</div>
|
|
294
268
|
<div class="minimap" id="minimap">
|
|
@@ -296,27 +270,6 @@ body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;backgrou
|
|
|
296
270
|
</div>
|
|
297
271
|
</div>
|
|
298
272
|
</div>
|
|
299
|
-
<div class="prompt-panel">
|
|
300
|
-
<div class="prompt-hdr" id="promptHdr">
|
|
301
|
-
<h3>💬 Send to Chat</h3>
|
|
302
|
-
<span class="arrow open" id="promptArrow">▼</span>
|
|
303
|
-
</div>
|
|
304
|
-
<div class="prompt-body open" id="promptBody">
|
|
305
|
-
<div class="presets">
|
|
306
|
-
<button class="preset" data-p="errors">🔴 Errors</button>
|
|
307
|
-
<button class="preset" data-p="status">📊 Status</button>
|
|
308
|
-
<button class="preset" data-p="slow">🐌 Slow</button>
|
|
309
|
-
<button class="preset" data-p="recent">📝 Recent</button>
|
|
310
|
-
</div>
|
|
311
|
-
<div class="prompt-row">
|
|
312
|
-
<textarea class="prompt-input" id="promptInput" placeholder="루나에게 요청..."></textarea>
|
|
313
|
-
<div class="prompt-actions">
|
|
314
|
-
<button class="send-btn-prompt" id="promptSend">📤 Send</button>
|
|
315
|
-
<button class="copy-btn-prompt" id="promptCopy">📋 Copy</button>
|
|
316
|
-
</div>
|
|
317
|
-
</div>
|
|
318
|
-
</div>
|
|
319
|
-
</div>
|
|
320
273
|
<script>
|
|
321
274
|
(function(){
|
|
322
275
|
'use strict';
|
|
@@ -328,16 +281,10 @@ const NEW_LINE_ANIM_MS = 900;
|
|
|
328
281
|
const AUTO_SCROLL_THRESHOLD = 50;
|
|
329
282
|
const MAX_INCREMENTAL_LINES = 200;
|
|
330
283
|
const PINS_STORAGE_KEY = 'mama-log-viewer-pins';
|
|
331
|
-
const WS_RECONNECT_DELAY = 3000;
|
|
332
|
-
const WS_MAX_RETRIES = 5;
|
|
333
284
|
const HISTOGRAM_BUCKETS = 30;
|
|
334
285
|
|
|
335
286
|
// Use same-origin by default so custom host/IP + non-default port keeps working.
|
|
336
287
|
const API_BASE = location.protocol === 'file:' ? 'http://localhost:3847' : '';
|
|
337
|
-
const WS_URL = location.protocol === 'file:'
|
|
338
|
-
? 'ws://localhost:3847/ws'
|
|
339
|
-
: (location.protocol === 'https:' ? 'wss://' : 'ws://') + location.host + '/ws';
|
|
340
|
-
const POST_MSG_ORIGIN = location.protocol === 'file:' ? '*' : location.origin;
|
|
341
288
|
|
|
342
289
|
const S = {
|
|
343
290
|
logs: [], autoScroll: true, selected: new Set(), lastClickIdx: -1,
|
|
@@ -348,7 +295,6 @@ const S = {
|
|
|
348
295
|
activeSources: null, excludedSources: new Set(),
|
|
349
296
|
activeChannels: null, excludedChannels: new Set(), sourcePanelOpen: false,
|
|
350
297
|
regexMode: false,
|
|
351
|
-
wsMode: false, ws: null, wsRetries: 0,
|
|
352
298
|
pins: new Set(),
|
|
353
299
|
pinsPanelOpen: false,
|
|
354
300
|
statsDashOpen: false,
|
|
@@ -630,8 +576,7 @@ function fetchLogs() {
|
|
|
630
576
|
}).then((data) => {
|
|
631
577
|
const hasTotal = Number.isFinite(data.total);
|
|
632
578
|
const displayTotal = hasTotal ? data.total : (data.truncated ? '~' + data.lines.length : data.lines.length);
|
|
633
|
-
|
|
634
|
-
setStatus('connected', modeLabel + ' (' + (parseInt($('intervalSel').value) / 1000) + 's) \u2022 ' + displayTotal + ' total');
|
|
579
|
+
setStatus('connected', 'Polling (' + (parseInt($('intervalSel').value) / 1000) + 's) \u2022 ' + displayTotal + ' total');
|
|
635
580
|
const bytes = data.totalBytes || data.fileSize;
|
|
636
581
|
if (bytes) $('fileInfo').textContent = (bytes / 1024 / 1024).toFixed(1) + 'MB';
|
|
637
582
|
|
|
@@ -688,77 +633,6 @@ function stopPolling() {
|
|
|
688
633
|
setStatus('disconnected', 'Stopped \u2014 ' + S.logs.length + ' lines');
|
|
689
634
|
}
|
|
690
635
|
|
|
691
|
-
/* ===== WebSocket ===== */
|
|
692
|
-
function connectWS() {
|
|
693
|
-
if (S.ws && (S.ws.readyState === WebSocket.OPEN || S.ws.readyState === WebSocket.CONNECTING)) return;
|
|
694
|
-
|
|
695
|
-
S.wsMode = true;
|
|
696
|
-
$('wsBtn').classList.add('ws-mode');
|
|
697
|
-
setStatus('connecting', 'WS connecting...');
|
|
698
|
-
|
|
699
|
-
try {
|
|
700
|
-
S.ws = new WebSocket(WS_URL);
|
|
701
|
-
} catch (e) {
|
|
702
|
-
fallbackToPolling('WS init failed');
|
|
703
|
-
return;
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
S.ws.onopen = () => {
|
|
707
|
-
S.wsRetries = 0;
|
|
708
|
-
setStatus('ws-connected', 'WebSocket connected');
|
|
709
|
-
S.ws.send(JSON.stringify({ type: 'subscribe', channel: 'logs' }));
|
|
710
|
-
};
|
|
711
|
-
|
|
712
|
-
S.ws.onmessage = (evt) => {
|
|
713
|
-
try {
|
|
714
|
-
const data = JSON.parse(evt.data);
|
|
715
|
-
if (data.type === 'log' && data.line) {
|
|
716
|
-
if (emptyState && emptyState.parentNode) emptyState.remove();
|
|
717
|
-
addLines([data.line], false);
|
|
718
|
-
} else if (data.type === 'logs' && data.lines) {
|
|
719
|
-
addLines(data.lines, !S.initialized);
|
|
720
|
-
S.initialized = true;
|
|
721
|
-
}
|
|
722
|
-
} catch (e) {
|
|
723
|
-
if (typeof evt.data === 'string' && evt.data.trim()) {
|
|
724
|
-
addLines([evt.data], false);
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
};
|
|
728
|
-
|
|
729
|
-
S.ws.onclose = () => {
|
|
730
|
-
if (!S.wsMode) return;
|
|
731
|
-
S.wsRetries++;
|
|
732
|
-
if (S.wsRetries <= WS_MAX_RETRIES) {
|
|
733
|
-
setStatus('connecting', 'WS reconnecting (' + S.wsRetries + '/' + WS_MAX_RETRIES + ')...');
|
|
734
|
-
setTimeout(connectWS, WS_RECONNECT_DELAY);
|
|
735
|
-
} else {
|
|
736
|
-
fallbackToPolling('WS max retries reached');
|
|
737
|
-
}
|
|
738
|
-
};
|
|
739
|
-
|
|
740
|
-
S.ws.onerror = () => {
|
|
741
|
-
// onclose will fire after onerror
|
|
742
|
-
};
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
function disconnectWS() {
|
|
746
|
-
S.wsMode = false;
|
|
747
|
-
$('wsBtn').classList.remove('ws-mode');
|
|
748
|
-
if (S.ws) {
|
|
749
|
-
S.ws.onclose = null;
|
|
750
|
-
S.ws.close();
|
|
751
|
-
S.ws = null;
|
|
752
|
-
}
|
|
753
|
-
S.wsRetries = 0;
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
function fallbackToPolling(reason) {
|
|
757
|
-
disconnectWS();
|
|
758
|
-
setStatus('disconnected', 'WS failed: ' + reason + ' \u2014 switching to polling');
|
|
759
|
-
if (!S.live) startPolling();
|
|
760
|
-
}
|
|
761
|
-
|
|
762
636
|
/* ===== Export ===== */
|
|
763
637
|
function downloadFile(content, filename) {
|
|
764
638
|
const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
|
|
@@ -1169,14 +1043,6 @@ $('selCopy').addEventListener('click', () => {
|
|
|
1169
1043
|
$('selCopy').textContent = '\u2705'; setTimeout(() => { $('selCopy').textContent = 'Copy'; }, 1200);
|
|
1170
1044
|
});
|
|
1171
1045
|
});
|
|
1172
|
-
$('selSend').addEventListener('click', () => {
|
|
1173
|
-
const text = getSelectedText();
|
|
1174
|
-
const msg = '[\uc120\ud0dd\ub41c \ub85c\uadf8 ' + S.selected.size + '\uc904]:\n```\n' + text + '\n```';
|
|
1175
|
-
window.parent.postMessage({ type: 'viewer:sendToChat', message: msg }, POST_MSG_ORIGIN);
|
|
1176
|
-
S.selected.clear();
|
|
1177
|
-
logArea.querySelectorAll('.ll.selected').forEach((el) => { el.classList.remove('selected'); });
|
|
1178
|
-
updateSelectionBar();
|
|
1179
|
-
});
|
|
1180
1046
|
$('selClear').addEventListener('click', () => {
|
|
1181
1047
|
S.selected.clear();
|
|
1182
1048
|
logArea.querySelectorAll('.ll.selected').forEach((el) => { el.classList.remove('selected'); });
|
|
@@ -1211,16 +1077,6 @@ $('clearBtn').addEventListener('click', () => {
|
|
|
1211
1077
|
if (S.statsDashOpen) updateStatsDashboard();
|
|
1212
1078
|
});
|
|
1213
1079
|
|
|
1214
|
-
$('wsBtn').addEventListener('click', () => {
|
|
1215
|
-
if (S.wsMode) {
|
|
1216
|
-
disconnectWS();
|
|
1217
|
-
setStatus(S.live ? 'connected' : 'disconnected', S.live ? 'Polling mode' : 'WS disconnected');
|
|
1218
|
-
} else {
|
|
1219
|
-
connectWS();
|
|
1220
|
-
if (!S.live) startPolling();
|
|
1221
|
-
}
|
|
1222
|
-
});
|
|
1223
|
-
|
|
1224
1080
|
document.querySelectorAll('.lvl').forEach((el) => {
|
|
1225
1081
|
el.addEventListener('click', () => {
|
|
1226
1082
|
const lv = el.getAttribute('data-l');
|
|
@@ -1256,7 +1112,6 @@ document.addEventListener('keydown', (e) => {
|
|
|
1256
1112
|
if (e.ctrlKey && e.key === 'f') { e.preventDefault(); $('searchInput').focus(); return; }
|
|
1257
1113
|
if (e.key === '/') { e.preventDefault(); $('searchInput').focus(); return; }
|
|
1258
1114
|
if (e.key === 'l' || e.key === 'L') { S.live ? stopPolling() : startPolling(); }
|
|
1259
|
-
if (e.key === 'w' || e.key === 'W') { $('wsBtn').click(); }
|
|
1260
1115
|
if (e.key === 'a' && !e.ctrlKey && !e.metaKey) {
|
|
1261
1116
|
S.autoScroll = !S.autoScroll;
|
|
1262
1117
|
$('autoBtn').classList.toggle('active', S.autoScroll);
|
|
@@ -1288,20 +1143,6 @@ document.addEventListener('keydown', (e) => {
|
|
|
1288
1143
|
}
|
|
1289
1144
|
});
|
|
1290
1145
|
|
|
1291
|
-
/* ===== Prompt panel ===== */
|
|
1292
|
-
$('promptHdr').addEventListener('click', () => {
|
|
1293
|
-
$('promptBody').classList.toggle('open');
|
|
1294
|
-
$('promptArrow').classList.toggle('open');
|
|
1295
|
-
});
|
|
1296
|
-
|
|
1297
|
-
const PRESETS = {
|
|
1298
|
-
errors: 'Analyze ERROR, FAIL, ECONNREFUSED logs from daemon.log and explain causes and fixes',
|
|
1299
|
-
status: 'Check current MAMA OS status (processes, memory, uptime, connections)',
|
|
1300
|
-
slow: 'Find slow operations (timeout, slow, 1000ms+) and analyze performance bottlenecks',
|
|
1301
|
-
recent: 'Summarize the last 50 log lines \u2014 what happened, any errors?',
|
|
1302
|
-
agents: 'Show only agent activity: Dashboard Agent, Wiki Agent, Conductor, delegate, audit results'
|
|
1303
|
-
};
|
|
1304
|
-
|
|
1305
1146
|
// Agent Activity patterns for filtering and audit timeline
|
|
1306
1147
|
const AGENT_PATTERNS = /Dashboard Agent|Wiki Agent|Conductor|Audit|delegate|Briefing published|Compilation complete|No changes detected|agent-refresh|wiki\/compile|Tool\] delegate/;
|
|
1307
1148
|
|
|
@@ -1337,13 +1178,6 @@ $('auditPanelToggle').addEventListener('click', () => {
|
|
|
1337
1178
|
panel.style.display = isVisible ? 'none' : 'block';
|
|
1338
1179
|
if (!isVisible) buildAuditTimeline();
|
|
1339
1180
|
});
|
|
1340
|
-
document.querySelectorAll('.preset').forEach((el) => {
|
|
1341
|
-
el.addEventListener('click', () => {
|
|
1342
|
-
$('promptInput').value = PRESETS[el.getAttribute('data-p')];
|
|
1343
|
-
$('promptInput').focus();
|
|
1344
|
-
});
|
|
1345
|
-
});
|
|
1346
|
-
|
|
1347
1181
|
// Toolbar: Agent filter button
|
|
1348
1182
|
$('agentFilterBtn').addEventListener('click', () => {
|
|
1349
1183
|
const isActive = $('agentFilterBtn').classList.toggle('active');
|
|
@@ -1358,30 +1192,6 @@ $('agentFilterBtn').addEventListener('click', () => {
|
|
|
1358
1192
|
applyFilters();
|
|
1359
1193
|
});
|
|
1360
1194
|
|
|
1361
|
-
$('promptSend').addEventListener('click', () => {
|
|
1362
|
-
const text = $('promptInput').value.trim();
|
|
1363
|
-
if (!text) return;
|
|
1364
|
-
let msg = text;
|
|
1365
|
-
if (S.selected.size > 0) {
|
|
1366
|
-
const selLogs = [];
|
|
1367
|
-
S.selected.forEach((idx) => { if (S.logs[idx]) selLogs.push(S.logs[idx].raw); });
|
|
1368
|
-
msg += '\n\n[\uc120\ud0dd\ub41c \ub85c\uadf8 ' + selLogs.length + '\uc904]:\n```\n' + selLogs.join('\n') + '\n```';
|
|
1369
|
-
}
|
|
1370
|
-
window.parent.postMessage({ type: 'viewer:sendToChat', message: msg }, POST_MSG_ORIGIN);
|
|
1371
|
-
$('promptInput').value = '';
|
|
1372
|
-
});
|
|
1373
|
-
$('promptCopy').addEventListener('click', () => {
|
|
1374
|
-
let text = $('promptInput').value;
|
|
1375
|
-
if (S.selected.size > 0) {
|
|
1376
|
-
const selLogs = [];
|
|
1377
|
-
S.selected.forEach((idx) => { if (S.logs[idx]) selLogs.push(S.logs[idx].raw); });
|
|
1378
|
-
text += '\n\n```\n' + selLogs.join('\n') + '\n```';
|
|
1379
|
-
}
|
|
1380
|
-
navigator.clipboard.writeText(text).then(() => {
|
|
1381
|
-
$('promptCopy').textContent = '\u2705'; setTimeout(() => { $('promptCopy').innerHTML = '📋 Copy'; }, 1200);
|
|
1382
|
-
});
|
|
1383
|
-
});
|
|
1384
|
-
|
|
1385
1195
|
window.addEventListener('message', (e) => {
|
|
1386
1196
|
if (e.data && e.data.type === 'mama:logs') {
|
|
1387
1197
|
const lines = e.data.content.split('\n').filter((l) => l.trim());
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/*! tailwindcss v4.3.2 | MIT License | https://tailwindcss.com */@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-y-reverse:0;--tw-border-style:solid;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--spacing:.25rem;--container-sm:24rem;--container-md:28rem;--container-4xl:56rem;--container-6xl:72rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height: 1.5 ;--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--font-weight-medium:500;--font-weight-semibold:600;--tracking-wide:.025em;--radius-lg:.5rem;--radius-xl:.75rem;--shadow-xs:0 1px 2px #0000000d;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--color-bg:#f9f8f6;--color-surface:#fff;--color-surface-hover:#f5f3f0;--color-surface-selected:#efece7;--color-surface-secondary:#f7f5f3;--color-agent:#ffce00;--color-agent-hover:#e6b800;--color-agent-strong:#7d5b00;--color-agent-light:#eddbf7;--color-on-agent:#131313;--color-text:#131313;--color-text-secondary:#616161;--color-text-tertiary:#8a8a8a;--color-border:#e3e3e3;--color-success:#047b5d;--color-warning:#ffb800;--color-danger:#d72c0d;--color-success-soft:#e7f5f0;--color-success-text:#04664e;--color-warning-soft:#fff4d6;--color-warning-text:#805c00;--shadow-drawer:-12px 0 32px #0000002e}}@layer base{#operator-root{color-scheme:light;height:100%;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif);-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;font-size:16px;line-height:1.5}#operator-root,#operator-root *,#operator-root :before,#operator-root :after{box-sizing:border-box;border:0 solid;margin:0;padding:0}#operator-root h1,#operator-root h2,#operator-root h3,#operator-root h4{font-size:inherit;font-weight:inherit}#operator-root ol,#operator-root ul{list-style:none}#operator-root button,#operator-root input,#operator-root select,#operator-root textarea{font:inherit;color:inherit;letter-spacing:inherit;background-color:#0000;border-radius:0}#operator-root button,#operator-root [role=button]{cursor:pointer}#operator-root button:disabled{cursor:default}#operator-root img,#operator-root svg,#operator-root video,#operator-root canvas{vertical-align:middle;display:block}#operator-root img,#operator-root video{max-width:100%;height:auto}#operator-root table{border-collapse:collapse;text-indent:0;border-color:inherit}#operator-root ::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){#operator-root ::placeholder{color:color-mix(in srgb,currentColor 50%,transparent)}}#operator-root :where(a[href],button,input,select,textarea,summary,[tabindex]:not([tabindex="-1"])):focus-visible{outline:2px solid var(--color-agent-strong);outline-offset:2px}}@layer components;@layer utilities{.fixed{position:fixed}.container{width:100%}@media(min-width:40rem){.container{max-width:40rem}}@media(min-width:48rem){.container{max-width:48rem}}@media(min-width:64rem){.container{max-width:64rem}}@media(min-width:80rem){.container{max-width:80rem}}@media(min-width:96rem){.container{max-width:96rem}}.mx-auto{margin-inline:auto}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:var(--spacing)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.h-4{height:calc(var(--spacing) * 4)}.h-full{height:100%}.min-h-0{min-height:0}.w-4{width:calc(var(--spacing) * 4)}.w-full{width:100%}.max-w-4xl{max-width:var(--container-4xl)}.max-w-6xl{max-width:var(--container-6xl)}.max-w-48{max-width:calc(var(--spacing) * 48)}.max-w-80{max-width:calc(var(--spacing) * 80)}.max-w-md{max-width:var(--container-md)}.min-w-0{min-width:0}.min-w-56{min-width:calc(var(--spacing) * 56)}.min-w-64{min-width:calc(var(--spacing) * 64)}.flex-1{flex:1}.flex-shrink-0,.shrink-0{flex-shrink:0}.rotate-90{rotate:90deg}.cursor-pointer{cursor:pointer}.scroll-mt-4{scroll-margin-top:calc(var(--spacing) * 4)}.list-decimal{list-style-type:decimal}.list-disc{list-style-type:disc}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(var(--spacing) * var(--tw-space-y-reverse));margin-block-end:calc(var(--spacing) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)))}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-border{border-color:var(--color-border)}.bg-agent{background-color:var(--color-agent)}.bg-agent-light{background-color:var(--color-agent-light)}.bg-bg{background-color:var(--color-bg)}.bg-danger{background-color:var(--color-danger)}.bg-danger\/10{background-color:#d72c0d1a}@supports (color:color-mix(in lab,red,red)){.bg-danger\/10{background-color:color-mix(in oklab,var(--color-danger) 10%,transparent)}}.bg-success-soft{background-color:var(--color-success-soft)}.bg-surface{background-color:var(--color-surface)}.bg-surface-hover{background-color:var(--color-surface-hover)}.bg-surface-secondary{background-color:var(--color-surface-secondary)}.bg-surface-selected{background-color:var(--color-surface-selected)}.bg-warning-soft{background-color:var(--color-warning-soft)}.p-4{padding:calc(var(--spacing) * 4)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-5{padding-inline:calc(var(--spacing) * 5)}.px-6{padding-inline:calc(var(--spacing) * 6)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:var(--spacing)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-5{padding-block:calc(var(--spacing) * 5)}.py-16{padding-block:calc(var(--spacing) * 16)}.pt-5{padding-top:calc(var(--spacing) * 5)}.pl-1{padding-left:var(--spacing)}.pl-5{padding-left:calc(var(--spacing) * 5)}.text-center{text-align:center}.text-left{text-align:left}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.whitespace-normal{white-space:normal}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-agent-strong{color:var(--color-agent-strong)}.text-danger{color:var(--color-danger)}.text-on-agent{color:var(--color-on-agent)}.text-success{color:var(--color-success)}.text-success-text{color:var(--color-success-text)}.text-text{color:var(--color-text)}.text-text-secondary{color:var(--color-text-secondary)}.text-text-tertiary{color:var(--color-text-tertiary)}.text-warning-text{color:var(--color-warning-text)}.uppercase{text-transform:uppercase}.underline-offset-2{text-underline-offset:2px}.shadow-\[var\(--shadow-xs\)\]{--tw-shadow:var(--shadow-xs);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.last\:border-0:last-child{border-style:var(--tw-border-style);border-width:0}@media(hover:hover){.hover\:bg-agent-hover:hover{background-color:var(--color-agent-hover)}.hover\:bg-surface-hover:hover{background-color:var(--color-surface-hover)}.hover\:bg-surface-selected:hover{background-color:var(--color-surface-selected)}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-80:hover{opacity:.8}}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-agent-strong:focus{--tw-ring-color:var(--color-agent-strong)}.disabled\:opacity-40:disabled{opacity:.4}.disabled\:opacity-50:disabled{opacity:.5}@media(min-width:40rem){.sm\:max-w-sm{max-width:var(--container-sm)}.sm\:flex-row{flex-direction:row}.sm\:items-end{align-items:flex-end}.sm\:justify-between{justify-content:space-between}}@media(min-width:48rem){.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}}@media(prefers-reduced-motion:reduce){#operator-root *,#operator-root :before,#operator-root :after{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important}}.trigger-drawer,.task-drawer{border:0;border-left:1px solid var(--color-border);background:var(--color-surface);width:min(100%,32rem);height:100dvh;max-height:100dvh;color:var(--color-text);box-shadow:var(--shadow-drawer);margin:0 0 0 auto;padding:0;overflow:hidden}.trigger-drawer::backdrop,.task-drawer::backdrop{background:#13131361}@supports (color:color-mix(in lab,red,red)){.trigger-drawer::backdrop,.task-drawer::backdrop{background:color-mix(in srgb,var(--color-text) 38%,transparent)}}.report-summary{margin-bottom:12px}.summary-title{color:var(--color-text);margin-bottom:4px;font-size:14px;font-weight:600}.summary-stats{color:var(--color-text-secondary);font-size:12px}.stat-highlight{color:var(--color-agent-strong);font-weight:700}.report-section-title{color:var(--color-text);margin:4px 0 10px;font-size:13px;font-weight:600}.report-card{border:1px solid var(--color-border);background:var(--color-surface);border-radius:10px;margin-bottom:8px;padding:10px 12px}.card-header{justify-content:space-between;align-items:center;gap:8px;display:flex}.card-title{color:var(--color-text);text-overflow:ellipsis;white-space:nowrap;min-width:0;font-size:13px;font-weight:600;overflow:hidden}.card-badge{border-radius:999px;flex-shrink:0;padding:2px 8px;font-size:11px;font-weight:500}.badge-danger{color:var(--color-danger);background:#d72c0d1f}@supports (color:color-mix(in lab,red,red)){.badge-danger{background:color-mix(in srgb,var(--color-danger) 12%,transparent)}}.badge-warning{color:var(--color-warning-text);background:#ffb80029}@supports (color:color-mix(in lab,red,red)){.badge-warning{background:color-mix(in srgb,var(--color-warning) 16%,transparent)}}.badge-info{color:var(--color-agent-strong);background:var(--color-agent-light)}.badge-success{color:var(--color-success);background:#047b5d1f}@supports (color:color-mix(in lab,red,red)){.badge-success{background:color-mix(in srgb,var(--color-success) 12%,transparent)}}.card-tags{flex-wrap:wrap;gap:4px;margin-top:4px;display:flex}.tag{background:var(--color-surface-secondary);color:var(--color-text-tertiary);border-radius:999px;padding:1px 6px;font-size:10px}.tag-channel{background:var(--color-agent-light);color:var(--color-agent-strong)}.card-action{color:var(--color-text-secondary);margin-top:6px;font-size:12px}.card-action:before{content:"→ ";color:var(--color-agent-strong)}.report-table{border-collapse:collapse;width:100%;min-width:640px;font-size:12px}.report-slot-content{overflow-wrap:anywhere;min-width:0;max-width:100%;overflow-x:auto}.report-table th{text-align:left;color:var(--color-text-tertiary);border-bottom:1px solid var(--color-border);padding:6px 8px;font-weight:600}.report-table td{border-bottom:1px solid var(--color-border);color:var(--color-text-secondary);padding:6px 8px}.report-table tr:last-child td{border-bottom:none}.task-table{border-collapse:collapse;width:100%;min-width:1040px}.task-table th{border-bottom:1px solid var(--color-border);background:var(--color-surface-secondary);color:var(--color-text-secondary);text-align:left;white-space:nowrap;padding:10px 12px;font-size:11px;font-weight:600}.task-reference-link{color:var(--color-agent-strong);text-underline-offset:2px;font-weight:600;text-decoration:underline}.task-reference-link:hover{color:var(--color-text)}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}
|