@jungjaehoon/mama-os 0.32.3 → 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.d.ts.map +1 -1
- package/dist/cli/commands/start.js +58 -23
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/cli/runtime/api-routes-init.d.ts +2 -0
- package/dist/cli/runtime/api-routes-init.d.ts.map +1 -1
- package/dist/cli/runtime/api-routes-init.js +62 -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/gateways/message-router.d.ts +4 -0
- package/dist/gateways/message-router.d.ts.map +1 -1
- package/dist/gateways/message-router.js +62 -13
- package/dist/gateways/message-router.js.map +1 -1
- package/dist/gateways/owner-report-inbox.d.ts +40 -0
- package/dist/gateways/owner-report-inbox.d.ts.map +1 -0
- package/dist/gateways/owner-report-inbox.js +128 -0
- package/dist/gateways/owner-report-inbox.js.map +1 -0
- package/dist/gateways/report-projection-v1.d.ts +41 -0
- package/dist/gateways/report-projection-v1.d.ts.map +1 -0
- package/dist/gateways/report-projection-v1.js +160 -0
- package/dist/gateways/report-projection-v1.js.map +1 -0
- package/dist/gateways/session-store.d.ts +22 -0
- package/dist/gateways/session-store.d.ts.map +1 -1
- package/dist/gateways/session-store.js +76 -3
- package/dist/gateways/session-store.js.map +1 -1
- package/dist/gateways/telegram-message-ledger.d.ts +10 -0
- package/dist/gateways/telegram-message-ledger.d.ts.map +1 -1
- package/dist/gateways/telegram-message-ledger.js +23 -2
- package/dist/gateways/telegram-message-ledger.js.map +1 -1
- package/dist/gateways/telegram-report-context-store.d.ts +193 -0
- package/dist/gateways/telegram-report-context-store.d.ts.map +1 -0
- package/dist/gateways/telegram-report-context-store.js +612 -0
- package/dist/gateways/telegram-report-context-store.js.map +1 -0
- package/dist/gateways/telegram.d.ts +9 -0
- package/dist/gateways/telegram.d.ts.map +1 -1
- package/dist/gateways/telegram.js +100 -0
- package/dist/gateways/telegram.js.map +1 -1
- package/dist/gateways/types.d.ts +12 -0
- package/dist/gateways/types.d.ts.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/dist/operator/operator-trigger-loop.d.ts +17 -1
- package/dist/operator/operator-trigger-loop.d.ts.map +1 -1
- package/dist/operator/operator-trigger-loop.js +87 -23
- package/dist/operator/operator-trigger-loop.js.map +1 -1
- package/dist/operator/report-delivery-coordinator.d.ts +82 -0
- package/dist/operator/report-delivery-coordinator.d.ts.map +1 -0
- package/dist/operator/report-delivery-coordinator.js +123 -0
- package/dist/operator/report-delivery-coordinator.js.map +1 -0
- package/dist/operator/situation-report.d.ts +7 -0
- package/dist/operator/situation-report.d.ts.map +1 -1
- package/dist/operator/situation-report.js +12 -0
- package/dist/operator/situation-report.js.map +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
|
@@ -1,456 +0,0 @@
|
|
|
1
|
-
import { API, } from '../utils/api.js';
|
|
2
|
-
import { DebugLogger } from '../utils/debug-logger.js';
|
|
3
|
-
import { createCollapsible } from '../utils/dom.js';
|
|
4
|
-
import { reportPageContext } from '../utils/ui-commands.js';
|
|
5
|
-
const logger = new DebugLogger('Dashboard');
|
|
6
|
-
// ── Style constants ─────────────────────────────────────────────────────────
|
|
7
|
-
const COLOR = {
|
|
8
|
-
primary: '#1A1A1A',
|
|
9
|
-
secondary: '#6B6560',
|
|
10
|
-
tertiary: '#9E9891',
|
|
11
|
-
border: '#EDE9E1',
|
|
12
|
-
bg: '#FAFAF8',
|
|
13
|
-
red: '#D94F4F',
|
|
14
|
-
green: '#3A9E7E',
|
|
15
|
-
yellow: '#F5C518',
|
|
16
|
-
};
|
|
17
|
-
const S = {
|
|
18
|
-
heading: `font-family:Inter,'Noto Sans KR',sans-serif;font-size:14px;font-weight:600;color:${COLOR.primary};margin:0 0 10px 0;`,
|
|
19
|
-
body: `font-size:12px;color:${COLOR.secondary};line-height:1.6;margin:0;`,
|
|
20
|
-
pill: `display:inline-block;font-size:10px;padding:1px 6px;border-radius:2px;font-weight:600;`,
|
|
21
|
-
row: `display:flex;align-items:baseline;gap:8px;padding:5px 0;border-bottom:1px solid ${COLOR.border};`,
|
|
22
|
-
time: `font-size:10px;color:${COLOR.tertiary};min-width:48px;white-space:nowrap;font-variant-numeric:tabular-nums;`,
|
|
23
|
-
section: `background:#fff;border:1px solid ${COLOR.border};border-radius:4px;padding:16px 20px;margin-bottom:12px;`,
|
|
24
|
-
};
|
|
25
|
-
const CONNECTOR_ICON = {
|
|
26
|
-
calendar: '\u{1F4C5}',
|
|
27
|
-
slack: '\u{1F4AC}',
|
|
28
|
-
discord: '\u{1F4AC}',
|
|
29
|
-
telegram: '\u{1F4AC}',
|
|
30
|
-
trello: '\u{1F4CB}',
|
|
31
|
-
kagemusha: '\u{1F977}',
|
|
32
|
-
'claude-code': '\u{1F916}',
|
|
33
|
-
gmail: '\u{1F4E7}',
|
|
34
|
-
notion: '\u{1F4DD}',
|
|
35
|
-
obsidian: '\u{1F4D3}',
|
|
36
|
-
sheets: '\u{1F4CA}',
|
|
37
|
-
drive: '\u{1F4C1}',
|
|
38
|
-
chatwork: '\u{1F4AC}',
|
|
39
|
-
imessage: '\u{1F4AC}',
|
|
40
|
-
};
|
|
41
|
-
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
42
|
-
function esc(s) {
|
|
43
|
-
return s
|
|
44
|
-
.replace(/&/g, '&')
|
|
45
|
-
.replace(/</g, '<')
|
|
46
|
-
.replace(/>/g, '>')
|
|
47
|
-
.replace(/"/g, '"');
|
|
48
|
-
}
|
|
49
|
-
function relativeTime(ts) {
|
|
50
|
-
const diffMs = Date.now() - new Date(ts).getTime();
|
|
51
|
-
const diffMin = Math.floor(diffMs / 60_000);
|
|
52
|
-
if (diffMin < 1)
|
|
53
|
-
return 'just now';
|
|
54
|
-
if (diffMin < 60)
|
|
55
|
-
return `${diffMin}m ago`;
|
|
56
|
-
const diffHr = Math.floor(diffMin / 60);
|
|
57
|
-
if (diffHr < 24)
|
|
58
|
-
return `${diffHr}h ago`;
|
|
59
|
-
const diffDays = Math.floor(diffHr / 24);
|
|
60
|
-
return `${diffDays}d ago`;
|
|
61
|
-
}
|
|
62
|
-
// ── Dashboard Module ────────────────────────────────────────────────────────
|
|
63
|
-
const REFRESH_INTERVAL = 5 * 60 * 1000; // 5 minutes
|
|
64
|
-
export class DashboardModule {
|
|
65
|
-
container = null;
|
|
66
|
-
eventSource = null;
|
|
67
|
-
refreshTimer = null;
|
|
68
|
-
selectedProject = null;
|
|
69
|
-
selectedConnector = null;
|
|
70
|
-
getCurrentPageType() {
|
|
71
|
-
if (this.selectedConnector) {
|
|
72
|
-
return 'dashboard-connector';
|
|
73
|
-
}
|
|
74
|
-
if (this.selectedProject) {
|
|
75
|
-
return 'dashboard-project';
|
|
76
|
-
}
|
|
77
|
-
return 'dashboard-overview';
|
|
78
|
-
}
|
|
79
|
-
init() {
|
|
80
|
-
this.container = document.getElementById('dashboard-slots');
|
|
81
|
-
if (!this.container)
|
|
82
|
-
return;
|
|
83
|
-
this.loadDashboard();
|
|
84
|
-
this.connectSse();
|
|
85
|
-
this.refreshTimer = setInterval(() => this.loadDashboard(), REFRESH_INTERVAL);
|
|
86
|
-
}
|
|
87
|
-
async loadDashboard() {
|
|
88
|
-
if (!this.container)
|
|
89
|
-
return;
|
|
90
|
-
try {
|
|
91
|
-
const [summaryRes, noticesRes, pipelineRes, connectorRes, projectsRes, slotsRes] = await Promise.all([
|
|
92
|
-
API.getIntelligenceSummary().catch(() => ({ text: '', generatedAt: null })),
|
|
93
|
-
API.getNotices(10).catch(() => ({ notices: [] })),
|
|
94
|
-
API.getPipeline().catch(() => ({ projects: [] })),
|
|
95
|
-
API.getConnectorActivity().catch(() => ({ connectors: [] })),
|
|
96
|
-
API.getProjects().catch(() => ({ projects: [] })),
|
|
97
|
-
API.getReportSlots().catch(() => ({ slots: [] })),
|
|
98
|
-
]);
|
|
99
|
-
this.render({
|
|
100
|
-
summary: summaryRes,
|
|
101
|
-
notices: noticesRes.notices,
|
|
102
|
-
pipeline: pipelineRes.projects,
|
|
103
|
-
connectors: connectorRes.connectors,
|
|
104
|
-
totalDecisions: projectsRes.projects.reduce((sum, p) => sum + p.activeDecisions, 0),
|
|
105
|
-
agentCount: new Set(noticesRes.notices.map((n) => n.agent)).size,
|
|
106
|
-
briefingSlot: slotsRes.slots.find((s) => s.slotId === 'briefing') ?? null,
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
catch (err) {
|
|
110
|
-
logger.error('Failed to load dashboard data', err);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
connectSse() {
|
|
114
|
-
try {
|
|
115
|
-
this.eventSource = new EventSource('/api/report/events');
|
|
116
|
-
this.eventSource.addEventListener('report-update', (event) => {
|
|
117
|
-
try {
|
|
118
|
-
const data = JSON.parse(event.data);
|
|
119
|
-
if (data.slots && Array.isArray(data.slots)) {
|
|
120
|
-
const briefing = data.slots.find((s) => s.slotId === 'briefing');
|
|
121
|
-
if (briefing)
|
|
122
|
-
this.updateBriefingSection(briefing.html);
|
|
123
|
-
}
|
|
124
|
-
else if (data.slot === 'briefing' && typeof data.html === 'string') {
|
|
125
|
-
this.updateBriefingSection(data.html);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
catch (err) {
|
|
129
|
-
logger.error('SSE parse error', err);
|
|
130
|
-
}
|
|
131
|
-
});
|
|
132
|
-
this.eventSource.onerror = () => {
|
|
133
|
-
logger.warn('SSE connection lost, will auto-reconnect');
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
catch (err) {
|
|
137
|
-
logger.error('Failed to connect SSE', err);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
updateBriefingSection(html) {
|
|
141
|
-
const el = this.container?.querySelector('#dash-briefing-content');
|
|
142
|
-
if (!el)
|
|
143
|
-
return;
|
|
144
|
-
const clean = DOMPurify.sanitize(html);
|
|
145
|
-
if (el.innerHTML !== clean)
|
|
146
|
-
el.innerHTML = clean;
|
|
147
|
-
}
|
|
148
|
-
render(data) {
|
|
149
|
-
if (!this.container)
|
|
150
|
-
return;
|
|
151
|
-
reportPageContext('dashboard', {
|
|
152
|
-
pageType: this.getCurrentPageType(),
|
|
153
|
-
selectedProject: this.selectedProject,
|
|
154
|
-
selectedConnector: this.selectedConnector,
|
|
155
|
-
noticeCount: data.notices.length,
|
|
156
|
-
pipelineCount: data.pipeline.length,
|
|
157
|
-
connectorCount: data.connectors.length,
|
|
158
|
-
totalDecisions: data.totalDecisions,
|
|
159
|
-
agentCount: data.agentCount,
|
|
160
|
-
summary: data.summary.text?.slice(0, 300) ?? '',
|
|
161
|
-
});
|
|
162
|
-
const emptyEl = document.getElementById('slots-empty');
|
|
163
|
-
const hasData = data.summary.text ||
|
|
164
|
-
data.notices.length > 0 ||
|
|
165
|
-
data.pipeline.length > 0 ||
|
|
166
|
-
data.connectors.length > 0 ||
|
|
167
|
-
data.briefingSlot?.html;
|
|
168
|
-
if (!hasData) {
|
|
169
|
-
if (emptyEl)
|
|
170
|
-
emptyEl.style.display = '';
|
|
171
|
-
this.container.querySelectorAll('.dash-section').forEach((el) => el.remove());
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
if (emptyEl)
|
|
175
|
-
emptyEl.style.display = 'none';
|
|
176
|
-
// Clear container and build DOM elements
|
|
177
|
-
this.container.innerHTML = '';
|
|
178
|
-
// ── Section 1: Summary + Notices ──────────────────────────────────────
|
|
179
|
-
{
|
|
180
|
-
let summaryHtml = '';
|
|
181
|
-
if (data.briefingSlot?.html) {
|
|
182
|
-
summaryHtml += `<div id="dash-briefing-content" style="margin-bottom:12px">${DOMPurify.sanitize(data.briefingSlot.html)}</div>`;
|
|
183
|
-
}
|
|
184
|
-
else if (data.summary.text) {
|
|
185
|
-
summaryHtml += `<p style="${S.body}margin-bottom:12px">${esc(data.summary.text)}</p>`;
|
|
186
|
-
if (data.summary.generatedAt) {
|
|
187
|
-
summaryHtml += `<div style="font-size:10px;color:${COLOR.tertiary};margin-bottom:12px">${esc(relativeTime(data.summary.generatedAt))}</div>`;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
else {
|
|
191
|
-
summaryHtml += `<p style="font-size:12px;color:${COLOR.tertiary};margin-bottom:12px">Waiting for agent briefing...</p>`;
|
|
192
|
-
}
|
|
193
|
-
if (data.notices.length > 0) {
|
|
194
|
-
summaryHtml += `<div style="margin-top:8px">`;
|
|
195
|
-
for (const n of data.notices) {
|
|
196
|
-
const time = relativeTime(n.timestamp);
|
|
197
|
-
summaryHtml +=
|
|
198
|
-
`<div style="${S.row}">` +
|
|
199
|
-
`<span style="${S.time}">${esc(time)}</span>` +
|
|
200
|
-
`<span style="${S.pill}background:${COLOR.green}20;color:${COLOR.green}">${esc(n.agent)}</span>` +
|
|
201
|
-
`<span style="font-size:11px;color:${COLOR.primary};flex:1">${esc(n.action)}</span>` +
|
|
202
|
-
`<span style="font-size:10px;color:${COLOR.tertiary};overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:200px">${esc(n.target)}</span>` +
|
|
203
|
-
`</div>`;
|
|
204
|
-
}
|
|
205
|
-
summaryHtml += `</div>`;
|
|
206
|
-
}
|
|
207
|
-
else {
|
|
208
|
-
summaryHtml += `<div style="font-size:11px;color:${COLOR.tertiary};margin-top:8px">No agent activity yet</div>`;
|
|
209
|
-
}
|
|
210
|
-
const summarySection = createCollapsible('Summary', summaryHtml, {
|
|
211
|
-
storageKey: 'dash-collapse-summary',
|
|
212
|
-
defaultOpen: true,
|
|
213
|
-
headingStyle: S.heading,
|
|
214
|
-
containerStyle: S.section,
|
|
215
|
-
});
|
|
216
|
-
summarySection.classList.add('dash-section');
|
|
217
|
-
this.container.appendChild(summarySection);
|
|
218
|
-
}
|
|
219
|
-
// ── Section 2: Pipeline (clickable rows) ─────────────────────────────
|
|
220
|
-
if (data.pipeline.length > 0) {
|
|
221
|
-
const sorted = [...data.pipeline].sort((a, b) => new Date(b.lastActivity).getTime() - new Date(a.lastActivity).getTime());
|
|
222
|
-
let pipelineHtml = '';
|
|
223
|
-
for (const p of sorted) {
|
|
224
|
-
const rel = relativeTime(p.lastActivity);
|
|
225
|
-
const isRecent = Date.now() - new Date(p.lastActivity).getTime() < 3600000;
|
|
226
|
-
const newBadge = isRecent
|
|
227
|
-
? ' <span style="color:#D94F4F;font-size:10px">\u{1F534}</span>'
|
|
228
|
-
: '';
|
|
229
|
-
const dotColor = isRecent ? COLOR.green : COLOR.tertiary;
|
|
230
|
-
const projectId = esc(p.project);
|
|
231
|
-
pipelineHtml +=
|
|
232
|
-
`<div class="dash-pipeline-row" data-project="${projectId}" ` +
|
|
233
|
-
`style="cursor:pointer;border-bottom:1px solid ${COLOR.border};transition:background 0.1s" ` +
|
|
234
|
-
`onmouseover="this.style.background='#F5F3EF'" onmouseout="this.style.background='transparent'">` +
|
|
235
|
-
`<div style="display:flex;align-items:center;gap:8px;padding:4px 0">` +
|
|
236
|
-
`<span class="dash-row-arrow" style="display:inline-block;font-size:9px;width:10px;color:${COLOR.tertiary}">\u25B6</span>` +
|
|
237
|
-
`<span style="display:inline-block;width:6px;height:6px;background:${dotColor};flex-shrink:0"></span>` +
|
|
238
|
-
`<span style="font-size:12px;font-weight:600;color:${COLOR.primary};flex:1">${esc(p.project.slice(0, 20))}${newBadge}</span>` +
|
|
239
|
-
`<span style="font-size:10px;color:${COLOR.tertiary}">${p.activeDecisions} decisions</span>` +
|
|
240
|
-
`<span style="${S.time}">${esc(rel)}</span>` +
|
|
241
|
-
`</div>` +
|
|
242
|
-
`<div class="dash-pipeline-detail" style="display:none;padding:6px 0 6px 28px;font-size:11px;color:${COLOR.secondary}">Loading...</div>` +
|
|
243
|
-
`</div>`;
|
|
244
|
-
}
|
|
245
|
-
const pipelineSection = createCollapsible('Pipeline', pipelineHtml, {
|
|
246
|
-
storageKey: 'dash-collapse-pipeline',
|
|
247
|
-
defaultOpen: true,
|
|
248
|
-
headingStyle: S.heading,
|
|
249
|
-
containerStyle: S.section,
|
|
250
|
-
});
|
|
251
|
-
pipelineSection.classList.add('dash-section');
|
|
252
|
-
this.container.appendChild(pipelineSection);
|
|
253
|
-
// Bind click-to-expand on pipeline rows
|
|
254
|
-
pipelineSection.querySelectorAll('.dash-pipeline-row').forEach((el) => {
|
|
255
|
-
el.addEventListener('click', () => {
|
|
256
|
-
const row = el;
|
|
257
|
-
const detail = row.querySelector('.dash-pipeline-detail');
|
|
258
|
-
const arrow = row.querySelector('.dash-row-arrow');
|
|
259
|
-
if (!detail || !arrow)
|
|
260
|
-
return;
|
|
261
|
-
const isOpen = detail.style.display !== 'none';
|
|
262
|
-
if (isOpen) {
|
|
263
|
-
detail.style.display = 'none';
|
|
264
|
-
arrow.textContent = '\u25B6';
|
|
265
|
-
this.selectedProject = null;
|
|
266
|
-
reportPageContext('dashboard', {
|
|
267
|
-
pageType: this.getCurrentPageType(),
|
|
268
|
-
selectedProject: null,
|
|
269
|
-
selectedConnector: this.selectedConnector,
|
|
270
|
-
});
|
|
271
|
-
}
|
|
272
|
-
else {
|
|
273
|
-
detail.style.display = '';
|
|
274
|
-
arrow.textContent = '\u25BC';
|
|
275
|
-
const projectName = row.dataset.project;
|
|
276
|
-
if (projectName && detail.textContent === 'Loading...') {
|
|
277
|
-
this.loadPipelineDetail(detail, projectName);
|
|
278
|
-
}
|
|
279
|
-
this.selectedProject = projectName ?? null;
|
|
280
|
-
this.selectedConnector = null;
|
|
281
|
-
reportPageContext('dashboard', {
|
|
282
|
-
pageType: 'dashboard-project',
|
|
283
|
-
selectedProject: this.selectedProject,
|
|
284
|
-
selectedConnector: this.selectedConnector,
|
|
285
|
-
});
|
|
286
|
-
}
|
|
287
|
-
});
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
// ── Section 3: Connector Activity (clickable rows) ───────────────────
|
|
291
|
-
if (data.connectors.length > 0) {
|
|
292
|
-
let connHtml = '';
|
|
293
|
-
for (const c of data.connectors) {
|
|
294
|
-
const icon = CONNECTOR_ICON[c.connector] || '\u{1F517}';
|
|
295
|
-
const connId = esc(c.connector);
|
|
296
|
-
if (c.status === 'active') {
|
|
297
|
-
const rel = relativeTime(c.timestamp);
|
|
298
|
-
const snippet = c.content.replace(/\n/g, ' ').slice(0, 60);
|
|
299
|
-
connHtml +=
|
|
300
|
-
`<div class="dash-connector-row" data-connector="${connId}" ` +
|
|
301
|
-
`style="cursor:pointer;transition:background 0.1s" ` +
|
|
302
|
-
`onmouseover="this.style.background='#F5F3EF'" onmouseout="this.style.background='transparent'">` +
|
|
303
|
-
`<div style="${S.row}">` +
|
|
304
|
-
`<span class="dash-row-arrow" style="display:inline-block;font-size:9px;width:10px;color:${COLOR.tertiary}">\u25B6</span>` +
|
|
305
|
-
`<span style="font-size:14px;min-width:20px">${icon}</span>` +
|
|
306
|
-
`<span style="font-size:12px;font-weight:600;color:${COLOR.primary};min-width:80px">${connId}</span>` +
|
|
307
|
-
`<span style="font-size:11px;color:${COLOR.secondary};flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">` +
|
|
308
|
-
`<span style="color:${COLOR.tertiary}">${esc(c.channel)}</span> ${esc(snippet)}</span>` +
|
|
309
|
-
`<span style="${S.time}">${esc(rel)}</span>` +
|
|
310
|
-
`</div>` +
|
|
311
|
-
`<div class="dash-connector-detail" style="display:none;padding:4px 0 4px 36px;font-size:11px;color:${COLOR.secondary}">Loading...</div>` +
|
|
312
|
-
`</div>`;
|
|
313
|
-
}
|
|
314
|
-
else if (c.status === 'idle') {
|
|
315
|
-
connHtml +=
|
|
316
|
-
`<div style="${S.row}">` +
|
|
317
|
-
`<span style="font-size:14px;min-width:20px">${icon}</span>` +
|
|
318
|
-
`<span style="font-size:12px;color:${COLOR.tertiary};flex:1">${connId} <span style="font-size:10px">idle</span></span>` +
|
|
319
|
-
`</div>`;
|
|
320
|
-
}
|
|
321
|
-
else {
|
|
322
|
-
connHtml +=
|
|
323
|
-
`<div style="${S.row}">` +
|
|
324
|
-
`<span style="font-size:14px;min-width:20px">${icon}</span>` +
|
|
325
|
-
`<span style="font-size:12px;color:${COLOR.red};flex:1">${connId} <span style="font-size:10px">\u26A0\uFE0F \uBBF8\uC5F0\uACB0</span></span>` +
|
|
326
|
-
`</div>`;
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
const connSection = createCollapsible('Connectors', connHtml, {
|
|
330
|
-
storageKey: 'dash-collapse-connectors',
|
|
331
|
-
defaultOpen: true,
|
|
332
|
-
headingStyle: S.heading,
|
|
333
|
-
containerStyle: S.section,
|
|
334
|
-
});
|
|
335
|
-
connSection.classList.add('dash-section');
|
|
336
|
-
this.container.appendChild(connSection);
|
|
337
|
-
// Bind click-to-expand on connector rows
|
|
338
|
-
connSection.querySelectorAll('.dash-connector-row').forEach((el) => {
|
|
339
|
-
el.addEventListener('click', () => {
|
|
340
|
-
const row = el;
|
|
341
|
-
const detail = row.querySelector('.dash-connector-detail');
|
|
342
|
-
const arrow = row.querySelector('.dash-row-arrow');
|
|
343
|
-
if (!detail || !arrow)
|
|
344
|
-
return;
|
|
345
|
-
const isOpen = detail.style.display !== 'none';
|
|
346
|
-
if (isOpen) {
|
|
347
|
-
detail.style.display = 'none';
|
|
348
|
-
arrow.textContent = '\u25B6';
|
|
349
|
-
this.selectedConnector = null;
|
|
350
|
-
reportPageContext('dashboard', {
|
|
351
|
-
pageType: this.getCurrentPageType(),
|
|
352
|
-
selectedProject: this.selectedProject,
|
|
353
|
-
selectedConnector: null,
|
|
354
|
-
});
|
|
355
|
-
}
|
|
356
|
-
else {
|
|
357
|
-
detail.style.display = '';
|
|
358
|
-
arrow.textContent = '\u25BC';
|
|
359
|
-
const connName = row.dataset.connector;
|
|
360
|
-
if (connName && detail.textContent === 'Loading...') {
|
|
361
|
-
this.loadConnectorDetail(detail, connName);
|
|
362
|
-
}
|
|
363
|
-
this.selectedConnector = connName ?? null;
|
|
364
|
-
this.selectedProject = null;
|
|
365
|
-
reportPageContext('dashboard', {
|
|
366
|
-
pageType: 'dashboard-connector',
|
|
367
|
-
selectedProject: this.selectedProject,
|
|
368
|
-
selectedConnector: this.selectedConnector,
|
|
369
|
-
});
|
|
370
|
-
}
|
|
371
|
-
});
|
|
372
|
-
});
|
|
373
|
-
}
|
|
374
|
-
// ── Section 4: System ────────────────────────────────────────────────
|
|
375
|
-
{
|
|
376
|
-
const statsHtml = `<div style="display:flex;gap:24px;align-items:center">` +
|
|
377
|
-
`<span style="font-size:11px;color:${COLOR.secondary}">Agents: <strong style="color:${COLOR.primary}">${data.agentCount || '-'}</strong></span>` +
|
|
378
|
-
`<span style="font-size:11px;color:${COLOR.secondary}">Decisions: <strong style="color:${COLOR.primary}">${data.totalDecisions}</strong></span>` +
|
|
379
|
-
`</div>`;
|
|
380
|
-
const systemSection = createCollapsible('System', statsHtml, {
|
|
381
|
-
storageKey: 'dash-collapse-system',
|
|
382
|
-
defaultOpen: true,
|
|
383
|
-
headingStyle: S.heading,
|
|
384
|
-
containerStyle: S.section + 'padding:10px 20px;',
|
|
385
|
-
});
|
|
386
|
-
systemSection.classList.add('dash-section');
|
|
387
|
-
this.container.appendChild(systemSection);
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
/** Load and render project decisions inline for a pipeline row */
|
|
391
|
-
async loadPipelineDetail(detailEl, project) {
|
|
392
|
-
try {
|
|
393
|
-
const res = await API.getProjectDecisions(project, 10);
|
|
394
|
-
if (res.decisions.length === 0) {
|
|
395
|
-
detailEl.innerHTML = `<div style="color:${COLOR.tertiary}">No decisions found.</div>`;
|
|
396
|
-
return;
|
|
397
|
-
}
|
|
398
|
-
let html = '';
|
|
399
|
-
for (const d of res.decisions) {
|
|
400
|
-
const rel = relativeTime(d.created_at);
|
|
401
|
-
const statusColor = d.status === 'active' ? COLOR.green : COLOR.tertiary;
|
|
402
|
-
html +=
|
|
403
|
-
`<div style="padding:3px 0;border-bottom:1px solid ${COLOR.border}">` +
|
|
404
|
-
`<div style="display:flex;align-items:baseline;gap:6px">` +
|
|
405
|
-
`<span style="${S.pill}background:${statusColor}20;color:${statusColor}">${esc(d.status)}</span>` +
|
|
406
|
-
`<span style="font-size:11px;font-weight:600;color:${COLOR.primary};flex:1">${esc(d.topic)}</span>` +
|
|
407
|
-
`<span style="font-size:10px;color:${COLOR.tertiary}">${esc(rel)}</span>` +
|
|
408
|
-
`</div>` +
|
|
409
|
-
`<div style="font-size:11px;color:${COLOR.secondary};margin-top:2px;line-height:1.4">${esc(d.decision.slice(0, 120))}</div>` +
|
|
410
|
-
`</div>`;
|
|
411
|
-
}
|
|
412
|
-
detailEl.innerHTML = html;
|
|
413
|
-
}
|
|
414
|
-
catch {
|
|
415
|
-
detailEl.innerHTML = `<div style="color:${COLOR.red}">Failed to load decisions.</div>`;
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
/** Load and render recent feed items inline for a connector row */
|
|
419
|
-
async loadConnectorDetail(detailEl, connector) {
|
|
420
|
-
try {
|
|
421
|
-
const res = await API.getConnectorFeed(connector, 5);
|
|
422
|
-
if (res.feed.length === 0) {
|
|
423
|
-
detailEl.innerHTML = `<div style="color:${COLOR.tertiary}">No recent items.</div>`;
|
|
424
|
-
return;
|
|
425
|
-
}
|
|
426
|
-
let html = '';
|
|
427
|
-
for (const ch of res.feed) {
|
|
428
|
-
for (const item of ch.items) {
|
|
429
|
-
const rel = relativeTime(item.timestamp);
|
|
430
|
-
const preview = item.content.replace(/\n/g, ' ').slice(0, 100);
|
|
431
|
-
html +=
|
|
432
|
-
`<div style="padding:3px 0;border-bottom:1px solid ${COLOR.border}">` +
|
|
433
|
-
`<div style="display:flex;align-items:baseline;gap:6px">` +
|
|
434
|
-
`<span style="font-size:10px;font-weight:600;color:${COLOR.primary}">${esc(item.author)}</span>` +
|
|
435
|
-
`<span style="font-size:10px;color:${COLOR.tertiary};background:${COLOR.bg};padding:0 4px;border-radius:2px">#${esc(ch.channel)}</span>` +
|
|
436
|
-
`<span style="font-size:10px;color:${COLOR.tertiary};margin-left:auto">${esc(rel)}</span>` +
|
|
437
|
-
`</div>` +
|
|
438
|
-
`<div style="font-size:11px;color:${COLOR.secondary};margin-top:2px">${esc(preview)}</div>` +
|
|
439
|
-
`</div>`;
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
detailEl.innerHTML = html || `<div style="color:${COLOR.tertiary}">No recent items.</div>`;
|
|
443
|
-
}
|
|
444
|
-
catch {
|
|
445
|
-
detailEl.innerHTML = `<div style="color:${COLOR.red}">Failed to load feed.</div>`;
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
destroy() {
|
|
449
|
-
this.eventSource?.close();
|
|
450
|
-
this.eventSource = null;
|
|
451
|
-
if (this.refreshTimer) {
|
|
452
|
-
clearInterval(this.refreshTimer);
|
|
453
|
-
this.refreshTimer = null;
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
}
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Entity Audit Viewer Module
|
|
3
|
-
*
|
|
4
|
-
* Minimal audit-run surface. DOM-free controller + render-state helpers so
|
|
5
|
-
* the module can be unit-tested in node.
|
|
6
|
-
*/
|
|
7
|
-
/* eslint-env browser */
|
|
8
|
-
import { API } from '../utils/api.js';
|
|
9
|
-
export class EntityAuditController {
|
|
10
|
-
async startRun() {
|
|
11
|
-
return API.post('/api/entities/audit/run', {});
|
|
12
|
-
}
|
|
13
|
-
async listRuns(limit = 25) {
|
|
14
|
-
return API.get('/api/entities/audit/runs', { limit });
|
|
15
|
-
}
|
|
16
|
-
async getRun(runId) {
|
|
17
|
-
return API.get(`/api/entities/audit/runs/${encodeURIComponent(runId)}`);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
export function buildAuditBannerState(run) {
|
|
21
|
-
if (run.status === 'running') {
|
|
22
|
-
return {
|
|
23
|
-
variant: 'neutral',
|
|
24
|
-
label: 'RUNNING',
|
|
25
|
-
headline: 'Audit run in progress',
|
|
26
|
-
hint: 'Wait for completion before starting a new run.',
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
if (run.status === 'failed' || run.status === 'timeout') {
|
|
30
|
-
return {
|
|
31
|
-
variant: 'danger',
|
|
32
|
-
label: run.status.toUpperCase(),
|
|
33
|
-
headline: `Audit run ${run.status}`,
|
|
34
|
-
hint: run.reason ?? 'Check server logs for details.',
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
switch (run.classification) {
|
|
38
|
-
case 'regressed':
|
|
39
|
-
return {
|
|
40
|
-
variant: 'danger',
|
|
41
|
-
label: 'REGRESSED',
|
|
42
|
-
headline: 'false_merge_rate spike detected',
|
|
43
|
-
hint: 'See #false-merge-spike in the entity substrate runbook.',
|
|
44
|
-
};
|
|
45
|
-
case 'improved':
|
|
46
|
-
return {
|
|
47
|
-
variant: 'success',
|
|
48
|
-
label: 'IMPROVED',
|
|
49
|
-
headline: 'All tracked metrics improved or held',
|
|
50
|
-
hint: 'Safe to promote the current baseline.',
|
|
51
|
-
};
|
|
52
|
-
case 'inconclusive':
|
|
53
|
-
return {
|
|
54
|
-
variant: 'warning',
|
|
55
|
-
label: 'INCONCLUSIVE',
|
|
56
|
-
headline: 'No baseline available for comparison',
|
|
57
|
-
hint: 'Re-run after the next ingest pass to establish a baseline.',
|
|
58
|
-
};
|
|
59
|
-
case 'stable':
|
|
60
|
-
default:
|
|
61
|
-
return {
|
|
62
|
-
variant: 'neutral',
|
|
63
|
-
label: 'STABLE',
|
|
64
|
-
headline: 'Metrics within tolerance',
|
|
65
|
-
hint: 'No action required.',
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
export function buildAuditMetricRows(run) {
|
|
70
|
-
const metrics = run.metrics ?? {};
|
|
71
|
-
return [
|
|
72
|
-
{
|
|
73
|
-
key: 'false_merge_rate',
|
|
74
|
-
label: 'False merge rate',
|
|
75
|
-
value: formatNumber(metrics.false_merge_rate),
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
key: 'cross_language_candidate_recall_at_10',
|
|
79
|
-
label: 'Cross-language recall@10',
|
|
80
|
-
value: formatNumber(metrics.cross_language_candidate_recall_at_10),
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
key: 'ontology_violation_count',
|
|
84
|
-
label: 'Ontology violations',
|
|
85
|
-
value: formatInteger(metrics.ontology_violation_count),
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
key: 'projection_fragmentation_rate',
|
|
89
|
-
label: 'Projection fragmentation',
|
|
90
|
-
value: formatNumber(metrics.projection_fragmentation_rate),
|
|
91
|
-
},
|
|
92
|
-
];
|
|
93
|
-
}
|
|
94
|
-
function formatNumber(value) {
|
|
95
|
-
if (typeof value !== 'number' || Number.isNaN(value))
|
|
96
|
-
return '—';
|
|
97
|
-
return value.toFixed(3);
|
|
98
|
-
}
|
|
99
|
-
function formatInteger(value) {
|
|
100
|
-
if (typeof value !== 'number' || Number.isNaN(value))
|
|
101
|
-
return '—';
|
|
102
|
-
return String(Math.round(value));
|
|
103
|
-
}
|
|
104
|
-
export function buildConcurrentRunLockoutMessage(run) {
|
|
105
|
-
return `Audit run ${run.id} is already in progress. Wait for it to finish before starting a new one.`;
|
|
106
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Entity Review Queue Module
|
|
3
|
-
*
|
|
4
|
-
* Minimal review surface for canonical entity resolution candidates.
|
|
5
|
-
* Data layer is intentionally DOM-free so it can be unit-tested in node.
|
|
6
|
-
*/
|
|
7
|
-
/* eslint-env browser */
|
|
8
|
-
import { API } from '../utils/api.js';
|
|
9
|
-
export class EntityReviewController {
|
|
10
|
-
endpoint;
|
|
11
|
-
constructor(opts = {}) {
|
|
12
|
-
this.endpoint = opts.endpoint ?? '/api/entities/candidates';
|
|
13
|
-
}
|
|
14
|
-
async fetchCandidates(params = {}) {
|
|
15
|
-
const query = {};
|
|
16
|
-
if (typeof params.limit === 'number')
|
|
17
|
-
query.limit = params.limit;
|
|
18
|
-
if (params.cursor)
|
|
19
|
-
query.cursor = params.cursor;
|
|
20
|
-
return API.get(this.endpoint, query);
|
|
21
|
-
}
|
|
22
|
-
async fetchCandidate(id) {
|
|
23
|
-
return API.get(`${this.endpoint}/${encodeURIComponent(id)}`);
|
|
24
|
-
}
|
|
25
|
-
async review(id, action, body = {}) {
|
|
26
|
-
return API.post(`${this.endpoint}/${encodeURIComponent(id)}/${action}`, body);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
export function buildListRenderState(response) {
|
|
30
|
-
return {
|
|
31
|
-
isEmpty: response.candidates.length === 0,
|
|
32
|
-
total: response.candidates.length,
|
|
33
|
-
rows: response.candidates.map((c) => ({
|
|
34
|
-
id: c.id,
|
|
35
|
-
scoreText: c.score_total.toFixed(2),
|
|
36
|
-
leftLabel: c.left_ref.label,
|
|
37
|
-
rightLabel: c.right_ref.label,
|
|
38
|
-
scopeLabel: c.scope_kind ? `${c.scope_kind}:${c.scope_id ?? ''}` : 'global',
|
|
39
|
-
createdAtIso: c.created_at,
|
|
40
|
-
})),
|
|
41
|
-
nextCursor: response.next_cursor,
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
export function buildDetailRenderState(response) {
|
|
45
|
-
const c = response.candidate;
|
|
46
|
-
return {
|
|
47
|
-
id: c.id,
|
|
48
|
-
leftLabel: c.left_ref.label,
|
|
49
|
-
rightLabel: c.right_ref.label,
|
|
50
|
-
scoreBreakdown: [
|
|
51
|
-
{ key: 'total', label: 'Total', value: c.score_total },
|
|
52
|
-
{ key: 'structural', label: 'Structural', value: c.score_structural },
|
|
53
|
-
{ key: 'string', label: 'String', value: c.score_string },
|
|
54
|
-
{ key: 'context', label: 'Context', value: c.score_context },
|
|
55
|
-
{ key: 'graph', label: 'Graph', value: c.score_graph },
|
|
56
|
-
{ key: 'embedding', label: 'Embedding', value: c.score_embedding },
|
|
57
|
-
],
|
|
58
|
-
ruleTrace: c.rule_trace,
|
|
59
|
-
evidence: c.evidence,
|
|
60
|
-
extractorVersion: c.extractor_version,
|
|
61
|
-
embeddingModelVersion: c.embedding_model_version ?? 'n/a',
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
export function renderEmptyState() {
|
|
65
|
-
return 'No candidates pending review.';
|
|
66
|
-
}
|