clementine-agent 1.4.1 → 1.4.3
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 +417 -64
- package/package.json +1 -1
package/dist/cli/dashboard.js
CHANGED
|
@@ -2240,6 +2240,140 @@ export async function cmdDashboard(opts) {
|
|
|
2240
2240
|
writeFileSync(queueFile, JSON.stringify(queue, null, 2));
|
|
2241
2241
|
res.json({ ok: true, id });
|
|
2242
2242
|
});
|
|
2243
|
+
app.get('/api/vault-files', async (req, res) => {
|
|
2244
|
+
try {
|
|
2245
|
+
const limit = Math.min(parseInt(String(req.query.limit ?? '120'), 10) || 120, 500);
|
|
2246
|
+
const sinceDays = Math.max(parseInt(String(req.query.sinceDays ?? '30'), 10) || 30, 1);
|
|
2247
|
+
const agentFilter = typeof req.query.agent === 'string' ? req.query.agent : '';
|
|
2248
|
+
const folderFilter = typeof req.query.folder === 'string' ? req.query.folder : '';
|
|
2249
|
+
const search = typeof req.query.q === 'string' ? req.query.q.toLowerCase() : '';
|
|
2250
|
+
const includeAuto = req.query.includeAuto === '1';
|
|
2251
|
+
const cutoffMs = Date.now() - sinceDays * 24 * 60 * 60 * 1000;
|
|
2252
|
+
const vaultRoot = path.join(BASE_DIR, 'vault');
|
|
2253
|
+
const matter = (await import('gray-matter')).default;
|
|
2254
|
+
const files = [];
|
|
2255
|
+
function walk(dir) {
|
|
2256
|
+
let entries = [];
|
|
2257
|
+
try {
|
|
2258
|
+
entries = readdirSync(dir);
|
|
2259
|
+
}
|
|
2260
|
+
catch {
|
|
2261
|
+
return;
|
|
2262
|
+
}
|
|
2263
|
+
for (const e of entries) {
|
|
2264
|
+
if (e.startsWith('.'))
|
|
2265
|
+
continue;
|
|
2266
|
+
const full = path.join(dir, e);
|
|
2267
|
+
let stat;
|
|
2268
|
+
try {
|
|
2269
|
+
stat = statSync(full);
|
|
2270
|
+
}
|
|
2271
|
+
catch {
|
|
2272
|
+
continue;
|
|
2273
|
+
}
|
|
2274
|
+
if (stat.isDirectory()) {
|
|
2275
|
+
walk(full);
|
|
2276
|
+
continue;
|
|
2277
|
+
}
|
|
2278
|
+
if (!e.endsWith('.md'))
|
|
2279
|
+
continue;
|
|
2280
|
+
if (e.endsWith('.md.bak'))
|
|
2281
|
+
continue;
|
|
2282
|
+
if (stat.mtimeMs < cutoffMs)
|
|
2283
|
+
continue;
|
|
2284
|
+
const rel = path.relative(vaultRoot, full);
|
|
2285
|
+
// Skip auto-generated MCP/skill wrappers unless explicitly requested.
|
|
2286
|
+
// Path patterns: 00-System/skills/auto/* (generated tool wrappers).
|
|
2287
|
+
if (!includeAuto && rel.startsWith('00-System/skills/auto/'))
|
|
2288
|
+
continue;
|
|
2289
|
+
if (!includeAuto && rel.startsWith('00-System/agents/') && /\/(MEMORY|HEARTBEAT|TASKS|CRON)\.md$/.test(rel))
|
|
2290
|
+
continue;
|
|
2291
|
+
const folder = path.dirname(rel).split(path.sep)[0] || '';
|
|
2292
|
+
let agentSlug = null;
|
|
2293
|
+
if (rel.startsWith('00-System/agents/')) {
|
|
2294
|
+
const m = rel.match(/^00-System\/agents\/([^/]+)\//);
|
|
2295
|
+
if (m)
|
|
2296
|
+
agentSlug = m[1];
|
|
2297
|
+
}
|
|
2298
|
+
// Skip system housekeeping files (their author will surface via mtime in agent's own dir)
|
|
2299
|
+
let title = path.basename(rel, '.md');
|
|
2300
|
+
let typeTag = null;
|
|
2301
|
+
try {
|
|
2302
|
+
const head = readFileSync(full, 'utf-8').slice(0, 4000);
|
|
2303
|
+
const parsed = matter(head);
|
|
2304
|
+
const data = parsed.data;
|
|
2305
|
+
if (typeof data.title === 'string')
|
|
2306
|
+
title = data.title;
|
|
2307
|
+
else if (typeof data.name === 'string')
|
|
2308
|
+
title = data.name;
|
|
2309
|
+
else {
|
|
2310
|
+
const h1 = (parsed.content || '').match(/^#\s+(.+)$/m);
|
|
2311
|
+
if (h1)
|
|
2312
|
+
title = h1[1].trim();
|
|
2313
|
+
}
|
|
2314
|
+
if (typeof data.type === 'string')
|
|
2315
|
+
typeTag = data.type;
|
|
2316
|
+
}
|
|
2317
|
+
catch { /* */ }
|
|
2318
|
+
files.push({
|
|
2319
|
+
path: full,
|
|
2320
|
+
relPath: rel,
|
|
2321
|
+
title,
|
|
2322
|
+
folder,
|
|
2323
|
+
agentSlug,
|
|
2324
|
+
mtime: new Date(stat.mtimeMs).toISOString(),
|
|
2325
|
+
sizeBytes: stat.size,
|
|
2326
|
+
type: typeTag,
|
|
2327
|
+
});
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2330
|
+
walk(vaultRoot);
|
|
2331
|
+
let filtered = files
|
|
2332
|
+
.sort((a, b) => b.mtime.localeCompare(a.mtime))
|
|
2333
|
+
.filter(f => {
|
|
2334
|
+
if (agentFilter === '__shared__' && f.agentSlug != null)
|
|
2335
|
+
return false;
|
|
2336
|
+
if (agentFilter && agentFilter !== '__shared__' && f.agentSlug !== agentFilter)
|
|
2337
|
+
return false;
|
|
2338
|
+
if (folderFilter && f.folder !== folderFilter)
|
|
2339
|
+
return false;
|
|
2340
|
+
if (search) {
|
|
2341
|
+
const hay = (f.title + ' ' + f.relPath).toLowerCase();
|
|
2342
|
+
if (!hay.includes(search))
|
|
2343
|
+
return false;
|
|
2344
|
+
}
|
|
2345
|
+
return true;
|
|
2346
|
+
})
|
|
2347
|
+
.slice(0, limit);
|
|
2348
|
+
// Compute folder counts for filter chips
|
|
2349
|
+
const folderCounts = {};
|
|
2350
|
+
for (const f of files)
|
|
2351
|
+
folderCounts[f.folder] = (folderCounts[f.folder] || 0) + 1;
|
|
2352
|
+
res.json({ files: filtered, total: files.length, folderCounts });
|
|
2353
|
+
}
|
|
2354
|
+
catch (err) {
|
|
2355
|
+
res.status(500).json({ error: String(err) });
|
|
2356
|
+
}
|
|
2357
|
+
});
|
|
2358
|
+
app.get('/api/vault-file', async (req, res) => {
|
|
2359
|
+
try {
|
|
2360
|
+
const relPath = typeof req.query.path === 'string' ? req.query.path : '';
|
|
2361
|
+
if (!relPath || relPath.includes('..')) {
|
|
2362
|
+
res.status(400).json({ error: 'Bad path' });
|
|
2363
|
+
return;
|
|
2364
|
+
}
|
|
2365
|
+
const full = path.join(BASE_DIR, 'vault', relPath);
|
|
2366
|
+
if (!existsSync(full)) {
|
|
2367
|
+
res.status(404).json({ error: 'Not found' });
|
|
2368
|
+
return;
|
|
2369
|
+
}
|
|
2370
|
+
const content = readFileSync(full, 'utf-8');
|
|
2371
|
+
res.json({ path: relPath, content });
|
|
2372
|
+
}
|
|
2373
|
+
catch (err) {
|
|
2374
|
+
res.status(500).json({ error: String(err) });
|
|
2375
|
+
}
|
|
2376
|
+
});
|
|
2243
2377
|
app.get('/api/memory', async (_req, res) => {
|
|
2244
2378
|
res.json(await getMemory());
|
|
2245
2379
|
});
|
|
@@ -8718,13 +8852,15 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
8718
8852
|
.home-rail {
|
|
8719
8853
|
display: flex;
|
|
8720
8854
|
flex-direction: column;
|
|
8721
|
-
gap:
|
|
8855
|
+
gap: 8px;
|
|
8722
8856
|
overflow-y: auto;
|
|
8723
8857
|
position: relative;
|
|
8724
8858
|
}
|
|
8725
8859
|
.home-rail.collapsed {
|
|
8726
8860
|
display: none;
|
|
8727
8861
|
}
|
|
8862
|
+
/* Auto-hide cards that have no actionable content (set via JS toggling .rail-card.empty) */
|
|
8863
|
+
.rail-card.empty { display: none; }
|
|
8728
8864
|
.rail-collapse-btn {
|
|
8729
8865
|
position: absolute;
|
|
8730
8866
|
top: -4px;
|
|
@@ -8745,23 +8881,24 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
8745
8881
|
.rail-card {
|
|
8746
8882
|
background: var(--bg-card);
|
|
8747
8883
|
border: 1px solid var(--border);
|
|
8748
|
-
border-radius:
|
|
8884
|
+
border-radius: var(--radius-md);
|
|
8749
8885
|
overflow: hidden;
|
|
8750
|
-
box-shadow:
|
|
8886
|
+
box-shadow: var(--shadow-xs);
|
|
8751
8887
|
}
|
|
8752
8888
|
.rail-header {
|
|
8753
8889
|
display: flex;
|
|
8754
8890
|
align-items: center;
|
|
8755
8891
|
justify-content: space-between;
|
|
8756
|
-
padding:
|
|
8757
|
-
font-size:
|
|
8892
|
+
padding: 8px 12px;
|
|
8893
|
+
font-size: var(--text-xs);
|
|
8758
8894
|
font-weight: 600;
|
|
8759
|
-
|
|
8760
|
-
|
|
8761
|
-
|
|
8895
|
+
text-transform: uppercase;
|
|
8896
|
+
letter-spacing: 0.04em;
|
|
8897
|
+
color: var(--text-muted);
|
|
8898
|
+
background: transparent;
|
|
8762
8899
|
}
|
|
8763
|
-
.rail-body { padding: 12px
|
|
8764
|
-
.rail-body .empty-state, .rail-body .skel-row { font-size:
|
|
8900
|
+
.rail-body { padding: 8px 12px 10px; font-size: var(--text-sm); line-height: 1.45; }
|
|
8901
|
+
.rail-body .empty-state, .rail-body .skel-row { font-size: var(--text-xs); }
|
|
8765
8902
|
.rail-badge {
|
|
8766
8903
|
display: inline-flex;
|
|
8767
8904
|
align-items: center;
|
|
@@ -10540,35 +10677,39 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
10540
10677
|
margin-top: 4px;
|
|
10541
10678
|
}
|
|
10542
10679
|
|
|
10543
|
-
/* ── Timeline
|
|
10680
|
+
/* ── Timeline (compact) ───────────────────── */
|
|
10544
10681
|
.timeline {
|
|
10545
10682
|
position: relative;
|
|
10546
|
-
padding-left:
|
|
10683
|
+
padding-left: 18px;
|
|
10547
10684
|
}
|
|
10548
10685
|
.timeline::before {
|
|
10549
10686
|
content: '';
|
|
10550
10687
|
position: absolute;
|
|
10551
|
-
left:
|
|
10688
|
+
left: 5px;
|
|
10552
10689
|
top: 4px;
|
|
10553
10690
|
bottom: 4px;
|
|
10554
|
-
width:
|
|
10691
|
+
width: 1px;
|
|
10555
10692
|
background: var(--border);
|
|
10556
10693
|
}
|
|
10557
10694
|
.timeline-item {
|
|
10558
10695
|
position: relative;
|
|
10559
|
-
padding:
|
|
10560
|
-
font-size:
|
|
10696
|
+
padding: 4px 0;
|
|
10697
|
+
font-size: var(--text-sm);
|
|
10561
10698
|
display: flex;
|
|
10562
|
-
align-items:
|
|
10563
|
-
gap:
|
|
10699
|
+
align-items: center;
|
|
10700
|
+
gap: 8px;
|
|
10701
|
+
border-radius: var(--radius-xs);
|
|
10702
|
+
transition: background var(--motion);
|
|
10564
10703
|
}
|
|
10704
|
+
.timeline-item:hover { background: var(--bg-hover); }
|
|
10565
10705
|
.timeline-item::before {
|
|
10566
10706
|
content: '';
|
|
10567
10707
|
position: absolute;
|
|
10568
|
-
left: -
|
|
10569
|
-
top:
|
|
10570
|
-
|
|
10571
|
-
|
|
10708
|
+
left: -16px;
|
|
10709
|
+
top: 50%;
|
|
10710
|
+
transform: translateY(-50%);
|
|
10711
|
+
width: 7px;
|
|
10712
|
+
height: 7px;
|
|
10572
10713
|
border-radius: 50%;
|
|
10573
10714
|
background: var(--text-muted);
|
|
10574
10715
|
border: 2px solid var(--bg-primary);
|
|
@@ -10576,8 +10717,52 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
10576
10717
|
}
|
|
10577
10718
|
.timeline-item.ok::before { background: var(--green); }
|
|
10578
10719
|
.timeline-item.error::before { background: var(--red); }
|
|
10579
|
-
.timeline-msg {
|
|
10580
|
-
|
|
10720
|
+
.timeline-msg {
|
|
10721
|
+
flex: 1;
|
|
10722
|
+
color: var(--text-primary);
|
|
10723
|
+
line-height: 1.35;
|
|
10724
|
+
white-space: nowrap;
|
|
10725
|
+
overflow: hidden;
|
|
10726
|
+
text-overflow: ellipsis;
|
|
10727
|
+
min-width: 0;
|
|
10728
|
+
}
|
|
10729
|
+
.timeline-title { font-weight: 500; }
|
|
10730
|
+
.timeline-agent {
|
|
10731
|
+
color: var(--clementine);
|
|
10732
|
+
font-size: var(--text-xs);
|
|
10733
|
+
margin-left: 6px;
|
|
10734
|
+
font-weight: 500;
|
|
10735
|
+
}
|
|
10736
|
+
.timeline-body {
|
|
10737
|
+
color: var(--text-muted);
|
|
10738
|
+
font-size: var(--text-xs);
|
|
10739
|
+
margin-left: 4px;
|
|
10740
|
+
}
|
|
10741
|
+
.timeline-time { flex-shrink: 0; color: var(--text-muted); font-size: var(--text-xs); }
|
|
10742
|
+
/* Cap activity card height so chat dominates the page */
|
|
10743
|
+
.home-activity .card-body { max-height: 320px; overflow-y: auto; padding: 10px 16px; }
|
|
10744
|
+
/* Hide chat profile selector when default — the row gets cleaner */
|
|
10745
|
+
.home-chat-input-row .chat-profile-spacer { display: none; }
|
|
10746
|
+
|
|
10747
|
+
/* Vault file folder chips */
|
|
10748
|
+
.vault-folder-chip {
|
|
10749
|
+
padding: 4px 12px;
|
|
10750
|
+
border: 1px solid var(--border);
|
|
10751
|
+
border-radius: 16px;
|
|
10752
|
+
font-size: var(--text-sm);
|
|
10753
|
+
color: var(--text-secondary);
|
|
10754
|
+
cursor: pointer;
|
|
10755
|
+
transition: all var(--motion);
|
|
10756
|
+
background: var(--bg-secondary);
|
|
10757
|
+
user-select: none;
|
|
10758
|
+
}
|
|
10759
|
+
.vault-folder-chip:hover { background: var(--bg-hover); color: var(--text-primary); }
|
|
10760
|
+
.vault-folder-chip.active {
|
|
10761
|
+
background: var(--clementine-bg);
|
|
10762
|
+
color: var(--clementine);
|
|
10763
|
+
border-color: var(--clementine);
|
|
10764
|
+
font-weight: 500;
|
|
10765
|
+
}
|
|
10581
10766
|
|
|
10582
10767
|
/* ── Task Cards ─────────────────────────── */
|
|
10583
10768
|
.task-grid {
|
|
@@ -11734,6 +11919,7 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
11734
11919
|
<div class="tab-bar" id="intelligence-tabs" style="margin:0 0 0 18px">
|
|
11735
11920
|
<button class="active" data-icon="database" onclick="switchTab('intelligence','search')"><span class="icon-slot"></span> Memory</button>
|
|
11736
11921
|
<button data-icon="sparkles" onclick="switchTab('intelligence','graph')"><span class="icon-slot"></span> Knowledge</button>
|
|
11922
|
+
<button data-icon="fileText" onclick="switchTab('intelligence','files')"><span class="icon-slot"></span> Files</button>
|
|
11737
11923
|
<button data-icon="folder" onclick="switchTab('intelligence','sources')"><span class="icon-slot"></span> Ingestion</button>
|
|
11738
11924
|
<button data-icon="zap" onclick="switchTab('intelligence','health')"><span class="icon-slot"></span> Health <span class="tab-badge" id="brain-health-badge" style="display:none;background:#ef4444;color:#fff">0</span></button>
|
|
11739
11925
|
<button data-icon="users" onclick="switchTab('intelligence','user-model')"><span class="icon-slot"></span> User Model</button>
|
|
@@ -11942,6 +12128,26 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
11942
12128
|
<div class="tab-pane" id="tab-intelligence-runs">
|
|
11943
12129
|
<div id="brain-runs-list"></div>
|
|
11944
12130
|
</div>
|
|
12131
|
+
<div class="tab-pane" id="tab-intelligence-files">
|
|
12132
|
+
<div style="display:flex;align-items:center;gap:10px;margin-bottom:14px;flex-wrap:wrap">
|
|
12133
|
+
<input type="text" id="vault-files-search" placeholder="Search title or path..." style="flex:1;min-width:200px;padding:7px 12px;border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--bg-input);color:var(--text-primary);font-size:13px" oninput="refreshVaultFiles()">
|
|
12134
|
+
<select id="vault-files-agent-filter" onchange="refreshVaultFiles()" style="padding:7px 10px;border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--bg-secondary);color:var(--text-primary);font-size:12px">
|
|
12135
|
+
<option value="">All authors</option>
|
|
12136
|
+
<option value="__shared__">Shared (vault root)</option>
|
|
12137
|
+
</select>
|
|
12138
|
+
<select id="vault-files-since" onchange="refreshVaultFiles()" style="padding:7px 10px;border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--bg-secondary);color:var(--text-primary);font-size:12px">
|
|
12139
|
+
<option value="7">Past 7 days</option>
|
|
12140
|
+
<option value="30" selected>Past 30 days</option>
|
|
12141
|
+
<option value="90">Past 90 days</option>
|
|
12142
|
+
<option value="365">Past year</option>
|
|
12143
|
+
</select>
|
|
12144
|
+
<button class="btn-sm" onclick="refreshVaultFiles()">Refresh</button>
|
|
12145
|
+
</div>
|
|
12146
|
+
<div id="vault-files-folder-chips" style="display:flex;flex-wrap:wrap;gap:6px;margin-bottom:12px"></div>
|
|
12147
|
+
<div id="vault-files-list">
|
|
12148
|
+
<div class="skel-block"><div class="skel-row med"></div><div class="skel-row"></div><div class="skel-row short"></div></div>
|
|
12149
|
+
</div>
|
|
12150
|
+
</div>
|
|
11945
12151
|
<div class="tab-pane" id="tab-intelligence-health">
|
|
11946
12152
|
<div style="display:flex;align-items:center;gap:8px;margin-bottom:14px;flex-wrap:wrap">
|
|
11947
12153
|
<button class="btn-sm" onclick="memoryHealthAction('janitor')" title="Run the janitor cleanup pass now">Run cleanup</button>
|
|
@@ -14534,6 +14740,7 @@ function switchTab(group, tab) {
|
|
|
14534
14740
|
if (group === 'intelligence') {
|
|
14535
14741
|
if (tab === 'graph') refreshGraph();
|
|
14536
14742
|
if (tab === 'memory') refreshMemory();
|
|
14743
|
+
if (tab === 'files' && typeof refreshVaultFiles === 'function') refreshVaultFiles();
|
|
14537
14744
|
if (tab === 'health') {
|
|
14538
14745
|
if (typeof refreshMemoryHealth === 'function') refreshMemoryHealth();
|
|
14539
14746
|
if (typeof refreshClaims === 'function') refreshClaims();
|
|
@@ -17260,15 +17467,15 @@ var sourceIcons = {
|
|
|
17260
17467
|
};
|
|
17261
17468
|
|
|
17262
17469
|
function activityEventHtml(e) {
|
|
17263
|
-
var icon = sourceIcons[e.source] || '●';
|
|
17264
17470
|
var statusCls = e.status === 'ok' || e.status === 'approved' ? 'ok'
|
|
17265
17471
|
: (e.status === 'error' || e.eventType === 'cron_error') ? 'error'
|
|
17266
|
-
: e.status === 'pending' ? '' : '';
|
|
17267
|
-
var agentLabel = e.agentSlug ? '<span
|
|
17268
|
-
return '<div class="timeline-item ' + statusCls + '">'
|
|
17269
|
-
+ '<span
|
|
17270
|
-
|
|
17271
|
-
|
|
17472
|
+
: e.status === 'pending' ? 'pending' : '';
|
|
17473
|
+
var agentLabel = e.agentSlug ? '<span class="timeline-agent">[' + esc(e.agentSlug) + ']</span>' : '';
|
|
17474
|
+
return '<div class="timeline-item ' + statusCls + '" title="' + esc(e.body || e.title) + '">'
|
|
17475
|
+
+ '<span class="timeline-msg">'
|
|
17476
|
+
+ '<span class="timeline-title">' + esc(e.title) + '</span>'
|
|
17477
|
+
+ agentLabel
|
|
17478
|
+
+ (e.body ? ' <span class="timeline-body">· ' + esc(e.body) + '</span>' : '')
|
|
17272
17479
|
+ '</span>'
|
|
17273
17480
|
+ '<span class="timeline-time">' + timeAgo(e.timestamp) + '</span>'
|
|
17274
17481
|
+ '</div>';
|
|
@@ -18396,13 +18603,17 @@ async function loadProfiles() {
|
|
|
18396
18603
|
var d = await r.json();
|
|
18397
18604
|
var sel = document.getElementById('chat-profile-select');
|
|
18398
18605
|
sel.innerHTML = '<option value="">Default</option>';
|
|
18606
|
+
var customCount = 0;
|
|
18399
18607
|
for (var p of (d.profiles || [])) {
|
|
18400
18608
|
var opt = document.createElement('option');
|
|
18401
18609
|
opt.value = p.slug;
|
|
18402
18610
|
opt.textContent = p.name + (p.description ? ' — ' + p.description : '');
|
|
18403
18611
|
if (p.slug === d.active) opt.selected = true;
|
|
18404
18612
|
sel.appendChild(opt);
|
|
18613
|
+
customCount++;
|
|
18405
18614
|
}
|
|
18615
|
+
// Hide the picker entirely if there are no custom profiles — declutters the chat input row.
|
|
18616
|
+
sel.style.display = customCount === 0 ? 'none' : '';
|
|
18406
18617
|
} catch(e) { /* profiles are optional */ }
|
|
18407
18618
|
}
|
|
18408
18619
|
|
|
@@ -19781,6 +19992,134 @@ async function memoryHealthAction(action) {
|
|
|
19781
19992
|
}
|
|
19782
19993
|
}
|
|
19783
19994
|
|
|
19995
|
+
// ── Vault Files (Brain → Files tab) ──────────────────────────────
|
|
19996
|
+
var _vaultFilesCache = null;
|
|
19997
|
+
var _vaultFilesFolder = ''; // current folder filter
|
|
19998
|
+
|
|
19999
|
+
async function refreshVaultFiles() {
|
|
20000
|
+
var listEl = document.getElementById('vault-files-list');
|
|
20001
|
+
if (!listEl) return;
|
|
20002
|
+
var q = document.getElementById('vault-files-search')?.value || '';
|
|
20003
|
+
var agent = document.getElementById('vault-files-agent-filter')?.value || '';
|
|
20004
|
+
var since = document.getElementById('vault-files-since')?.value || '30';
|
|
20005
|
+
// Show skeleton while loading
|
|
20006
|
+
listEl.innerHTML = '<div class="skel-block"><div class="skel-row med"></div><div class="skel-row"></div><div class="skel-row short"></div></div>';
|
|
20007
|
+
try {
|
|
20008
|
+
var url = '/api/vault-files?sinceDays=' + encodeURIComponent(since)
|
|
20009
|
+
+ (q ? '&q=' + encodeURIComponent(q) : '')
|
|
20010
|
+
+ (agent ? '&agent=' + encodeURIComponent(agent) : '')
|
|
20011
|
+
+ (_vaultFilesFolder ? '&folder=' + encodeURIComponent(_vaultFilesFolder) : '');
|
|
20012
|
+
var r = await apiFetch(url);
|
|
20013
|
+
var d = await r.json();
|
|
20014
|
+
var files = d.files || [];
|
|
20015
|
+
_vaultFilesCache = files;
|
|
20016
|
+
// Populate agent filter from ALL files response (use server's full set, not filtered)
|
|
20017
|
+
var agentSel = document.getElementById('vault-files-agent-filter');
|
|
20018
|
+
if (agentSel && agentSel.options.length <= 2) {
|
|
20019
|
+
var slugs = [...new Set(files.map(function(f) { return f.agentSlug; }).filter(Boolean))].sort();
|
|
20020
|
+
slugs.forEach(function(slug) {
|
|
20021
|
+
var opt = document.createElement('option');
|
|
20022
|
+
opt.value = slug;
|
|
20023
|
+
opt.textContent = slug;
|
|
20024
|
+
agentSel.appendChild(opt);
|
|
20025
|
+
});
|
|
20026
|
+
}
|
|
20027
|
+
// Render folder filter chips (using folderCounts from server)
|
|
20028
|
+
var chipsEl = document.getElementById('vault-files-folder-chips');
|
|
20029
|
+
if (chipsEl && d.folderCounts) {
|
|
20030
|
+
var folders = Object.entries(d.folderCounts).sort(function(a, b) { return b[1] - a[1]; });
|
|
20031
|
+
var totalCount = folders.reduce(function(s, p) { return s + p[1]; }, 0);
|
|
20032
|
+
var chipHtml = '<div class="vault-folder-chip' + (_vaultFilesFolder === '' ? ' active' : '') + '" data-folder="" onclick="setVaultFolderFilter(\\x27\\x27)">All <span style="opacity:0.6">' + totalCount + '</span></div>';
|
|
20033
|
+
folders.forEach(function(p) {
|
|
20034
|
+
var folder = p[0]; var count = p[1];
|
|
20035
|
+
if (!folder) return;
|
|
20036
|
+
chipHtml += '<div class="vault-folder-chip' + (_vaultFilesFolder === folder ? ' active' : '') + '" data-folder="' + esc(folder) + '" onclick="setVaultFolderFilter(\\x27' + esc(folder) + '\\x27)">' + esc(folder) + ' <span style="opacity:0.6">' + count + '</span></div>';
|
|
20037
|
+
});
|
|
20038
|
+
chipsEl.innerHTML = chipHtml;
|
|
20039
|
+
}
|
|
20040
|
+
if (files.length === 0) {
|
|
20041
|
+
listEl.innerHTML = '<div class="empty-cta"><div class="label">No recent files</div><div class="hint">Try a wider time window or different filter.</div></div>';
|
|
20042
|
+
return;
|
|
20043
|
+
}
|
|
20044
|
+
var html = '<div style="font-size:11px;color:var(--text-muted);margin-bottom:10px">Showing ' + files.length + ' of ' + d.total + ' files modified in the last ' + since + ' days.</div>';
|
|
20045
|
+
html += '<div style="display:flex;flex-direction:column;gap:1px;border:1px solid var(--border);border-radius:var(--radius-md);overflow:hidden;background:var(--bg-card)">';
|
|
20046
|
+
for (var i = 0; i < files.length; i++) {
|
|
20047
|
+
var f = files[i];
|
|
20048
|
+
var agentBadge = f.agentSlug
|
|
20049
|
+
? '<span style="font-size:10px;background:var(--clementine-bg);color:var(--clementine);padding:2px 7px;border-radius:var(--radius-xs);font-weight:500">' + esc(f.agentSlug) + '</span>'
|
|
20050
|
+
: '<span style="font-size:10px;background:var(--bg-tertiary);color:var(--text-muted);padding:2px 7px;border-radius:var(--radius-xs)">shared</span>';
|
|
20051
|
+
var typeBadge = f.type ? '<span style="font-size:10px;color:var(--text-muted);margin-right:6px">' + esc(f.type) + '</span>' : '';
|
|
20052
|
+
html += '<div class="vault-file-row clickable-row" data-path="' + esc(f.relPath) + '" style="display:flex;align-items:center;gap:12px;padding:10px 14px;background:var(--bg-secondary);border-bottom:1px solid var(--border-light);font-size:13px">'
|
|
20053
|
+
+ '<div style="flex:1;min-width:0">'
|
|
20054
|
+
+ '<div style="font-weight:500;color:var(--text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis">' + esc(f.title) + '</div>'
|
|
20055
|
+
+ '<div style="font-size:11px;color:var(--text-muted);font-family:\\x27JetBrains Mono\\x27,monospace;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:2px">' + esc(f.relPath) + '</div>'
|
|
20056
|
+
+ '</div>'
|
|
20057
|
+
+ '<div style="display:flex;align-items:center;gap:8px;flex-shrink:0">'
|
|
20058
|
+
+ typeBadge + agentBadge
|
|
20059
|
+
+ '<span style="font-size:11px;color:var(--text-muted);min-width:60px;text-align:right">' + esc(timeAgo(f.mtime)) + '</span>'
|
|
20060
|
+
+ '</div>'
|
|
20061
|
+
+ '</div>';
|
|
20062
|
+
}
|
|
20063
|
+
html += '</div>';
|
|
20064
|
+
listEl.innerHTML = html;
|
|
20065
|
+
// Wire row clicks
|
|
20066
|
+
listEl.querySelectorAll('.vault-file-row').forEach(function(row) {
|
|
20067
|
+
row.onclick = function() { openVaultFile(row.getAttribute('data-path')); };
|
|
20068
|
+
});
|
|
20069
|
+
} catch (err) {
|
|
20070
|
+
listEl.innerHTML = '<div style="padding:24px;color:var(--red);font-size:13px">Failed to load: ' + esc(String(err)) + '</div>';
|
|
20071
|
+
}
|
|
20072
|
+
}
|
|
20073
|
+
|
|
20074
|
+
async function openVaultFile(relPath) {
|
|
20075
|
+
if (!relPath) return;
|
|
20076
|
+
// Build/reuse a slide-out drawer for content preview
|
|
20077
|
+
var drawer = document.getElementById('vault-file-drawer');
|
|
20078
|
+
if (!drawer) {
|
|
20079
|
+
drawer = document.createElement('div');
|
|
20080
|
+
drawer.id = 'vault-file-drawer';
|
|
20081
|
+
drawer.style.cssText = 'position:fixed;right:0;top:0;bottom:0;width:560px;max-width:92vw;background:var(--bg-secondary);border-left:1px solid var(--border);box-shadow:-8px 0 32px rgba(0,0,0,0.18);z-index:200;display:flex;flex-direction:column;transform:translateX(100%);transition:transform 200ms ease';
|
|
20082
|
+
drawer.innerHTML =
|
|
20083
|
+
'<div style="display:flex;align-items:center;gap:10px;padding:14px 18px;border-bottom:1px solid var(--border);flex-shrink:0">'
|
|
20084
|
+
+ '<div style="flex:1;min-width:0">'
|
|
20085
|
+
+ '<div id="vault-file-drawer-title" style="font-weight:600;font-size:15px;letter-spacing:-0.01em"></div>'
|
|
20086
|
+
+ '<div id="vault-file-drawer-path" style="font-size:11px;color:var(--text-muted);font-family:\\x27JetBrains Mono\\x27,monospace;margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis"></div>'
|
|
20087
|
+
+ '</div>'
|
|
20088
|
+
+ '<button class="btn-icon btn-sm" onclick="closeVaultFileDrawer()" title="Close">' + lucide('x', 'icn-sm') + '</button>'
|
|
20089
|
+
+ '</div>'
|
|
20090
|
+
+ '<div id="vault-file-drawer-body" style="flex:1;overflow-y:auto;padding:18px 22px;font-size:13px;line-height:1.55"></div>';
|
|
20091
|
+
document.body.appendChild(drawer);
|
|
20092
|
+
}
|
|
20093
|
+
var titleEl = document.getElementById('vault-file-drawer-title');
|
|
20094
|
+
var pathEl = document.getElementById('vault-file-drawer-path');
|
|
20095
|
+
var body = document.getElementById('vault-file-drawer-body');
|
|
20096
|
+
if (titleEl) titleEl.textContent = relPath.split('/').pop().replace(/\\.md$/, '');
|
|
20097
|
+
if (pathEl) pathEl.textContent = relPath;
|
|
20098
|
+
if (body) body.innerHTML = '<div class="skel-block"><div class="skel-row"></div><div class="skel-row med"></div><div class="skel-row short"></div></div>';
|
|
20099
|
+
drawer.style.transform = 'translateX(0)';
|
|
20100
|
+
try {
|
|
20101
|
+
var r = await apiFetch('/api/vault-file?path=' + encodeURIComponent(relPath));
|
|
20102
|
+
var d = await r.json();
|
|
20103
|
+
if (d.error) {
|
|
20104
|
+
body.innerHTML = '<div style="color:var(--red)">' + esc(d.error) + '</div>';
|
|
20105
|
+
return;
|
|
20106
|
+
}
|
|
20107
|
+
body.innerHTML = renderMd(d.content);
|
|
20108
|
+
} catch (err) {
|
|
20109
|
+
body.innerHTML = '<div style="color:var(--red)">Failed: ' + esc(String(err)) + '</div>';
|
|
20110
|
+
}
|
|
20111
|
+
}
|
|
20112
|
+
|
|
20113
|
+
function closeVaultFileDrawer() {
|
|
20114
|
+
var drawer = document.getElementById('vault-file-drawer');
|
|
20115
|
+
if (drawer) drawer.style.transform = 'translateX(100%)';
|
|
20116
|
+
}
|
|
20117
|
+
|
|
20118
|
+
function setVaultFolderFilter(folder) {
|
|
20119
|
+
_vaultFilesFolder = folder || '';
|
|
20120
|
+
refreshVaultFiles();
|
|
20121
|
+
}
|
|
20122
|
+
|
|
19784
20123
|
// ── Goals: inline create form ────────────────────────────────────
|
|
19785
20124
|
function openNewGoalForm() {
|
|
19786
20125
|
var el = document.getElementById('new-goal-form');
|
|
@@ -20561,44 +20900,53 @@ function toggleHomeRail() {
|
|
|
20561
20900
|
}
|
|
20562
20901
|
}
|
|
20563
20902
|
|
|
20903
|
+
function _railCard(bodyId) {
|
|
20904
|
+
var body = document.getElementById(bodyId);
|
|
20905
|
+
return body ? body.closest('.rail-card') : null;
|
|
20906
|
+
}
|
|
20907
|
+
function _setRailEmpty(bodyId, isEmpty) {
|
|
20908
|
+
var card = _railCard(bodyId);
|
|
20909
|
+
if (card) card.classList.toggle('empty', !!isEmpty);
|
|
20910
|
+
}
|
|
20911
|
+
|
|
20564
20912
|
async function refreshHomeRail() {
|
|
20565
|
-
// Daemon status
|
|
20913
|
+
// Daemon status — only surface when explicitly stopped. Treat null/undefined
|
|
20914
|
+
// (running-state unknown) as "fine, hide" since the dashboard wouldn't be
|
|
20915
|
+
// serving requests if the daemon were truly down.
|
|
20566
20916
|
try {
|
|
20567
20917
|
var rs = await apiFetch('/api/status');
|
|
20568
20918
|
var ds = await rs.json();
|
|
20919
|
+
var stopped = ds.running === false;
|
|
20569
20920
|
var pip = document.querySelector('#rail-daemon-body .agent-activity-dot');
|
|
20570
20921
|
var label = document.querySelector('#rail-daemon-body .agent-activity span:last-child');
|
|
20571
|
-
if (label) label.textContent =
|
|
20572
|
-
if (pip) pip.style.background =
|
|
20922
|
+
if (label) label.textContent = stopped ? 'Daemon stopped' : 'Running';
|
|
20923
|
+
if (pip) pip.style.background = stopped ? '#ef4444' : '#22c55e';
|
|
20573
20924
|
var up = document.getElementById('rail-daemon-uptime');
|
|
20574
20925
|
if (up && ds.uptimeMs) up.textContent = Math.round(ds.uptimeMs / 60000) + 'm';
|
|
20575
|
-
|
|
20926
|
+
_setRailEmpty('rail-daemon-body', !stopped);
|
|
20927
|
+
} catch { _setRailEmpty('rail-daemon-body', true); }
|
|
20576
20928
|
|
|
20577
|
-
// Today's plan (compact)
|
|
20929
|
+
// Today's plan (compact). Hide card if no plan or zero items.
|
|
20578
20930
|
try {
|
|
20579
20931
|
var rp = await apiFetch('/api/daily-plan');
|
|
20580
20932
|
var dp = await rp.json();
|
|
20581
20933
|
var planEl = document.getElementById('home-plan-content');
|
|
20934
|
+
var items = dp && dp.plan ? (dp.plan.items || []) : [];
|
|
20582
20935
|
if (planEl) {
|
|
20583
|
-
if (
|
|
20584
|
-
planEl.innerHTML = '<div style="font-size:
|
|
20936
|
+
if (items.length === 0) {
|
|
20937
|
+
planEl.innerHTML = '<div style="font-size:11px;color:var(--text-muted)">No plan yet today.</div>';
|
|
20585
20938
|
} else {
|
|
20586
|
-
|
|
20587
|
-
|
|
20588
|
-
|
|
20589
|
-
} else {
|
|
20590
|
-
planEl.innerHTML = items.map(function(it) {
|
|
20591
|
-
return '<div class="rail-row"><span class="label">' + esc(it.title || it.text || '') + '</span><span class="meta">' + esc(it.time || '') + '</span></div>';
|
|
20592
|
-
}).join('');
|
|
20593
|
-
}
|
|
20939
|
+
planEl.innerHTML = items.slice(0, 4).map(function(it) {
|
|
20940
|
+
return '<div class="rail-row"><span class="label">' + esc(it.title || it.text || '') + '</span><span class="meta">' + esc(it.time || '') + '</span></div>';
|
|
20941
|
+
}).join('');
|
|
20594
20942
|
}
|
|
20595
20943
|
}
|
|
20944
|
+
_setRailEmpty('home-plan-content', items.length === 0);
|
|
20596
20945
|
} catch {
|
|
20597
|
-
|
|
20598
|
-
if (pe) pe.innerHTML = '<div style="font-size:11px;color:var(--text-muted)">No plan available.</div>';
|
|
20946
|
+
_setRailEmpty('home-plan-content', true);
|
|
20599
20947
|
}
|
|
20600
20948
|
|
|
20601
|
-
// Upcoming cron fires (next 3)
|
|
20949
|
+
// Upcoming cron fires (next 3) — hide card if nothing scheduled
|
|
20602
20950
|
try {
|
|
20603
20951
|
var rc = await apiFetch('/api/cron');
|
|
20604
20952
|
var dc = await rc.json();
|
|
@@ -20609,13 +20957,14 @@ async function refreshHomeRail() {
|
|
|
20609
20957
|
var uc = document.getElementById('rail-upcoming-count');
|
|
20610
20958
|
if (uc) uc.textContent = String(jobs.length);
|
|
20611
20959
|
if (ue) {
|
|
20612
|
-
ue.innerHTML = top.
|
|
20960
|
+
ue.innerHTML = top.map(function(j) {
|
|
20613
20961
|
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>';
|
|
20614
|
-
}).join('')
|
|
20962
|
+
}).join('');
|
|
20615
20963
|
}
|
|
20616
|
-
|
|
20964
|
+
_setRailEmpty('rail-upcoming', top.length === 0);
|
|
20965
|
+
} catch { _setRailEmpty('rail-upcoming', true); }
|
|
20617
20966
|
|
|
20618
|
-
// Active unleashed runs
|
|
20967
|
+
// Active unleashed runs — hide card unless something running
|
|
20619
20968
|
try {
|
|
20620
20969
|
var ru = await apiFetch('/api/unleashed');
|
|
20621
20970
|
var du = await ru.json();
|
|
@@ -20627,25 +20976,27 @@ async function refreshHomeRail() {
|
|
|
20627
20976
|
else ac.style.display = 'none';
|
|
20628
20977
|
}
|
|
20629
20978
|
if (ae) {
|
|
20630
|
-
ae.innerHTML = active.
|
|
20979
|
+
ae.innerHTML = active.map(function(t) {
|
|
20631
20980
|
return '<div class="rail-row clickable-row" onclick="navigateTo(\\x27build\\x27,{tab:\\x27workflows\\x27})"><span class="label">' + esc(t.name) + '</span><span class="meta">' + esc(t.phase || '') + '</span></div>';
|
|
20632
|
-
}).join('')
|
|
20981
|
+
}).join('');
|
|
20633
20982
|
}
|
|
20634
|
-
|
|
20983
|
+
_setRailEmpty('rail-active', active.length === 0);
|
|
20984
|
+
} catch { _setRailEmpty('rail-active', true); }
|
|
20635
20985
|
|
|
20636
|
-
// Time saved (
|
|
20986
|
+
// Time saved (compact). Hide if zero.
|
|
20637
20987
|
try {
|
|
20638
20988
|
var rm = await apiFetch('/api/metrics?period=week');
|
|
20639
20989
|
var dm = await rm.json();
|
|
20640
20990
|
var minutes = ((dm.cronRuns || 0) * 5) + ((dm.exchanges || 0) * 2);
|
|
20641
20991
|
var ts = document.getElementById('rail-time-saved');
|
|
20642
20992
|
if (ts) {
|
|
20643
|
-
if (minutes >= 60) ts.innerHTML = '<div style="font-size:
|
|
20644
|
-
else ts.innerHTML = '<div style="font-size:
|
|
20993
|
+
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>';
|
|
20994
|
+
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>';
|
|
20645
20995
|
}
|
|
20646
|
-
|
|
20996
|
+
_setRailEmpty('rail-time-saved', minutes === 0);
|
|
20997
|
+
} catch { _setRailEmpty('rail-time-saved', true); }
|
|
20647
20998
|
|
|
20648
|
-
// Approvals
|
|
20999
|
+
// Approvals — hide card unless something pending
|
|
20649
21000
|
try {
|
|
20650
21001
|
var rsi = await apiFetch('/api/self-improve');
|
|
20651
21002
|
var dsi = await rsi.json();
|
|
@@ -20657,12 +21008,12 @@ async function refreshHomeRail() {
|
|
|
20657
21008
|
else ac2.style.display = 'none';
|
|
20658
21009
|
}
|
|
20659
21010
|
if (ae2) {
|
|
20660
|
-
|
|
20661
|
-
else ae2.innerHTML = pending.slice(0, 3).map(function(p) {
|
|
21011
|
+
ae2.innerHTML = pending.slice(0, 3).map(function(p) {
|
|
20662
21012
|
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>';
|
|
20663
21013
|
}).join('');
|
|
20664
21014
|
}
|
|
20665
|
-
|
|
21015
|
+
_setRailEmpty('rail-approvals', pending.length === 0);
|
|
21016
|
+
} catch { _setRailEmpty('rail-approvals', true); }
|
|
20666
21017
|
}
|
|
20667
21018
|
|
|
20668
21019
|
function timeUntil(iso) {
|
|
@@ -23992,6 +24343,8 @@ async function refreshSalesforce() {
|
|
|
23992
24343
|
if (d.status) { try { refreshStatus(d.status); } catch(e) { console.warn('init: status', e); } }
|
|
23993
24344
|
if (d.activity) { try { refreshActivity(false, d.activity); } catch(e) { console.warn('init: activity', e); } }
|
|
23994
24345
|
else { try { refreshActivity(); } catch(e) { console.warn('init: activity fallback', e); } }
|
|
24346
|
+
// Populate the home right rail (daemon, plan, runs, time-saved, approvals)
|
|
24347
|
+
if (typeof refreshHomeRail === 'function') { try { refreshHomeRail(); } catch(e) { console.warn('init: rail', e); } }
|
|
23995
24348
|
if (d.office) { try { refreshTeamNav(d.office); refreshTeamPulse(d.office); } catch(e) { console.warn('init: office', e); } }
|
|
23996
24349
|
if (d.plan) { try { refreshHomePlan(d.plan); } catch(e) { console.warn('init: plan', e); } }
|
|
23997
24350
|
if (d.version) { try { _loadedHash = d.version.started; } catch(e) { /* ignore */ } }
|