clementine-agent 1.18.112 → 1.18.113
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/dist/cli/dashboard.js +3 -582
- package/package.json +1 -1
package/dist/cli/dashboard.js
CHANGED
|
@@ -2700,149 +2700,6 @@ export async function cmdDashboard(opts) {
|
|
|
2700
2700
|
const needsRestart = currentHash !== buildHash;
|
|
2701
2701
|
res.json({ hash: currentHash, started: buildHash, needsRestart });
|
|
2702
2702
|
});
|
|
2703
|
-
// ── Batch init — single request for all page-load data ───────────
|
|
2704
|
-
// Eliminates 12+ concurrent requests that were saturating the event loop.
|
|
2705
|
-
app.get('/api/init', async (_req, res) => {
|
|
2706
|
-
try {
|
|
2707
|
-
const result = {};
|
|
2708
|
-
// Version
|
|
2709
|
-
let currentHash = buildHash;
|
|
2710
|
-
try {
|
|
2711
|
-
const currentMtime = String(Math.floor(statSync(distDashboard).mtimeMs));
|
|
2712
|
-
const gitHash = execSync('git rev-parse --short HEAD', { cwd: PACKAGE_ROOT, encoding: 'utf-8', timeout: 3000 }).trim();
|
|
2713
|
-
currentHash = gitHash + '-' + currentMtime;
|
|
2714
|
-
}
|
|
2715
|
-
catch {
|
|
2716
|
-
try {
|
|
2717
|
-
currentHash = String(Math.floor(statSync(distDashboard).mtimeMs));
|
|
2718
|
-
}
|
|
2719
|
-
catch { /* use cached */ }
|
|
2720
|
-
}
|
|
2721
|
-
result.version = { hash: currentHash, started: buildHash, needsRestart: currentHash !== buildHash };
|
|
2722
|
-
// Status
|
|
2723
|
-
result.status = getStatus();
|
|
2724
|
-
// Activity (default: no filters, limit 50)
|
|
2725
|
-
try {
|
|
2726
|
-
result.activity = cached('activity::::', 5_000, () => {
|
|
2727
|
-
const events = [];
|
|
2728
|
-
const runsDir = path.join(BASE_DIR, 'cron', 'runs');
|
|
2729
|
-
if (existsSync(runsDir)) {
|
|
2730
|
-
const files = readdirSync(runsDir).filter(f => f.endsWith('.jsonl'));
|
|
2731
|
-
for (const file of files) {
|
|
2732
|
-
const jobName = file.replace('.jsonl', '');
|
|
2733
|
-
const colonIdx = jobName.indexOf(':');
|
|
2734
|
-
const slug = colonIdx > 0 ? jobName.substring(0, colonIdx) : null;
|
|
2735
|
-
const filePath = path.join(runsDir, file);
|
|
2736
|
-
try {
|
|
2737
|
-
const lines = readFileSync(filePath, 'utf-8').trim().split('\n').filter(Boolean);
|
|
2738
|
-
for (const line of lines.slice(-10)) {
|
|
2739
|
-
try {
|
|
2740
|
-
const entry = JSON.parse(line);
|
|
2741
|
-
events.push({
|
|
2742
|
-
source: 'cron', eventType: 'cron_run', agentSlug: slug,
|
|
2743
|
-
title: jobName, body: entry.summary ?? '', timestamp: entry.timestamp ?? '',
|
|
2744
|
-
status: entry.success ? 'success' : 'error',
|
|
2745
|
-
});
|
|
2746
|
-
}
|
|
2747
|
-
catch { /* skip */ }
|
|
2748
|
-
}
|
|
2749
|
-
}
|
|
2750
|
-
catch { /* skip */ }
|
|
2751
|
-
}
|
|
2752
|
-
}
|
|
2753
|
-
events.sort((a, b) => String(b.timestamp).localeCompare(String(a.timestamp)));
|
|
2754
|
-
return { events: events.slice(0, 50) };
|
|
2755
|
-
});
|
|
2756
|
-
}
|
|
2757
|
-
catch {
|
|
2758
|
-
result.activity = { events: [] };
|
|
2759
|
-
}
|
|
2760
|
-
try {
|
|
2761
|
-
result.metrics = computeMetrics();
|
|
2762
|
-
}
|
|
2763
|
-
catch {
|
|
2764
|
-
result.metrics = {};
|
|
2765
|
-
}
|
|
2766
|
-
try {
|
|
2767
|
-
const today = new Date();
|
|
2768
|
-
const dateStr = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`;
|
|
2769
|
-
const planPath = path.join(PLANS_DIR, `${dateStr}.json`);
|
|
2770
|
-
result.plan = existsSync(planPath) ? { ok: true, plan: JSON.parse(readFileSync(planPath, 'utf-8')) } : { ok: false, plan: null };
|
|
2771
|
-
}
|
|
2772
|
-
catch {
|
|
2773
|
-
result.plan = { ok: false, plan: null };
|
|
2774
|
-
}
|
|
2775
|
-
try {
|
|
2776
|
-
result.mcpServers = { servers: discoverMcpServers() };
|
|
2777
|
-
}
|
|
2778
|
-
catch {
|
|
2779
|
-
result.mcpServers = { servers: [] };
|
|
2780
|
-
}
|
|
2781
|
-
try {
|
|
2782
|
-
result.claudeIntegrations = { integrations: getClaudeIntegrations() };
|
|
2783
|
-
}
|
|
2784
|
-
catch {
|
|
2785
|
-
result.claudeIntegrations = { integrations: [] };
|
|
2786
|
-
}
|
|
2787
|
-
result.projects = { projects: cachedProjects ?? [] };
|
|
2788
|
-
//
|
|
2789
|
-
try {
|
|
2790
|
-
const agDir = AGENTS_DIR;
|
|
2791
|
-
const mgr = new AgentManager(agDir);
|
|
2792
|
-
const allAgents = mgr.listAll();
|
|
2793
|
-
// Bot statuses from disk
|
|
2794
|
-
let botStatuses = {};
|
|
2795
|
-
try {
|
|
2796
|
-
const p = path.join(BASE_DIR, '.bot-status.json');
|
|
2797
|
-
if (existsSync(p))
|
|
2798
|
-
botStatuses = JSON.parse(readFileSync(p, 'utf-8'));
|
|
2799
|
-
}
|
|
2800
|
-
catch { /* */ }
|
|
2801
|
-
let slackStatuses = {};
|
|
2802
|
-
try {
|
|
2803
|
-
const p = path.join(BASE_DIR, '.slack-bot-status.json');
|
|
2804
|
-
if (existsSync(p))
|
|
2805
|
-
slackStatuses = JSON.parse(readFileSync(p, 'utf-8'));
|
|
2806
|
-
}
|
|
2807
|
-
catch { /* */ }
|
|
2808
|
-
const statusData = getStatus();
|
|
2809
|
-
result.office = {
|
|
2810
|
-
clementine: {
|
|
2811
|
-
name: statusData.name,
|
|
2812
|
-
status: statusData.alive ? 'online' : 'offline',
|
|
2813
|
-
uptime: statusData.uptime || '',
|
|
2814
|
-
currentActivity: statusData.currentActivity || 'Idle',
|
|
2815
|
-
channels: statusData.channels || [],
|
|
2816
|
-
sessions: { active: 0, totalExchanges: 0 },
|
|
2817
|
-
crons: { total: 0, runsToday: 0, successRate: 100, jobs: [] },
|
|
2818
|
-
tokens: { input: 0, output: 0 },
|
|
2819
|
-
},
|
|
2820
|
-
agents: allAgents.map(a => ({
|
|
2821
|
-
slug: a.slug,
|
|
2822
|
-
name: a.name,
|
|
2823
|
-
description: a.description,
|
|
2824
|
-
status: a.status ?? 'active',
|
|
2825
|
-
avatar: a.avatar ?? null,
|
|
2826
|
-
model: a.model ?? null,
|
|
2827
|
-
project: a.project ?? null,
|
|
2828
|
-
agentDir: mgr.getAgentDir(a.slug),
|
|
2829
|
-
botStatus: botStatuses[a.slug]?.status ?? null,
|
|
2830
|
-
slackBotStatus: slackStatuses[a.slug]?.status ?? null,
|
|
2831
|
-
sessions: { active: 0, totalExchanges: 0 },
|
|
2832
|
-
crons: { total: 0, runsToday: 0, successRate: 100, jobs: [] },
|
|
2833
|
-
tokens: { input: 0, output: 0 },
|
|
2834
|
-
})),
|
|
2835
|
-
};
|
|
2836
|
-
}
|
|
2837
|
-
catch {
|
|
2838
|
-
result.office = { clementine: { name: 'Clementine', status: 'offline' }, agents: [] };
|
|
2839
|
-
}
|
|
2840
|
-
res.json(result);
|
|
2841
|
-
}
|
|
2842
|
-
catch (err) {
|
|
2843
|
-
res.status(500).json({ error: String(err) });
|
|
2844
|
-
}
|
|
2845
|
-
});
|
|
2846
2703
|
app.get('/api/status', (_req, res) => {
|
|
2847
2704
|
res.json(getStatus());
|
|
2848
2705
|
});
|
|
@@ -6846,17 +6703,9 @@ If the tool returns nothing or errors, return an empty array \`[]\`.`,
|
|
|
6846
6703
|
}
|
|
6847
6704
|
});
|
|
6848
6705
|
// ── CRON CRUD routes ──────────────────────────────────────────
|
|
6849
|
-
|
|
6850
|
-
|
|
6851
|
-
|
|
6852
|
-
const projects = cachedProjects ?? [];
|
|
6853
|
-
const merged = projects;
|
|
6854
|
-
res.json({ projects: merged });
|
|
6855
|
-
}
|
|
6856
|
-
catch (err) {
|
|
6857
|
-
res.status(500).json({ error: String(err) });
|
|
6858
|
-
}
|
|
6859
|
-
});
|
|
6706
|
+
// (Dead duplicate /api/projects handler removed in 1.18.113 — first
|
|
6707
|
+
// registration at line 6183 is the live one; Express ignores later
|
|
6708
|
+
// same-method same-path registrations.)
|
|
6860
6709
|
app.post('/api/projects/link', (req, res) => {
|
|
6861
6710
|
try {
|
|
6862
6711
|
const { path: projPath, description, keywords } = req.body;
|
|
@@ -10231,73 +10080,6 @@ If the tool returns nothing or errors, return an empty array \`[]\`.`,
|
|
|
10231
10080
|
res.status(500).json({ error: String(err) });
|
|
10232
10081
|
}
|
|
10233
10082
|
});
|
|
10234
|
-
app.get('/api/skills', async (_req, res) => {
|
|
10235
|
-
try {
|
|
10236
|
-
const skillsDir = path.join(VAULT_DIR, '00-System', 'skills');
|
|
10237
|
-
if (!existsSync(skillsDir)) {
|
|
10238
|
-
res.json({ skills: [] });
|
|
10239
|
-
return;
|
|
10240
|
-
}
|
|
10241
|
-
// Aggregate last-7-day retrieval stats from skill_usage table (best-effort).
|
|
10242
|
-
const usageStats = new Map();
|
|
10243
|
-
if (existsSync(MEMORY_DB_PATH)) {
|
|
10244
|
-
try {
|
|
10245
|
-
const Database = (await import('better-sqlite3')).default;
|
|
10246
|
-
const db = new Database(MEMORY_DB_PATH, { readonly: true });
|
|
10247
|
-
try {
|
|
10248
|
-
const rows = db.prepare(`SELECT skill_name,
|
|
10249
|
-
COUNT(*) AS retrievals,
|
|
10250
|
-
MAX(retrieved_at) AS last_retrieved_at,
|
|
10251
|
-
AVG(score) AS avg_score
|
|
10252
|
-
FROM skill_usage
|
|
10253
|
-
WHERE retrieved_at >= datetime('now', '-7 days')
|
|
10254
|
-
GROUP BY skill_name`).all();
|
|
10255
|
-
for (const r of rows) {
|
|
10256
|
-
usageStats.set(r.skill_name, {
|
|
10257
|
-
retrievals7d: r.retrievals,
|
|
10258
|
-
lastRetrievedAt: r.last_retrieved_at,
|
|
10259
|
-
avgScore: r.avg_score,
|
|
10260
|
-
});
|
|
10261
|
-
}
|
|
10262
|
-
}
|
|
10263
|
-
catch { /* skill_usage may not exist on older DBs */ }
|
|
10264
|
-
db.close();
|
|
10265
|
-
}
|
|
10266
|
-
catch { /* non-fatal */ }
|
|
10267
|
-
}
|
|
10268
|
-
const files = readdirSync(skillsDir).filter(f => f.endsWith('.md'));
|
|
10269
|
-
const skills = files.map(f => {
|
|
10270
|
-
try {
|
|
10271
|
-
const parsed = matter(readFileSync(path.join(skillsDir, f), 'utf-8'));
|
|
10272
|
-
const name = f.replace('.md', '');
|
|
10273
|
-
const stats = usageStats.get(name);
|
|
10274
|
-
return {
|
|
10275
|
-
name,
|
|
10276
|
-
title: parsed.data.title ?? f,
|
|
10277
|
-
description: parsed.data.description ?? '',
|
|
10278
|
-
source: parsed.data.source ?? 'unknown',
|
|
10279
|
-
sourceJob: parsed.data.sourceJob ?? null,
|
|
10280
|
-
triggers: parsed.data.triggers ?? [],
|
|
10281
|
-
toolsUsed: parsed.data.toolsUsed ?? [],
|
|
10282
|
-
useCount: parsed.data.useCount ?? 0,
|
|
10283
|
-
lastUsed: parsed.data.lastUsed ?? null,
|
|
10284
|
-
createdAt: parsed.data.createdAt ?? '',
|
|
10285
|
-
updatedAt: parsed.data.updatedAt ?? '',
|
|
10286
|
-
retrievals7d: stats?.retrievals7d ?? 0,
|
|
10287
|
-
lastRetrievedAt: stats?.lastRetrievedAt ?? null,
|
|
10288
|
-
avgScore: stats?.avgScore ?? null,
|
|
10289
|
-
};
|
|
10290
|
-
}
|
|
10291
|
-
catch {
|
|
10292
|
-
return null;
|
|
10293
|
-
}
|
|
10294
|
-
}).filter(Boolean);
|
|
10295
|
-
res.json({ skills });
|
|
10296
|
-
}
|
|
10297
|
-
catch (err) {
|
|
10298
|
-
res.status(500).json({ error: String(err) });
|
|
10299
|
-
}
|
|
10300
|
-
});
|
|
10301
10083
|
app.post('/api/skills', (req, res) => {
|
|
10302
10084
|
try {
|
|
10303
10085
|
const { title, description, triggers, steps } = req.body;
|
|
@@ -10342,57 +10124,6 @@ If the tool returns nothing or errors, return an empty array \`[]\`.`,
|
|
|
10342
10124
|
res.status(500).json({ error: String(err) });
|
|
10343
10125
|
}
|
|
10344
10126
|
});
|
|
10345
|
-
app.get('/api/skills/:name', (req, res) => {
|
|
10346
|
-
try {
|
|
10347
|
-
// Check agent-scoped first (via query param), then global
|
|
10348
|
-
const agentSlug = req.query.agent;
|
|
10349
|
-
let filePath;
|
|
10350
|
-
let skillDir;
|
|
10351
|
-
if (agentSlug) {
|
|
10352
|
-
const agentPath = path.join(VAULT_DIR, '00-System', 'agents', agentSlug, 'skills', `${req.params.name}.md`);
|
|
10353
|
-
if (existsSync(agentPath)) {
|
|
10354
|
-
filePath = agentPath;
|
|
10355
|
-
skillDir = path.join(VAULT_DIR, '00-System', 'agents', agentSlug, 'skills');
|
|
10356
|
-
}
|
|
10357
|
-
else {
|
|
10358
|
-
filePath = path.join(VAULT_DIR, '00-System', 'skills', `${req.params.name}.md`);
|
|
10359
|
-
skillDir = path.join(VAULT_DIR, '00-System', 'skills');
|
|
10360
|
-
}
|
|
10361
|
-
}
|
|
10362
|
-
else {
|
|
10363
|
-
filePath = path.join(VAULT_DIR, '00-System', 'skills', `${req.params.name}.md`);
|
|
10364
|
-
skillDir = path.join(VAULT_DIR, '00-System', 'skills');
|
|
10365
|
-
}
|
|
10366
|
-
if (!existsSync(filePath)) {
|
|
10367
|
-
res.status(404).json({ error: 'Skill not found' });
|
|
10368
|
-
return;
|
|
10369
|
-
}
|
|
10370
|
-
const matterMod = require('gray-matter');
|
|
10371
|
-
const parsed = matterMod(readFileSync(filePath, 'utf-8'));
|
|
10372
|
-
// Extract steps from content (after "## Procedure" heading)
|
|
10373
|
-
const procMatch = parsed.content.match(/## Procedure\s*\n([\s\S]*)/);
|
|
10374
|
-
const steps = procMatch ? procMatch[1].trim() : parsed.content.trim();
|
|
10375
|
-
// Load attachment file list with base64 content for builder reload
|
|
10376
|
-
const attachments = [];
|
|
10377
|
-
const filesDir = path.join(skillDir, `${req.params.name}.files`);
|
|
10378
|
-
if (existsSync(filesDir)) {
|
|
10379
|
-
for (const f of readdirSync(filesDir)) {
|
|
10380
|
-
try {
|
|
10381
|
-
const fp = path.join(filesDir, f);
|
|
10382
|
-
const stat = statSync(fp);
|
|
10383
|
-
if (stat.isFile() && stat.size < 10 * 1024 * 1024) {
|
|
10384
|
-
attachments.push({ filename: f, content: readFileSync(fp).toString('base64'), size: stat.size });
|
|
10385
|
-
}
|
|
10386
|
-
}
|
|
10387
|
-
catch { /* skip */ }
|
|
10388
|
-
}
|
|
10389
|
-
}
|
|
10390
|
-
res.json({ ...parsed.data, name: req.params.name, content: parsed.content, steps, attachmentFiles: attachments });
|
|
10391
|
-
}
|
|
10392
|
-
catch (err) {
|
|
10393
|
-
res.status(500).json({ error: String(err) });
|
|
10394
|
-
}
|
|
10395
|
-
});
|
|
10396
10127
|
// ── Agent-scoped Skills ──
|
|
10397
10128
|
app.get('/api/agents/:slug/skills', (req, res) => {
|
|
10398
10129
|
try {
|
|
@@ -13602,123 +13333,6 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
13602
13333
|
}
|
|
13603
13334
|
|
|
13604
13335
|
/* Right rail */
|
|
13605
|
-
.home-rail {
|
|
13606
|
-
display: flex;
|
|
13607
|
-
flex-direction: column;
|
|
13608
|
-
gap: 8px;
|
|
13609
|
-
overflow-y: auto;
|
|
13610
|
-
position: relative;
|
|
13611
|
-
}
|
|
13612
|
-
.home-rail.collapsed {
|
|
13613
|
-
display: none;
|
|
13614
|
-
}
|
|
13615
|
-
/* Auto-hide cards that have no actionable content (set via JS toggling .rail-card.empty) */
|
|
13616
|
-
.rail-card.empty { display: none; }
|
|
13617
|
-
.rail-collapse-btn {
|
|
13618
|
-
position: absolute;
|
|
13619
|
-
top: -4px;
|
|
13620
|
-
right: -4px;
|
|
13621
|
-
background: none;
|
|
13622
|
-
border: 1px solid var(--border);
|
|
13623
|
-
color: var(--text-muted);
|
|
13624
|
-
width: 22px;
|
|
13625
|
-
height: 22px;
|
|
13626
|
-
border-radius: 50%;
|
|
13627
|
-
cursor: pointer;
|
|
13628
|
-
font-size: 14px;
|
|
13629
|
-
display: none;
|
|
13630
|
-
align-items: center;
|
|
13631
|
-
justify-content: center;
|
|
13632
|
-
z-index: 5;
|
|
13633
|
-
}
|
|
13634
|
-
.rail-card {
|
|
13635
|
-
background: var(--bg-card);
|
|
13636
|
-
border: 1px solid var(--border);
|
|
13637
|
-
border-radius: var(--radius-md);
|
|
13638
|
-
overflow: hidden;
|
|
13639
|
-
box-shadow: var(--shadow-xs);
|
|
13640
|
-
}
|
|
13641
|
-
.rail-header {
|
|
13642
|
-
display: flex;
|
|
13643
|
-
align-items: center;
|
|
13644
|
-
justify-content: space-between;
|
|
13645
|
-
padding: 8px 12px;
|
|
13646
|
-
font-size: var(--text-xs);
|
|
13647
|
-
font-weight: 600;
|
|
13648
|
-
text-transform: uppercase;
|
|
13649
|
-
letter-spacing: 0.04em;
|
|
13650
|
-
color: var(--text-muted);
|
|
13651
|
-
background: transparent;
|
|
13652
|
-
}
|
|
13653
|
-
.rail-body { padding: 8px 12px 10px; font-size: var(--text-sm); line-height: 1.45; }
|
|
13654
|
-
.rail-body .empty-state, .rail-body .skel-row { font-size: var(--text-xs); }
|
|
13655
|
-
.rail-badge {
|
|
13656
|
-
display: inline-flex;
|
|
13657
|
-
align-items: center;
|
|
13658
|
-
justify-content: center;
|
|
13659
|
-
min-width: 18px;
|
|
13660
|
-
height: 18px;
|
|
13661
|
-
padding: 0 6px;
|
|
13662
|
-
border-radius: 9px;
|
|
13663
|
-
background: var(--clementine);
|
|
13664
|
-
color: #fff;
|
|
13665
|
-
font-size: 10px;
|
|
13666
|
-
font-weight: 600;
|
|
13667
|
-
}
|
|
13668
|
-
.rail-row {
|
|
13669
|
-
display: flex;
|
|
13670
|
-
align-items: center;
|
|
13671
|
-
gap: 8px;
|
|
13672
|
-
padding: 6px 0;
|
|
13673
|
-
font-size: 12px;
|
|
13674
|
-
border-bottom: 1px dashed var(--border);
|
|
13675
|
-
}
|
|
13676
|
-
.rail-row:last-child { border-bottom: none; }
|
|
13677
|
-
.rail-row .label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
13678
|
-
.rail-row .meta { font-size: 10px; color: var(--text-muted); flex-shrink: 0; }
|
|
13679
|
-
|
|
13680
|
-
/* Floating "open rail" button when collapsed */
|
|
13681
|
-
.home-rail-toggle {
|
|
13682
|
-
position: fixed;
|
|
13683
|
-
top: 80px;
|
|
13684
|
-
right: 18px;
|
|
13685
|
-
z-index: 100;
|
|
13686
|
-
width: 36px;
|
|
13687
|
-
height: 36px;
|
|
13688
|
-
border-radius: 50%;
|
|
13689
|
-
background: var(--clementine);
|
|
13690
|
-
color: #fff;
|
|
13691
|
-
border: none;
|
|
13692
|
-
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
|
13693
|
-
cursor: pointer;
|
|
13694
|
-
font-size: 14px;
|
|
13695
|
-
display: none;
|
|
13696
|
-
}
|
|
13697
|
-
.home-rail.collapsed ~ .home-rail-toggle,
|
|
13698
|
-
.home-rail.collapsed + .home-rail-toggle { display: block; }
|
|
13699
|
-
|
|
13700
|
-
/* Narrow screens: rail becomes a slide-out drawer */
|
|
13701
|
-
@media (max-width: 1024px) {
|
|
13702
|
-
.home-layout { grid-template-columns: 1fr; }
|
|
13703
|
-
.home-rail {
|
|
13704
|
-
position: fixed;
|
|
13705
|
-
right: 0;
|
|
13706
|
-
top: var(--header-h);
|
|
13707
|
-
bottom: 0;
|
|
13708
|
-
width: 320px;
|
|
13709
|
-
max-width: 90vw;
|
|
13710
|
-
transform: translateX(100%);
|
|
13711
|
-
transition: transform 0.2s ease;
|
|
13712
|
-
background: var(--bg);
|
|
13713
|
-
border-left: 1px solid var(--border);
|
|
13714
|
-
box-shadow: -4px 0 20px rgba(0,0,0,0.15);
|
|
13715
|
-
padding: 14px;
|
|
13716
|
-
z-index: 50;
|
|
13717
|
-
}
|
|
13718
|
-
.home-rail.open { transform: translateX(0); }
|
|
13719
|
-
.rail-collapse-btn { display: flex; }
|
|
13720
|
-
.home-rail-toggle { display: block; }
|
|
13721
|
-
.home-rail.open ~ .home-rail-toggle { display: none; }
|
|
13722
13336
|
}
|
|
13723
13337
|
|
|
13724
13338
|
/* ── Cards ──────────────────────────────── */
|
|
@@ -22283,11 +21897,6 @@ function navigateTo(page, opts) {
|
|
|
22283
21897
|
if (t === 'chat') {
|
|
22284
21898
|
var ci = document.getElementById('chat-input');
|
|
22285
21899
|
if (ci) ci.focus();
|
|
22286
|
-
} else if (t === 'today') {
|
|
22287
|
-
var rail = document.getElementById('home-rail');
|
|
22288
|
-
if (rail && window.matchMedia('(max-width: 1024px)').matches) rail.classList.add('open');
|
|
22289
|
-
var p = document.getElementById('home-plan-content');
|
|
22290
|
-
if (p) p.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
22291
21900
|
} else if (t === 'activity') {
|
|
22292
21901
|
var act = document.getElementById('panel-activity');
|
|
22293
21902
|
if (act) act.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
@@ -24480,16 +24089,6 @@ function operationSectionHeader(title, subtitle, badgeClass, badgeText, marginTo
|
|
|
24480
24089
|
+ '</div>';
|
|
24481
24090
|
}
|
|
24482
24091
|
|
|
24483
|
-
function renderOperationsSummary(ops) {
|
|
24484
|
-
var s = ops.summary || {};
|
|
24485
|
-
return '<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:10px;margin-bottom:16px">'
|
|
24486
|
-
+ '<div style="border:1px solid var(--border);border-radius:8px;background:var(--bg-secondary);padding:10px 12px"><div style="font-size:11px;color:var(--text-muted)">Needs Attention</div><div style="font-size:20px;font-weight:700;color:' + ((s.needsAttention || 0) > 0 ? 'var(--red)' : 'var(--green)') + '">' + esc(s.needsAttention || 0) + '</div></div>'
|
|
24487
|
-
+ '<div style="border:1px solid var(--border);border-radius:8px;background:var(--bg-secondary);padding:10px 12px"><div style="font-size:11px;color:var(--text-muted)">Scheduled Tasks</div><div style="font-size:20px;font-weight:700">' + esc(s.enabledScheduledTasks || 0) + '/' + esc(s.scheduledTasks || 0) + '</div></div>'
|
|
24488
|
-
+ '<div style="border:1px solid var(--border);border-radius:8px;background:var(--bg-secondary);padding:10px 12px"><div style="font-size:11px;color:var(--text-muted)">Scheduled Workflows</div><div style="font-size:20px;font-weight:700">' + esc(s.enabledScheduledWorkflows || 0) + '/' + esc(s.scheduledWorkflows || 0) + '</div></div>'
|
|
24489
|
-
+ '<div style="border:1px solid var(--border);border-radius:8px;background:var(--bg-secondary);padding:10px 12px"><div style="font-size:11px;color:var(--text-muted)">Running Now</div><div style="font-size:20px;font-weight:700;color:' + ((s.runningNow || 0) > 0 ? 'var(--blue)' : 'var(--text-primary)') + '">' + esc(s.runningNow || 0) + '</div></div>'
|
|
24490
|
-
+ '<div style="border:1px solid var(--border);border-radius:8px;background:var(--bg-secondary);padding:10px 12px"><div style="font-size:11px;color:var(--text-muted)">Scheduled Tokens</div><div style="font-size:20px;font-weight:700">' + esc(formatTokens(s.automationTokens || 0)) + '</div></div>'
|
|
24491
|
-
+ '</div>';
|
|
24492
|
-
}
|
|
24493
24092
|
|
|
24494
24093
|
function renderAttentionCard(item) {
|
|
24495
24094
|
var broken = item.brokenJob || null;
|
|
@@ -26035,7 +25634,6 @@ async function refreshCron() {
|
|
|
26035
25634
|
// Reliability (failures stacked by category). Filled in by
|
|
26036
25635
|
// refreshMiniDashboards from the same /api/cron/runs payload.
|
|
26037
25636
|
html += '<div id="mini-dashboards" class="mini-dashboards"></div>';
|
|
26038
|
-
html += renderOperationsSummary(ops);
|
|
26039
25637
|
|
|
26040
25638
|
// ── Zone 1 — Running now (promoted to top, primary "what's live" view) ──
|
|
26041
25639
|
if (visibleRunning.length > 0) {
|
|
@@ -29771,28 +29369,6 @@ function setScheduleFromCron(expr) {
|
|
|
29771
29369
|
updateScheduleFromBuilder();
|
|
29772
29370
|
|
|
29773
29371
|
// ── Timers ────────────────────────────────
|
|
29774
|
-
async function refreshTimers() {
|
|
29775
|
-
try {
|
|
29776
|
-
const r = await apiFetch('/api/timers');
|
|
29777
|
-
const d = await r.json();
|
|
29778
|
-
const count = Array.isArray(d) ? d.length : 0;
|
|
29779
|
-
var _tc = document.getElementById('nav-timer-count'); if (_tc) _tc.textContent = count;
|
|
29780
|
-
var _ttc = document.getElementById('tab-timer-count'); if (_ttc) { _ttc.textContent = count; _ttc.style.display = count > 0 ? '' : 'none'; }
|
|
29781
|
-
if (!Array.isArray(d) || d.length === 0) {
|
|
29782
|
-
document.getElementById('panel-timers').innerHTML = '<div class="empty-state">No pending timers</div>';
|
|
29783
|
-
return;
|
|
29784
|
-
}
|
|
29785
|
-
let html = '<table><tr><th>ID</th><th>Fires At</th><th>Message</th><th style="width:80px"></th></tr>';
|
|
29786
|
-
for (const t of d) {
|
|
29787
|
-
html += '<tr><td><code>' + esc(t.id || '?') + '</code></td>'
|
|
29788
|
-
+ '<td>' + esc(t.fireAt || t.fire_at || t.time || '') + '</td>'
|
|
29789
|
-
+ '<td>' + esc((t.message || t.prompt || '').slice(0, 100)) + '</td>'
|
|
29790
|
-
+ '<td><button class="btn-danger btn-sm" onclick="apiPost(\\x27/api/timers/' + encodeURIComponent(t.id) + '/cancel\\x27)">Cancel</button></td></tr>';
|
|
29791
|
-
}
|
|
29792
|
-
html += '</table>';
|
|
29793
|
-
document.getElementById('panel-timers').innerHTML = html;
|
|
29794
|
-
} catch(e) { }
|
|
29795
|
-
}
|
|
29796
29372
|
|
|
29797
29373
|
// ── Activity Feed ─────────────────────────
|
|
29798
29374
|
var activityLastTimestamp = '';
|
|
@@ -34987,132 +34563,6 @@ function briefingNeedsReviewClick(href) {
|
|
|
34987
34563
|
}
|
|
34988
34564
|
}
|
|
34989
34565
|
|
|
34990
|
-
function toggleHomeRail() {
|
|
34991
|
-
var rail = document.getElementById('home-rail');
|
|
34992
|
-
if (!rail) return;
|
|
34993
|
-
// Mobile: open/close. Desktop: collapse/show.
|
|
34994
|
-
if (window.matchMedia('(max-width: 1024px)').matches) {
|
|
34995
|
-
rail.classList.toggle('open');
|
|
34996
|
-
} else {
|
|
34997
|
-
rail.classList.toggle('collapsed');
|
|
34998
|
-
}
|
|
34999
|
-
}
|
|
35000
|
-
|
|
35001
|
-
function _railCard(bodyId) {
|
|
35002
|
-
var body = document.getElementById(bodyId);
|
|
35003
|
-
return body ? body.closest('.rail-card') : null;
|
|
35004
|
-
}
|
|
35005
|
-
function _setRailEmpty(bodyId, isEmpty) {
|
|
35006
|
-
var card = _railCard(bodyId);
|
|
35007
|
-
if (card) card.classList.toggle('empty', !!isEmpty);
|
|
35008
|
-
}
|
|
35009
|
-
|
|
35010
|
-
async function refreshHomeRail() {
|
|
35011
|
-
// Daemon status — only surface when explicitly stopped. Treat null/undefined
|
|
35012
|
-
// (running-state unknown) as "fine, hide" since the dashboard wouldn't be
|
|
35013
|
-
// serving requests if the daemon were truly down.
|
|
35014
|
-
try {
|
|
35015
|
-
var rs = await apiFetch('/api/status');
|
|
35016
|
-
var ds = await rs.json();
|
|
35017
|
-
var stopped = ds.running === false;
|
|
35018
|
-
var pip = document.querySelector('#rail-daemon-body .agent-activity-dot');
|
|
35019
|
-
var label = document.querySelector('#rail-daemon-body .agent-activity span:last-child');
|
|
35020
|
-
if (label) label.textContent = stopped ? 'Daemon stopped' : 'Running';
|
|
35021
|
-
if (pip) pip.style.background = stopped ? '#ef4444' : '#22c55e';
|
|
35022
|
-
var up = document.getElementById('rail-daemon-uptime');
|
|
35023
|
-
if (up && ds.uptimeMs) up.textContent = Math.round(ds.uptimeMs / 60000) + 'm';
|
|
35024
|
-
_setRailEmpty('rail-daemon-body', !stopped);
|
|
35025
|
-
} catch { _setRailEmpty('rail-daemon-body', true); }
|
|
35026
|
-
|
|
35027
|
-
// Today's plan (compact). Hide card if no plan or zero items.
|
|
35028
|
-
try {
|
|
35029
|
-
var rp = await apiFetch('/api/daily-plan');
|
|
35030
|
-
var dp = await rp.json();
|
|
35031
|
-
var planEl = document.getElementById('home-plan-content');
|
|
35032
|
-
var items = dp && dp.plan ? (dp.plan.items || []) : [];
|
|
35033
|
-
if (planEl) {
|
|
35034
|
-
if (items.length === 0) {
|
|
35035
|
-
planEl.innerHTML = '<div style="font-size:11px;color:var(--text-muted)">No plan yet today.</div>';
|
|
35036
|
-
} else {
|
|
35037
|
-
planEl.innerHTML = items.slice(0, 4).map(function(it) {
|
|
35038
|
-
return '<div class="rail-row"><span class="label">' + esc(it.title || it.text || '') + '</span><span class="meta">' + esc(it.time || '') + '</span></div>';
|
|
35039
|
-
}).join('');
|
|
35040
|
-
}
|
|
35041
|
-
}
|
|
35042
|
-
_setRailEmpty('home-plan-content', items.length === 0);
|
|
35043
|
-
} catch {
|
|
35044
|
-
_setRailEmpty('home-plan-content', true);
|
|
35045
|
-
}
|
|
35046
|
-
|
|
35047
|
-
// Upcoming cron fires (next 3) — hide card if nothing scheduled
|
|
35048
|
-
try {
|
|
35049
|
-
var rc = await apiFetch('/api/cron');
|
|
35050
|
-
var dc = await rc.json();
|
|
35051
|
-
var jobs = (dc.jobs || []).filter(function(j) { return j.enabled && j.nextRun; });
|
|
35052
|
-
jobs.sort(function(a, b) { return new Date(a.nextRun).getTime() - new Date(b.nextRun).getTime(); });
|
|
35053
|
-
var top = jobs.slice(0, 3);
|
|
35054
|
-
var ue = document.getElementById('rail-upcoming');
|
|
35055
|
-
var uc = document.getElementById('rail-upcoming-count');
|
|
35056
|
-
if (uc) uc.textContent = String(jobs.length);
|
|
35057
|
-
if (ue) {
|
|
35058
|
-
ue.innerHTML = top.map(function(j) {
|
|
35059
|
-
return '<div class="rail-row clickable-row" onclick="navigateTo(\\x27build\\x27,{tab:\\x27crons\\x27})"><span class="label">' + esc(j.name) + '</span><span class="meta">' + esc(timeUntil(j.nextRun)) + '</span></div>';
|
|
35060
|
-
}).join('');
|
|
35061
|
-
}
|
|
35062
|
-
_setRailEmpty('rail-upcoming', top.length === 0);
|
|
35063
|
-
} catch { _setRailEmpty('rail-upcoming', true); }
|
|
35064
|
-
|
|
35065
|
-
// Active unleashed runs — hide card unless something running
|
|
35066
|
-
try {
|
|
35067
|
-
var ru = await apiFetch('/api/unleashed');
|
|
35068
|
-
var du = await ru.json();
|
|
35069
|
-
var active = (du.tasks || []).filter(function(t) { return t.live === true || t.runtimeState === 'active'; });
|
|
35070
|
-
var ae = document.getElementById('rail-active');
|
|
35071
|
-
var ac = document.getElementById('rail-active-count');
|
|
35072
|
-
if (ac) {
|
|
35073
|
-
if (active.length > 0) { ac.style.display = ''; ac.textContent = String(active.length); }
|
|
35074
|
-
else ac.style.display = 'none';
|
|
35075
|
-
}
|
|
35076
|
-
if (ae) {
|
|
35077
|
-
ae.innerHTML = active.map(function(t) {
|
|
35078
|
-
return '<div class="rail-row clickable-row" onclick="navigateTo(\\x27build\\x27,{tab:\\x27crons\\x27})"><span class="label">' + esc(t.name) + '</span><span class="meta">' + esc(t.phase || '') + '</span></div>';
|
|
35079
|
-
}).join('');
|
|
35080
|
-
}
|
|
35081
|
-
_setRailEmpty('rail-active', active.length === 0);
|
|
35082
|
-
} catch { _setRailEmpty('rail-active', true); }
|
|
35083
|
-
|
|
35084
|
-
// Time saved (compact). Hide if zero.
|
|
35085
|
-
try {
|
|
35086
|
-
var rm = await apiFetch('/api/metrics?period=week');
|
|
35087
|
-
var dm = await rm.json();
|
|
35088
|
-
var minutes = ((dm.cronRuns || 0) * 5) + ((dm.exchanges || 0) * 2);
|
|
35089
|
-
var ts = document.getElementById('rail-time-saved');
|
|
35090
|
-
if (ts) {
|
|
35091
|
-
if (minutes >= 60) ts.innerHTML = '<div style="font-size:var(--text-md);font-weight:600">' + (minutes / 60).toFixed(1) + 'h</div><div style="font-size:11px;color:var(--text-muted)">' + (dm.cronRuns || 0) + ' runs · ' + (dm.exchanges || 0) + ' chats</div>';
|
|
35092
|
-
else ts.innerHTML = '<div style="font-size:var(--text-md);font-weight:600">' + minutes + 'm</div><div style="font-size:11px;color:var(--text-muted)">' + (dm.cronRuns || 0) + ' runs</div>';
|
|
35093
|
-
}
|
|
35094
|
-
_setRailEmpty('rail-time-saved', minutes === 0);
|
|
35095
|
-
} catch { _setRailEmpty('rail-time-saved', true); }
|
|
35096
|
-
|
|
35097
|
-
// Approvals — hide card unless something pending
|
|
35098
|
-
try {
|
|
35099
|
-
var rsi = await apiFetch('/api/self-improve');
|
|
35100
|
-
var dsi = await rsi.json();
|
|
35101
|
-
var pending = (dsi.proposals || []).filter(function(p) { return p.status === 'pending'; });
|
|
35102
|
-
var ae2 = document.getElementById('rail-approvals');
|
|
35103
|
-
var ac2 = document.getElementById('rail-approvals-count');
|
|
35104
|
-
if (ac2) {
|
|
35105
|
-
if (pending.length > 0) { ac2.style.display = ''; ac2.textContent = String(pending.length); }
|
|
35106
|
-
else ac2.style.display = 'none';
|
|
35107
|
-
}
|
|
35108
|
-
if (ae2) {
|
|
35109
|
-
ae2.innerHTML = pending.slice(0, 3).map(function(p) {
|
|
35110
|
-
return '<div class="rail-row clickable-row" onclick="navigateTo(\\x27brain\\x27,{tab:\\x27learning\\x27})"><span class="label">' + esc(p.area || 'proposal') + ': ' + esc((p.target || '').slice(0, 40)) + '</span><span class="meta">' + esc(((p.score || 0) * 100).toFixed(0)) + '%</span></div>';
|
|
35111
|
-
}).join('');
|
|
35112
|
-
}
|
|
35113
|
-
_setRailEmpty('rail-approvals', pending.length === 0);
|
|
35114
|
-
} catch { _setRailEmpty('rail-approvals', true); }
|
|
35115
|
-
}
|
|
35116
34566
|
|
|
35117
34567
|
function timeUntil(iso) {
|
|
35118
34568
|
if (!iso) return '';
|
|
@@ -35134,7 +34584,6 @@ async function refreshAll() {
|
|
|
35134
34584
|
else refreshActivity(); // Fall back to direct /api/activity fetch when init didn't include it
|
|
35135
34585
|
if (d.office) refreshTeamNav(d.office);
|
|
35136
34586
|
// Home rail data — fire and forget, doesn't block init render.
|
|
35137
|
-
if (currentPage === 'home') refreshHomeRail();
|
|
35138
34587
|
if (d.version) {
|
|
35139
34588
|
if (d.version.needsRestart && !_restartBannerShown) {
|
|
35140
34589
|
_restartBannerShown = true;
|
|
@@ -38297,34 +37746,6 @@ async function refreshHomeMetrics() {
|
|
|
38297
37746
|
}
|
|
38298
37747
|
|
|
38299
37748
|
// ── Home Page: Sessions Tab ──────────────
|
|
38300
|
-
async function refreshHomeSessions() {
|
|
38301
|
-
var container = document.getElementById('panel-sessions-home');
|
|
38302
|
-
if (!container) return;
|
|
38303
|
-
try {
|
|
38304
|
-
var r = await apiFetch('/api/sessions');
|
|
38305
|
-
var d = await r.json();
|
|
38306
|
-
var keys = Object.keys(d);
|
|
38307
|
-
if (keys.length === 0) { container.innerHTML = '<div class="empty-state">No active sessions</div>'; return; }
|
|
38308
|
-
var html = '<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:12px">';
|
|
38309
|
-
keys.forEach(function(key) {
|
|
38310
|
-
var s = d[key];
|
|
38311
|
-
var icon = key.indexOf('discord') >= 0 ? '💬' : key.indexOf('slack') >= 0 ? '🗨' : key.indexOf('telegram') >= 0 ? '✈' : key.indexOf('dashboard') >= 0 ? '🌐' : '💬';
|
|
38312
|
-
var exchanges = s.exchanges || 0;
|
|
38313
|
-
var lastActive = s.lastActive ? fmtTimeAgo(s.lastActive) : 'unknown';
|
|
38314
|
-
html += '<div class="card" style="padding:12px;cursor:pointer" onclick="viewSessionModal(\\x27' + encodeURIComponent(key) + '\\x27)">';
|
|
38315
|
-
html += '<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px">';
|
|
38316
|
-
html += '<span style="font-size:16px">' + icon + '</span>';
|
|
38317
|
-
html += '<span style="font-weight:500;font-size:13px">' + esc(key.split(':').pop() || key) + '</span>';
|
|
38318
|
-
html += '</div>';
|
|
38319
|
-
html += '<div style="font-size:12px;color:var(--text-muted)">' + exchanges + ' exchanges · ' + lastActive + '</div>';
|
|
38320
|
-
html += '</div>';
|
|
38321
|
-
});
|
|
38322
|
-
html += '</div>';
|
|
38323
|
-
container.innerHTML = html;
|
|
38324
|
-
} catch(e) {
|
|
38325
|
-
container.innerHTML = '<div class="empty-state">Failed to load sessions</div>';
|
|
38326
|
-
}
|
|
38327
|
-
}
|
|
38328
37749
|
|
|
38329
37750
|
// ── Execution Analytics ───────────────────
|
|
38330
37751
|
async function refreshAdvisorAnalytics() {
|