kernelbot 1.0.37 → 1.0.38

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.
@@ -0,0 +1,579 @@
1
+ /**
2
+ * KERNEL Dashboard — Page-specific render functions and state.
3
+ * Depends on window.KERNEL from shared.js.
4
+ */
5
+ (function() {
6
+ const { esc, formatDuration, timeAgo, formatBytes, barColor, makeBar, $,
7
+ startClock, setGauge, setMiniGauge, connectSSE, initParticleCanvas, initWaveform } = window.KERNEL;
8
+
9
+ // ── State ──
10
+ let configData = null, selfData = null;
11
+ let activeMemTab = 'episodic', activeShareTab = 'pending', activeJournalDate = 'today';
12
+ let lastKnowledge = [], lastSharesData = { pending: [], shared: [], todayCount: 0 };
13
+ let lastMemories = [];
14
+
15
+ // ── Init ──
16
+ startClock();
17
+ initParticleCanvas();
18
+ initWaveform();
19
+
20
+ // ── Init fetches ──
21
+ fetch('/api/config').then(r=>r.json()).then(d => { configData = d; renderConfig(d); renderIntegrations(d); });
22
+ fetch('/api/self').then(r=>r.json()).then(d => { selfData = d; renderSelf(d); });
23
+
24
+ // ── SSE ──
25
+ connectSSE(renderSnapshot);
26
+
27
+ // ── Render snapshot ──
28
+ function renderSnapshot(snap) {
29
+ renderSystem(snap.system);
30
+ renderJobs(snap.jobs);
31
+ renderAutomations(snap.automations);
32
+ renderLife(snap.life);
33
+ lastMemories = snap.memories || [];
34
+ if (activeMemTab === 'episodic') renderMemories(lastMemories);
35
+ else renderKnowledge(snap.knowledge || []);
36
+ lastKnowledge = snap.knowledge || [];
37
+ renderJournal(snap.journal);
38
+ renderEvolution(snap.evolution);
39
+ renderLessons(snap.evolution?.lessons || []);
40
+ renderConversations(snap.conversations);
41
+ renderCharacter(snap.character);
42
+ lastSharesData = snap.shares || { pending: [], shared: [], todayCount: 0 };
43
+ renderShares(lastSharesData);
44
+ renderCapabilities(snap.capabilities);
45
+ renderIdeas(snap.life?.ideas || []);
46
+ renderLogs(snap.logs);
47
+ renderTicker(snap.logs);
48
+ updateRightBar(snap);
49
+ updatePulse(snap);
50
+ }
51
+
52
+ // ── Hero pulse live stats ──
53
+ function updatePulse(snap) {
54
+ const pBrain = $('pulse-brain');
55
+ if (pBrain && configData) {
56
+ const model = configData.brain?.model || '--';
57
+ pBrain.textContent = model.length > 16 ? model.slice(0, 14) + '..' : model;
58
+ pBrain.className = 'pulse-val active';
59
+ }
60
+ const pAct = $('pulse-activity');
61
+ if (pAct) {
62
+ const life = snap.life;
63
+ if (life && life.paused) { pAct.textContent = 'PAUSED'; pAct.className = 'pulse-val warn'; }
64
+ else if (life && life.status === 'active') { pAct.textContent = (life.lastActivity || 'ACTIVE').toUpperCase(); pAct.className = 'pulse-val active'; }
65
+ else { pAct.textContent = 'IDLE'; pAct.className = 'pulse-val idle'; }
66
+ }
67
+ const pJobs = $('pulse-jobs');
68
+ if (pJobs) {
69
+ const jobs = snap.jobs || [];
70
+ const running = jobs.filter(j => j.status === 'running').length;
71
+ pJobs.textContent = running > 0 ? running + ' RUN' : jobs.length > 0 ? jobs.length + ' TOTAL' : '0';
72
+ pJobs.className = 'pulse-val' + (running > 0 ? ' active' : ' idle');
73
+ }
74
+ }
75
+
76
+ // ── Right bar quick stats ──
77
+ function updateRightBar(snap) {
78
+ const jobs = snap.jobs || [];
79
+ const running = jobs.filter(j => j.status === 'running').length;
80
+ const rbJobs = $('rb-jobs');
81
+ if (rbJobs) { rbJobs.textContent = running; rbJobs.className = 'r-val' + (running > 0 ? '' : ' zero'); }
82
+ const rbTotal = $('rb-total-jobs');
83
+ if (rbTotal) { rbTotal.textContent = jobs.length; rbTotal.className = 'r-val' + (jobs.length > 0 ? '' : ' zero'); }
84
+
85
+ const lifeDot = $('rb-life-dot');
86
+ if (lifeDot) {
87
+ const isActive = snap.life && !snap.life.paused && snap.life.status === 'active';
88
+ const isPaused = snap.life && snap.life.paused;
89
+ lifeDot.className = 'r-dot ' + (isActive ? 'on' : isPaused ? 'paused' : 'off');
90
+ }
91
+ const rbActs = $('rb-activities');
92
+ if (rbActs) { const total = snap.life?.totalActivities || 0; rbActs.textContent = total; rbActs.className = 'r-val' + (total > 0 ? '' : ' zero'); }
93
+
94
+ const rbMem = $('rb-mem');
95
+ if (rbMem) { const c = (snap.memories || []).length; rbMem.textContent = c; rbMem.className = 'r-val' + (c > 0 ? '' : ' zero'); }
96
+
97
+ const rbShares = $('rb-shares');
98
+ if (rbShares) { const c = snap.shares?.pending?.length || 0; rbShares.textContent = c; rbShares.className = 'r-val' + (c > 0 ? '' : ' zero'); }
99
+
100
+ const rbEvo = $('rb-evo');
101
+ if (rbEvo) { const c = snap.evolution?.stats?.totalProposals || 0; rbEvo.textContent = c; rbEvo.className = 'r-val' + (c > 0 ? '' : ' zero'); }
102
+
103
+ const rbConvs = $('rb-convs');
104
+ if (rbConvs) { const c = (snap.conversations || []).length; rbConvs.textContent = c; rbConvs.className = 'r-val' + (c > 0 ? '' : ' zero'); }
105
+ }
106
+
107
+ function renderSystem(sys) {
108
+ if (!sys) return;
109
+ const hdrUp = $('hdr-uptime');
110
+ if (hdrUp) hdrUp.textContent = formatDuration(sys.uptime);
111
+ const hdrPid = $('hdr-pid');
112
+ if (hdrPid) hdrPid.textContent = sys.pid;
113
+ const hdrNode = $('hdr-node');
114
+ if (hdrNode) hdrNode.textContent = sys.nodeVersion;
115
+
116
+ const cores = sys.cpu.cores;
117
+ const cpu1 = sys.cpu.load1 / cores * 100;
118
+ const cpu5 = sys.cpu.load5 / cores * 100;
119
+ const ramPct = parseFloat(sys.ram.percent);
120
+ const heapPct = sys.process.heapTotal > 0 ? (sys.process.heap / sys.process.heapTotal * 100) : 0;
121
+
122
+ setGauge('g-cpu1', cpu1, sys.cpu.load1.toFixed(2) + '/' + cores);
123
+ setGauge('g-cpu5', cpu5, sys.cpu.load5.toFixed(2) + '/' + cores);
124
+ setGauge('g-ram', ramPct, formatBytes(sys.ram.used));
125
+ setGauge('g-heap', heapPct, formatBytes(sys.process.heap));
126
+ setMiniGauge('sb-cpu', cpu1);
127
+ setMiniGauge('sb-ram', ramPct);
128
+
129
+ const pCpu = $('pulse-cpu');
130
+ if (pCpu) { pCpu.textContent = cpu1.toFixed(0) + '%'; pCpu.className = 'pulse-val' + (cpu1 >= 80 ? ' warn' : cpu1 > 0 ? ' active' : ' idle'); }
131
+ const pHeap = $('pulse-heap');
132
+ if (pHeap) { pHeap.textContent = formatBytes(sys.process.heap); pHeap.className = 'pulse-val' + (heapPct >= 80 ? ' warn' : ' active'); }
133
+ const pUp = $('pulse-uptime');
134
+ if (pUp) { pUp.textContent = formatDuration(sys.uptime); pUp.className = 'pulse-val active'; }
135
+ }
136
+
137
+ function renderConfig(cfg) {
138
+ if (!cfg) return;
139
+ let h = '';
140
+ h += `<div class="row"><span class="k">Orch Provider</span><span class="v">${esc(cfg.orchestrator.provider)}</span></div>`;
141
+ h += `<div class="row"><span class="k">Orch Model</span><span class="v">${esc(cfg.orchestrator.model)}</span></div>`;
142
+ h += `<div class="row"><span class="k">Orch Key</span><span class="v">${esc(cfg.orchestrator.api_key)}</span></div>`;
143
+ h += `<div class="row"><span class="k">Brain Provider</span><span class="v">${esc(cfg.brain.provider)}</span></div>`;
144
+ h += `<div class="row"><span class="k">Brain Model</span><span class="v">${esc(cfg.brain.model)}</span></div>`;
145
+ h += `<div class="row"><span class="k">Brain Key</span><span class="v">${esc(cfg.brain.api_key)}</span></div>`;
146
+ h += `<div class="row"><span class="k">Max Tool Depth</span><span class="v">${cfg.brain.max_tool_depth || '--'}</span></div>`;
147
+ if (cfg.swarm) {
148
+ h += `<div class="row"><span class="k">Max Jobs</span><span class="v">${cfg.swarm.max_concurrent_jobs || '--'}</span></div>`;
149
+ h += `<div class="row"><span class="k">Job Timeout</span><span class="v">${cfg.swarm.job_timeout_seconds || '--'}s</span></div>`;
150
+ }
151
+ if (cfg.claude_code) {
152
+ h += `<div class="row"><span class="k">Claude Code</span><span class="v">${esc(cfg.claude_code.model)}</span></div>`;
153
+ h += `<div class="row"><span class="k">CC Auth</span><span class="v">${esc(cfg.claude_code.auth_mode)}</span></div>`;
154
+ h += `<div class="row"><span class="k">CC Max Turns</span><span class="v">${cfg.claude_code.max_turns || '--'}</span></div>`;
155
+ }
156
+ if (cfg.life) {
157
+ h += `<div class="row"><span class="k">Life</span><span class="v">${cfg.life.enabled ? 'ENABLED' : 'DISABLED'}</span></div>`;
158
+ if (cfg.life.self_coding) h += `<div class="row"><span class="k">Self-Coding</span><span class="v">${cfg.life.self_coding.enabled ? 'ON' : 'OFF'}</span></div>`;
159
+ }
160
+ h += `<div class="row"><span class="k">Allowed Users</span><span class="v">${cfg.telegram.allowed_users}</span></div>`;
161
+ $('config-body').innerHTML = h;
162
+ }
163
+
164
+ function renderIntegrations(cfg) {
165
+ if (!cfg || !cfg.integrations) return;
166
+ const items = [
167
+ { key: 'telegram', label: 'TELEGRAM' },
168
+ { key: 'github', label: 'GITHUB' },
169
+ { key: 'claude_code', label: 'CLAUDE' },
170
+ { key: 'linkedin', label: 'LINKEDIN' },
171
+ { key: 'x', label: 'X' },
172
+ { key: 'jira', label: 'JIRA' },
173
+ { key: 'elevenlabs', label: '11LABS' },
174
+ ];
175
+ let h = '';
176
+ for (const item of items) {
177
+ const on = cfg.integrations[item.key];
178
+ h += `<div class="integ-item"><span class="integ-dot ${on ? 'on' : 'off'}"></span>${item.label}</div>`;
179
+ }
180
+ $('integrations-bar').innerHTML = h;
181
+ }
182
+
183
+ function renderJobs(jobs) {
184
+ if (!jobs || !jobs.length) { $('jobs-body').innerHTML = '<div class="empty-msg">NO JOBS</div>'; updateJobsTag(0, 0); return; }
185
+ const order = { running: 0, queued: 1, completed: 2, failed: 3, cancelled: 4 };
186
+ const sorted = [...jobs].sort((a, b) => (order[a.status] ?? 9) - (order[b.status] ?? 9) || (b.createdAt || 0) - (a.createdAt || 0));
187
+ const running = sorted.filter(j => j.status === 'running').length;
188
+ updateJobsTag(sorted.length, running);
189
+ let h = '';
190
+ for (const j of sorted) {
191
+ h += `<div class="job-row" onclick="this.querySelector('.job-detail')?.classList.toggle('open')">`;
192
+ h += `<div class="job-meta"><span class="job-id">${esc(j.id)}</span><span class="job-type">${esc(j.type)}</span><span class="badge ${j.status}">${j.status.toUpperCase()}</span><span style="color:var(--dim)">${formatDuration(j.duration)}</span><span style="color:var(--dim)">LLM:${j.llmCalls||0} T:${j.toolCalls||0}</span></div>`;
193
+ h += `<div class="job-task">${esc((j.task||'').slice(0,120))}</div>`;
194
+ if (j.status === 'running' && j.lastThinking) h += `<div class="job-sub">${esc(j.lastThinking.slice(0,100))}</div>`;
195
+ if (j.status === 'running' && j.progress?.length) h += `<div class="job-sub">${esc(j.progress[j.progress.length-1])}</div>`;
196
+ if (j.status === 'completed' && j.completedAt) h += `<div class="job-sub">Completed ${timeAgo(j.completedAt)}</div>`;
197
+ if (j.status === 'failed') h += `<div class="job-sub" style="color:var(--red)">Failed ${timeAgo(j.completedAt)}</div>`;
198
+ if (j.status === 'cancelled') h += `<div class="job-sub" style="color:var(--amber)">Cancelled ${timeAgo(j.completedAt)}</div>`;
199
+ h += '<div class="job-detail">';
200
+ if (j.error) h += `<div class="job-detail-section"><div class="job-detail-label">ERROR</div><div class="job-detail-val error">${esc(j.error)}</div></div>`;
201
+ if (j.context) h += `<div class="job-detail-section"><div class="job-detail-label">CONTEXT</div><div class="job-detail-val">${esc(j.context.slice(0,300))}</div></div>`;
202
+ if (j.dependsOn?.length) h += `<div class="job-detail-section"><div class="job-detail-label">DEPENDS ON</div><div class="job-detail-val">${j.dependsOn.map(d => `<span class="tool-tag">${esc(d)}</span>`).join('')}</div></div>`;
203
+ if (j.timeoutMs) h += `<div class="job-detail-section"><div class="job-detail-label">TIMEOUT</div><div class="job-detail-val">${formatDuration(j.timeoutMs/1000)}</div></div>`;
204
+ if (j.structuredResult) {
205
+ const sr = j.structuredResult;
206
+ if (sr.summary) h += `<div class="job-detail-section"><div class="job-detail-label">SUMMARY</div><div class="job-detail-val">${esc(sr.summary)}</div></div>`;
207
+ if (sr.details) h += `<div class="job-detail-section"><div class="job-detail-label">DETAILS</div><div class="job-detail-val">${esc(sr.details.slice(0,500))}</div></div>`;
208
+ if (sr.toolsUsed?.length) h += `<div class="job-detail-section"><div class="job-detail-label">TOOLS USED</div><div class="job-detail-val">${sr.toolsUsed.map(t => `<span class="tool-tag">${esc(t)}</span>`).join('')}</div></div>`;
209
+ if (sr.artifacts?.length) h += `<div class="job-detail-section"><div class="job-detail-label">ARTIFACTS</div><div class="job-detail-val">${sr.artifacts.map(a => `<span class="artifact-tag">${esc(typeof a === 'string' ? a : JSON.stringify(a).slice(0,60))}</span>`).join('')}</div></div>`;
210
+ if (sr.errors?.length) h += `<div class="job-detail-section"><div class="job-detail-label">ERRORS</div><div class="job-detail-val error">${sr.errors.map(e => esc(e)).join('<br>')}</div></div>`;
211
+ if (sr.followUp) h += `<div class="job-detail-section"><div class="job-detail-label">FOLLOW-UP</div><div class="job-detail-val">${esc(sr.followUp)}</div></div>`;
212
+ }
213
+ if (j.progress?.length > 1) {
214
+ h += `<div class="job-detail-section"><div class="job-detail-label">PROGRESS (${j.progress.length})</div><div class="job-progress-timeline">`;
215
+ for (const p of j.progress) h += `<div>${esc(p)}</div>`;
216
+ h += '</div></div>';
217
+ }
218
+ h += '</div></div>';
219
+ }
220
+ $('jobs-body').innerHTML = h;
221
+ }
222
+
223
+ function updateJobsTag(total, running) {
224
+ const tag = $('jobs-count-tag');
225
+ if (tag) tag.textContent = 'SWARM // ' + total + (running > 0 ? ' (' + running + ' ACTIVE)' : '');
226
+ }
227
+
228
+ function renderAutomations(autos) {
229
+ if (!autos || !autos.length) { $('auto-body').innerHTML = '<div class="empty-msg">NO AUTOMATIONS</div>'; return; }
230
+ let h = '';
231
+ for (const a of autos) {
232
+ const st = a.enabled ? '<span style="color:var(--accent)">ON</span>' : '<span style="color:var(--amber)">OFF</span>';
233
+ const sched = a.schedule ? (a.schedule.expression || a.schedule.type + (a.schedule.minutes ? ' ' + a.schedule.minutes + 'm' : '')) : '--';
234
+ h += `<div class="auto-item"><div><span class="auto-name">${esc(a.name)}</span> ${st}</div><div class="auto-detail">Sched: ${esc(sched)} | Runs: ${a.runCount}</div>`;
235
+ if (a.lastError) h += `<div class="auto-detail" style="color:var(--red)">ERR: ${esc(a.lastError.slice(0,60))}</div>`;
236
+ h += '</div>';
237
+ }
238
+ $('auto-body').innerHTML = h;
239
+ }
240
+
241
+ function renderLife(life) {
242
+ if (!life || life.status === 'unknown') { $('life-body').innerHTML = '<div class="empty-msg">UNAVAILABLE</div>'; return; }
243
+ let h = '';
244
+ const sc = life.paused ? 'paused' : (life.status === 'active' ? 'active' : 'idle');
245
+ h += `<div class="life-status ${sc}">${(life.paused ? 'PAUSED' : life.status || 'IDLE').toUpperCase()}</div>`;
246
+ h += `<div class="row"><span class="k">Total Activities</span><span class="v">${life.totalActivities || 0}</span></div>`;
247
+ h += `<div class="row"><span class="k">Last Activity</span><span class="v">${esc(life.lastActivity || 'none')} (${esc(life.lastActivityAgo || 'never')})</span></div>`;
248
+ h += `<div class="row"><span class="k">Wake-Up</span><span class="v">${esc(life.lastWakeUpAgo || 'never')}</span></div>`;
249
+ if (life.activityCounts) {
250
+ const counts = life.activityCounts;
251
+ const max = Math.max(1, ...Object.values(counts));
252
+ h += '<div style="margin-top:6px">';
253
+ for (const [n, c] of Object.entries(counts)) {
254
+ h += `<div class="activity-bar"><span class="name">${esc(n)}</span><span class="bar"><span class="fill" style="width:${(c/max*100).toFixed(0)}%"></span></span><span class="count">${c}</span></div>`;
255
+ }
256
+ h += '</div>';
257
+ }
258
+ if (life.cooldowns) {
259
+ h += '<div style="margin-top:8px;border-top:1px solid rgba(57,255,20,0.05);padding-top:6px">';
260
+ h += '<div style="font-family:var(--font-hud);font-size:7px;letter-spacing:1.5px;color:var(--dim);margin-bottom:4px">COOLDOWNS</div>';
261
+ const cdNames = { journal: 'JOURNAL', self_code: 'SELF CODE', code_review: 'CODE REV', reflect: 'REFLECT' };
262
+ const cdMaxMs = { journal: 4*3600000, self_code: 2*3600000, code_review: 4*3600000, reflect: 4*3600000 };
263
+ for (const [key, label] of Object.entries(cdNames)) {
264
+ const ms = life.cooldowns[key] || 0;
265
+ const maxMs = cdMaxMs[key];
266
+ const pct = ms > 0 ? (ms / maxMs * 100) : 0;
267
+ const ready = ms <= 0;
268
+ const timeStr = ready ? 'READY' : formatDuration(Math.ceil(ms / 1000));
269
+ h += `<div class="cooldown-row"><span class="cooldown-label">${label}</span><div class="cooldown-bar-track"><div class="cooldown-fill${ready ? ' ready' : ''}" style="width:${ready ? 100 : 100 - pct}%"></div></div><span class="cooldown-time">${timeStr}</span></div>`;
270
+ }
271
+ h += '</div>';
272
+ }
273
+ $('life-body').innerHTML = h;
274
+ }
275
+
276
+ function renderEvolution(evo) {
277
+ if (!evo) { $('evo-body').innerHTML = '<div class="empty-msg">NO DATA</div>'; return; }
278
+ let h = '';
279
+ if (evo.stats) {
280
+ const s = evo.stats;
281
+ const total = (s.merged||0) + (s.rejected||0) + (s.failed||0);
282
+ const rate = s.successRate || 0;
283
+ const circ = 2 * Math.PI * 30;
284
+ const merged = total > 0 ? (s.merged||0)/total*circ : 0;
285
+ const rejected = total > 0 ? (s.rejected||0)/total*circ : 0;
286
+ const failed = total > 0 ? (s.failed||0)/total*circ : 0;
287
+ h += '<div class="evo-ring-row">';
288
+ h += '<div class="evo-ring"><svg viewBox="0 0 80 80"><circle class="track" cx="40" cy="40" r="30"/>';
289
+ if (total > 0) {
290
+ h += `<circle cx="40" cy="40" r="30" fill="none" stroke="#39ff14" stroke-width="6" stroke-dasharray="${merged} ${circ-merged}" stroke-dashoffset="0"/>`;
291
+ h += `<circle cx="40" cy="40" r="30" fill="none" stroke="#ffb000" stroke-width="6" stroke-dasharray="${rejected} ${circ-rejected}" stroke-dashoffset="${-merged}"/>`;
292
+ h += `<circle cx="40" cy="40" r="30" fill="none" stroke="#ff3333" stroke-width="6" stroke-dasharray="${failed} ${circ-failed}" stroke-dashoffset="${-(merged+rejected)}"/>`;
293
+ }
294
+ h += '</svg><div class="evo-ring-center"><span class="pct">' + rate.toFixed(0) + '%</span><span class="lbl">SUCCESS</span></div></div>';
295
+ h += '<div class="evo-legend">';
296
+ h += `<div class="evo-legend-item"><span class="evo-legend-dot" style="background:#39ff14"></span>${s.merged||0} Merged</div>`;
297
+ h += `<div class="evo-legend-item"><span class="evo-legend-dot" style="background:#ffb000"></span>${s.rejected||0} Rejected</div>`;
298
+ h += `<div class="evo-legend-item"><span class="evo-legend-dot" style="background:#ff3333"></span>${s.failed||0} Failed</div>`;
299
+ h += `<div class="evo-legend-item" style="color:var(--text-bright);margin-top:2px">${s.totalProposals||0} Total</div>`;
300
+ h += '</div></div>';
301
+ }
302
+ if (evo.active) {
303
+ const a = evo.active;
304
+ h += `<div class="evo-proposal" style="border-left:2px solid var(--accent);padding-left:6px"><div style="color:var(--accent);font-family:var(--font-hud);font-size:9px;letter-spacing:1px">ACTIVE: ${esc(a.status)}</div><div style="color:var(--text);font-size:11px">${esc(a.trigger||'')}</div>`;
305
+ if (a.branch) h += `<div style="color:var(--amber);font-size:10px">Branch: ${esc(a.branch)}</div>`;
306
+ h += '</div>';
307
+ }
308
+ if (evo.recent?.length) {
309
+ for (const p of evo.recent) {
310
+ h += `<div class="evo-proposal"><span class="badge ${p.status==='merged'?'completed':p.status==='failed'?'failed':p.status==='rejected'?'cancelled':'queued'}">${esc(p.status)}</span> <span style="color:var(--text);font-size:11px">${esc((p.trigger||'').slice(0,50))}</span> <span style="color:var(--dim);font-size:9px">${timeAgo(p.createdAt)}</span></div>`;
311
+ }
312
+ }
313
+ if (!evo.stats?.totalProposals && !evo.active && !evo.recent?.length) h = '<div class="empty-msg">NO DATA</div>';
314
+ $('evo-body').innerHTML = h;
315
+ }
316
+
317
+ function renderMemories(mems) {
318
+ if (!mems || !mems.length) { $('mem-body').innerHTML = '<div class="empty-msg">NO MEMORIES</div>'; return; }
319
+ let h = '';
320
+ for (const m of mems) {
321
+ const imp = m.importance || 0;
322
+ const impCls = imp >= 7 ? 'high' : '';
323
+ h += `<div class="mem-item"><span class="mem-time">${timeAgo(m.timestamp)}</span> <span class="mem-type ${esc(m.type||'interaction')}">${esc((m.type||'').toUpperCase())}</span>`;
324
+ if (imp > 0) h += `<span class="mem-importance ${impCls}">IMP:${imp}</span>`;
325
+ h += `<div class="mem-summary">${esc((m.summary||'').slice(0,120))}</div>`;
326
+ if (m.tags?.length) {
327
+ h += '<div class="mem-tags">';
328
+ for (const t of m.tags.slice(0, 5)) h += `<span class="mem-tag">${esc(t)}</span>`;
329
+ h += '</div>';
330
+ }
331
+ h += '</div>';
332
+ }
333
+ $('mem-body').innerHTML = h;
334
+ }
335
+
336
+ function renderJournal(j) {
337
+ if (!j) { $('journal-body').innerHTML = '<div class="empty-msg">NO JOURNAL DATA</div>'; return; }
338
+ const tabsEl = $('journal-tabs');
339
+ if (tabsEl && j.dates?.length) {
340
+ let th = `<span class="panel-tab ${activeJournalDate === 'today' ? 'active' : ''}" data-tab="today">TODAY</span>`;
341
+ for (const d of j.dates.slice(0, 6)) {
342
+ if (d === j.dates[0] && activeJournalDate === 'today') continue;
343
+ const short = d.slice(5);
344
+ th += `<span class="panel-tab ${activeJournalDate === d ? 'active' : ''}" data-tab="${esc(d)}">${short}</span>`;
345
+ }
346
+ tabsEl.innerHTML = th;
347
+ }
348
+ const tag = $('journal-tag');
349
+ if (tag) tag.textContent = activeJournalDate === 'today' ? 'TODAY' : activeJournalDate;
350
+ const content = activeJournalDate === 'today' ? j.content : (j.recent?.find(r => r.date === activeJournalDate)?.content);
351
+ if (!content) { $('journal-body').innerHTML = '<div class="empty-msg">NO ENTRY</div>'; return; }
352
+ const escaped = esc(content).replace(/(\d{1,2}:\d{2}(?::\d{2})?(?:\s*(?:AM|PM))?)/gi, '<span class="ts-line">$1</span>');
353
+ $('journal-body').innerHTML = `<div class="md-content">${escaped}</div>`;
354
+ }
355
+
356
+ function renderShares(data) {
357
+ if (!data) { $('shares-body').innerHTML = '<div class="empty-msg">NO SHARES</div>'; return; }
358
+ const tag = $('shares-tag');
359
+ if (tag) tag.textContent = `OUTBOUND // ${data.todayCount || 0} TODAY`;
360
+ const list = activeShareTab === 'pending' ? (data.pending || []) : (data.shared || []);
361
+ if (!list.length) { $('shares-body').innerHTML = `<div class="empty-msg">${activeShareTab === 'pending' ? 'NO PENDING SHARES' : 'NO HISTORY'}</div>`; return; }
362
+ let h = '';
363
+ for (const s of list) {
364
+ const cls = activeShareTab === 'shared' ? 'share-item share-shared' : 'share-item';
365
+ h += `<div class="${cls}"><span class="share-pri ${esc(s.priority||'low')}">[${(s.priority||'low').toUpperCase()}]</span> <span style="color:var(--dim);font-size:9px">${esc(s.source||'')} · ${timeAgo(s.createdAt)}</span><div style="color:var(--text);font-size:11px">${esc((s.content||'').slice(0,100))}</div></div>`;
366
+ }
367
+ $('shares-body').innerHTML = h;
368
+ }
369
+
370
+ function renderCharacter(ch) {
371
+ if (!ch) { $('char-body').innerHTML = '<div class="empty-msg">NO DATA</div>'; return; }
372
+ let h = '';
373
+ if (ch.active) {
374
+ h += `<div class="char-active">${esc(ch.active.emoji||'')} ${esc(ch.active.name)}</div>`;
375
+ if (ch.active.tagline) h += `<div style="color:var(--text);font-size:11px;margin-bottom:4px;font-style:italic">${esc(ch.active.tagline)}</div>`;
376
+ }
377
+ if (ch.origin) h += `<div class="row"><span class="k">Origin</span><span class="v">${esc(ch.origin)}</span></div>`;
378
+ if (ch.age) h += `<div class="row"><span class="k">Age</span><span class="v">${esc(ch.age)}</span></div>`;
379
+ if (ch.lastActiveAt) h += `<div class="row"><span class="k">Last Active</span><span class="v">${timeAgo(ch.lastActiveAt)}</span></div>`;
380
+ if (ch.characters?.length) {
381
+ h += '<div style="margin-top:4px;border-top:1px solid rgba(57,255,20,0.05);padding-top:4px">';
382
+ for (const c of ch.characters) {
383
+ const a = ch.active && c.id === ch.active.id;
384
+ h += `<div class="char-item ${a?'active':''}">${esc(c.emoji||'')} ${esc(c.name)}</div>`;
385
+ }
386
+ h += '</div>';
387
+ }
388
+ $('char-body').innerHTML = h;
389
+ }
390
+
391
+ function renderConversations(convs) {
392
+ if (!convs || !convs.length) { $('conv-body').innerHTML = '<div class="empty-msg">NONE</div>'; return; }
393
+ let h = '';
394
+ for (const c of convs) {
395
+ h += `<div class="conv-item"><span class="chat-id ${c.chatId==='__life__'?'life':''}">${esc(c.chatId)}</span><span style="color:var(--dim)">${c.messageCount} msgs · ${timeAgo(c.lastTimestamp)}</span></div>`;
396
+ if (c.userMessages || c.assistantMessages) {
397
+ h += `<div style="font-size:9px;color:var(--dim);padding-left:4px">USR:${c.userMessages||0} BOT:${c.assistantMessages||0}`;
398
+ if (c.activeSkill) h += ` <span style="color:var(--magenta)">SKILL:${esc(c.activeSkill)}</span>`;
399
+ h += '</div>';
400
+ }
401
+ }
402
+ $('conv-body').innerHTML = h;
403
+ }
404
+
405
+ function renderSelf(s) {
406
+ if (!s || !s.content) {
407
+ $('self-body').innerHTML = '<div class="empty-msg">NO DATA</div>';
408
+ $('self-goals-body').innerHTML = '<div class="empty-msg">NO DATA</div>';
409
+ return;
410
+ }
411
+ const lines = s.content.split('\n');
412
+ let awareness = [], goals = [], inGoals = false;
413
+ for (const line of lines) {
414
+ if (/^#+\s*(goals|aspirations|objectives)/i.test(line)) { inGoals = true; goals.push(line); continue; }
415
+ if (inGoals && /^#+\s/.test(line) && !/goals|aspirations|objectives/i.test(line)) { inGoals = false; }
416
+ if (inGoals) goals.push(line); else awareness.push(line);
417
+ }
418
+ $('self-body').innerHTML = `<div class="md-content">${esc(awareness.join('\n') || s.content)}</div>`;
419
+ $('self-goals-body').innerHTML = goals.length ? `<div class="md-content">${esc(goals.join('\n'))}</div>` : '<div class="empty-msg">NO GOALS DEFINED</div>';
420
+ }
421
+
422
+ function renderCapabilities(caps) {
423
+ if (!caps || !caps.workers) { $('caps-body').innerHTML = '<div class="empty-msg">NO DATA</div>'; return; }
424
+ const tag = $('caps-tag');
425
+ if (tag) tag.textContent = `WORKERS // ${caps.totalTools || 0} TOOLS`;
426
+ let h = '<div class="worker-grid">';
427
+ const workerList = Array.isArray(caps.workers) ? caps.workers : Object.values(caps.workers);
428
+ for (const w of workerList) {
429
+ h += '<div class="worker-card">';
430
+ h += `<div class="worker-card-head"><span class="worker-emoji">${esc(w.emoji)}</span><span class="worker-name">${esc(w.label)}</span></div>`;
431
+ h += `<div class="worker-desc">${esc(w.description)}</div>`;
432
+ h += `<div class="worker-meta"><span>Tools: <span class="v">${w.tools?.length || 0}</span></span><span>Timeout: <span class="v">${formatDuration(w.timeout)}</span></span></div>`;
433
+ if (w.tools?.length) {
434
+ h += '<div style="margin-top:3px">';
435
+ for (const t of w.tools.slice(0, 8)) h += `<span class="tool-tag" style="font-size:8px">${esc(t)}</span>`;
436
+ if (w.tools.length > 8) h += `<span style="color:var(--dim);font-size:8px">+${w.tools.length - 8}</span>`;
437
+ h += '</div>';
438
+ }
439
+ h += '</div>';
440
+ }
441
+ h += '</div>';
442
+ $('caps-body').innerHTML = h;
443
+ }
444
+
445
+ function renderKnowledge(topics) {
446
+ if (!topics || !topics.length) { $('mem-body').innerHTML = '<div class="empty-msg">NO KNOWLEDGE</div>'; return; }
447
+ const tag = $('mem-tag');
448
+ if (tag) tag.textContent = 'SEMANTIC';
449
+ let h = '';
450
+ for (const t of topics) {
451
+ h += '<div class="knowledge-item">';
452
+ h += `<span class="knowledge-topic">${esc(t.topic)}</span>`;
453
+ if (t.sources?.length) h += ` <span style="color:var(--dim);font-size:8px">${t.sources.length} sources</span>`;
454
+ if (t.summary) h += `<div class="knowledge-summary">${esc(t.summary.slice(0, 120))}</div>`;
455
+ if (t.relatedTopics?.length) h += `<div class="knowledge-related">Related: ${t.relatedTopics.slice(0, 4).map(r => esc(r)).join(', ')}</div>`;
456
+ if (t.learnedAt) h += `<div style="font-size:8px;color:var(--dim)">${timeAgo(new Date(t.learnedAt).getTime())}</div>`;
457
+ h += '</div>';
458
+ }
459
+ $('mem-body').innerHTML = h;
460
+ }
461
+
462
+ function renderIdeas(ideas) {
463
+ if (!ideas || !ideas.length) { $('ideas-body').innerHTML = '<div class="empty-msg">NO IDEAS</div>'; return; }
464
+ const tag = $('ideas-tag');
465
+ if (tag) tag.textContent = `BRAIN // ${ideas.length}`;
466
+ let h = '';
467
+ for (const idea of ideas) {
468
+ if (typeof idea === 'string') {
469
+ const isImprove = idea.startsWith('IMPROVE:');
470
+ h += `<div class="idea-item">${isImprove ? '<span class="improve-tag">IMPROVE</span>' : ''}${esc(isImprove ? idea.slice(8).trim() : idea)}</div>`;
471
+ } else {
472
+ h += `<div class="idea-item">`;
473
+ if (idea.type) h += `<span class="improve-tag">${esc(idea.type.toUpperCase())}</span>`;
474
+ h += `${esc(idea.text || idea.content || JSON.stringify(idea).slice(0, 80))}`;
475
+ if (idea.timestamp) h += `<span class="idea-time">${timeAgo(idea.timestamp)}</span>`;
476
+ h += '</div>';
477
+ }
478
+ }
479
+ $('ideas-body').innerHTML = h;
480
+ }
481
+
482
+ function renderLessons(lessons) {
483
+ if (!lessons || !lessons.length) { $('lessons-body').innerHTML = '<div class="empty-msg">NO LESSONS</div>'; return; }
484
+ const tag = $('lessons-tag');
485
+ if (tag) tag.textContent = `WISDOM // ${lessons.length}`;
486
+ let h = '';
487
+ for (const l of lessons) {
488
+ h += '<div class="lesson-item">';
489
+ if (l.category) h += `<span class="lesson-cat">${esc(l.category.toUpperCase())}</span>`;
490
+ h += `<span class="lesson-text">${esc((l.lesson || l.text || '').slice(0, 120))}</span>`;
491
+ if (l.importance) h += `<span class="lesson-importance">IMP:${l.importance}</span>`;
492
+ h += '</div>';
493
+ }
494
+ $('lessons-body').innerHTML = h;
495
+ }
496
+
497
+ // ── Tab click handlers ──
498
+ document.addEventListener('click', (e) => {
499
+ const tab = e.target.closest('.panel-tab');
500
+ if (!tab) return;
501
+ const parent = tab.closest('.panel-tabs');
502
+ if (!parent) return;
503
+ const tabId = parent.id;
504
+ const val = tab.dataset.tab;
505
+
506
+ if (tabId === 'mem-tabs') {
507
+ activeMemTab = val;
508
+ parent.querySelectorAll('.panel-tab').forEach(t => t.classList.remove('active'));
509
+ tab.classList.add('active');
510
+ const memTag = $('mem-tag');
511
+ if (memTag) memTag.textContent = val === 'episodic' ? 'EPISODIC' : 'SEMANTIC';
512
+ if (val === 'episodic') renderMemories(lastMemories);
513
+ else renderKnowledge(lastKnowledge);
514
+ } else if (tabId === 'shares-tabs') {
515
+ activeShareTab = val;
516
+ parent.querySelectorAll('.panel-tab').forEach(t => t.classList.remove('active'));
517
+ tab.classList.add('active');
518
+ renderShares(lastSharesData);
519
+ } else if (tabId === 'journal-tabs') {
520
+ activeJournalDate = val;
521
+ parent.querySelectorAll('.panel-tab').forEach(t => t.classList.remove('active'));
522
+ tab.classList.add('active');
523
+ }
524
+ });
525
+
526
+ function renderLogs(logs) {
527
+ if (!logs || !logs.length) { $('logs-body').innerHTML = '<div class="empty-msg">NO LOGS</div>'; return; }
528
+ const body = $('logs-body');
529
+ const atBottom = body.scrollTop + body.clientHeight >= body.scrollHeight - 20;
530
+ let h = '';
531
+ for (const l of logs) {
532
+ const lvl = (l.level||'info').toLowerCase();
533
+ const ts = l.timestamp ? l.timestamp.replace(/^.*T/,'').replace(/\..*$/,'') : '';
534
+ h += `<div class="log-line"><span class="ts">[${esc(ts)}]</span> <span class="lvl-${lvl}">${esc(lvl.toUpperCase().padEnd(5))}</span> <span class="msg">${esc(l.message)}</span></div>`;
535
+ }
536
+ body.innerHTML = h;
537
+ if (atBottom) body.scrollTop = body.scrollHeight;
538
+ }
539
+
540
+ function renderTicker(logs) {
541
+ if (!logs || !logs.length) return;
542
+ const last = logs.slice(-20);
543
+ let items = '';
544
+ for (const l of last) {
545
+ const lvl = (l.level||'info').toLowerCase();
546
+ const ts = l.timestamp ? l.timestamp.replace(/^.*T/,'').replace(/\..*$/,'') : '';
547
+ items += `<span class="ticker-item"><span class="ts">[${esc(ts)}]</span> <span class="lvl-${lvl}">${esc(lvl.toUpperCase())}</span> <span class="msg">${esc((l.message||'').slice(0,80))}</span></span>`;
548
+ }
549
+ $('ticker-track').innerHTML = items + items;
550
+ const count = last.length;
551
+ document.documentElement.style.setProperty('--ticker-duration', Math.max(count * 4, 30) + 's');
552
+ }
553
+
554
+ // ── Nav active state sync (sidebar + top bar) ──
555
+ document.querySelectorAll('.nav-item[href^="#"]').forEach(item => {
556
+ item.addEventListener('click', () => {
557
+ document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
558
+ item.classList.add('active');
559
+ const href = item.getAttribute('href');
560
+ document.querySelectorAll('.top-bar-nav-item[href^="#"]').forEach(n => n.classList.remove('active'));
561
+ const match = document.querySelector(`.top-bar-nav-item[href="${href}"]`);
562
+ if (match) match.classList.add('active');
563
+ });
564
+ });
565
+ document.querySelectorAll('.top-bar-nav-item[href^="#"]').forEach(item => {
566
+ item.addEventListener('click', (e) => {
567
+ e.preventDefault();
568
+ document.querySelectorAll('.top-bar-nav-item[href^="#"]').forEach(n => n.classList.remove('active'));
569
+ item.classList.add('active');
570
+ const href = item.getAttribute('href');
571
+ document.querySelectorAll('.nav-item[href^="#"]').forEach(n => n.classList.remove('active'));
572
+ const match = document.querySelector(`.nav-item[href="${href}"]`);
573
+ if (match) match.classList.add('active');
574
+ const target = document.querySelector(href);
575
+ if (target) target.scrollIntoView({ behavior: 'smooth', block: 'start' });
576
+ });
577
+ });
578
+
579
+ })();