clementine-agent 1.2.3 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/dashboard.js +2501 -681
- package/dist/cli/static/LICENSE-NOTICES.md +12 -0
- package/dist/cli/static/drawflow.min.css +1 -0
- package/dist/cli/static/drawflow.min.js +1 -0
- package/dist/dashboard/builder/dry-run.d.ts +31 -0
- package/dist/dashboard/builder/dry-run.js +138 -0
- package/dist/dashboard/builder/events.d.ts +23 -0
- package/dist/dashboard/builder/events.js +28 -0
- package/dist/dashboard/builder/mcp-invoke.d.ts +25 -0
- package/dist/dashboard/builder/mcp-invoke.js +143 -0
- package/dist/dashboard/builder/runner.d.ts +68 -0
- package/dist/dashboard/builder/runner.js +418 -0
- package/dist/dashboard/builder/serializer.d.ts +79 -0
- package/dist/dashboard/builder/serializer.js +547 -0
- package/dist/dashboard/builder/snapshots.d.ts +32 -0
- package/dist/dashboard/builder/snapshots.js +138 -0
- package/dist/dashboard/builder/validation.d.ts +26 -0
- package/dist/dashboard/builder/validation.js +183 -0
- package/dist/gateway/router.js +31 -2
- package/dist/index.js +18 -0
- package/dist/tools/builder-tools.d.ts +13 -0
- package/dist/tools/builder-tools.js +437 -0
- package/dist/tools/mcp-server.js +2 -0
- package/dist/types.d.ts +46 -0
- package/package.json +2 -2
- package/vault/00-System/skills/builder-canvas.md +126 -0
package/dist/cli/dashboard.js
CHANGED
|
@@ -1265,6 +1265,13 @@ export async function cmdDashboard(opts) {
|
|
|
1265
1265
|
const app = express();
|
|
1266
1266
|
const jsonParser = express.json({ limit: '5mb' });
|
|
1267
1267
|
const rawBodyParser = express.raw({ type: '*/*', limit: '5mb' });
|
|
1268
|
+
// Vendored frontend assets (Drawflow for the Builder canvas, etc.)
|
|
1269
|
+
// Public — no auth required.
|
|
1270
|
+
app.use('/static', express.static(path.join(__dirname, 'static'), {
|
|
1271
|
+
maxAge: '7d',
|
|
1272
|
+
etag: true,
|
|
1273
|
+
index: false,
|
|
1274
|
+
}));
|
|
1268
1275
|
// Health check — always responds, no auth, no middleware dependency
|
|
1269
1276
|
app.get('/health', (_req, res) => { res.json({ ok: true, ts: Date.now() }); });
|
|
1270
1277
|
// ── Webhook ingestion (raw-body, HMAC-authed) ─────────────────────
|
|
@@ -2403,6 +2410,260 @@ export async function cmdDashboard(opts) {
|
|
|
2403
2410
|
}
|
|
2404
2411
|
// Let the lazy-gateway dispatcher publish deep_result events through SSE.
|
|
2405
2412
|
dashboardSseBroadcast = broadcastEvent;
|
|
2413
|
+
// ── Builder event bridge ──────────────────────────────────────
|
|
2414
|
+
// Forward events from src/dashboard/builder/events.ts through SSE so the
|
|
2415
|
+
// Builder page can update its canvas live as the agent edits via MCP tools.
|
|
2416
|
+
(async () => {
|
|
2417
|
+
try {
|
|
2418
|
+
const { onAnyBuilderEvent } = await import('../dashboard/builder/events.js');
|
|
2419
|
+
onAnyBuilderEvent((e) => {
|
|
2420
|
+
broadcastEvent({ type: 'builder', data: e });
|
|
2421
|
+
});
|
|
2422
|
+
}
|
|
2423
|
+
catch {
|
|
2424
|
+
// Builder event bridge optional; ignore if module fails to load.
|
|
2425
|
+
}
|
|
2426
|
+
})();
|
|
2427
|
+
// ── Builder API routes ─────────────────────────────────────────
|
|
2428
|
+
app.get('/api/builder/workflows', async (_req, res) => {
|
|
2429
|
+
try {
|
|
2430
|
+
const { listAllForBuilder } = await import('../dashboard/builder/serializer.js');
|
|
2431
|
+
res.json({ workflows: listAllForBuilder() });
|
|
2432
|
+
}
|
|
2433
|
+
catch (err) {
|
|
2434
|
+
res.status(500).json({ error: 'Failed to list workflows', detail: String(err) });
|
|
2435
|
+
}
|
|
2436
|
+
});
|
|
2437
|
+
app.get('/api/builder/workflows/:id', async (req, res) => {
|
|
2438
|
+
try {
|
|
2439
|
+
const id = decodeURIComponent(req.params.id);
|
|
2440
|
+
const [{ readWorkflow, workflowToDrawflow }, { validateWorkflow }] = await Promise.all([
|
|
2441
|
+
import('../dashboard/builder/serializer.js'),
|
|
2442
|
+
import('../dashboard/builder/validation.js'),
|
|
2443
|
+
]);
|
|
2444
|
+
const wf = readWorkflow(id);
|
|
2445
|
+
if (!wf) {
|
|
2446
|
+
res.status(404).json({ error: 'Not found' });
|
|
2447
|
+
return;
|
|
2448
|
+
}
|
|
2449
|
+
res.json({
|
|
2450
|
+
id,
|
|
2451
|
+
workflow: wf,
|
|
2452
|
+
drawflow: workflowToDrawflow(wf),
|
|
2453
|
+
validation: validateWorkflow(wf),
|
|
2454
|
+
});
|
|
2455
|
+
}
|
|
2456
|
+
catch (err) {
|
|
2457
|
+
res.status(500).json({ error: 'Failed to read workflow', detail: String(err) });
|
|
2458
|
+
}
|
|
2459
|
+
});
|
|
2460
|
+
app.put('/api/builder/workflows/:id', async (req, res) => {
|
|
2461
|
+
try {
|
|
2462
|
+
const id = decodeURIComponent(req.params.id);
|
|
2463
|
+
const body = req.body;
|
|
2464
|
+
if (!body || typeof body.workflow !== 'object') {
|
|
2465
|
+
res.status(400).json({ error: 'Missing workflow body' });
|
|
2466
|
+
return;
|
|
2467
|
+
}
|
|
2468
|
+
const [{ readWorkflow, saveWorkflow }, { validateWorkflow }, { emitBuilderEvent }] = await Promise.all([
|
|
2469
|
+
import('../dashboard/builder/serializer.js'),
|
|
2470
|
+
import('../dashboard/builder/validation.js'),
|
|
2471
|
+
import('../dashboard/builder/events.js'),
|
|
2472
|
+
]);
|
|
2473
|
+
const existing = readWorkflow(id);
|
|
2474
|
+
if (!existing) {
|
|
2475
|
+
res.status(404).json({ error: 'Not found' });
|
|
2476
|
+
return;
|
|
2477
|
+
}
|
|
2478
|
+
const incoming = body.workflow;
|
|
2479
|
+
const next = { ...incoming, sourceFile: existing.sourceFile };
|
|
2480
|
+
const v = validateWorkflow(next);
|
|
2481
|
+
if (!v.ok && !body.force) {
|
|
2482
|
+
res.status(400).json({ error: 'validation', validation: v });
|
|
2483
|
+
return;
|
|
2484
|
+
}
|
|
2485
|
+
const result = saveWorkflow(id, next);
|
|
2486
|
+
if (!result.ok) {
|
|
2487
|
+
res.status(400).json({ error: result.error });
|
|
2488
|
+
return;
|
|
2489
|
+
}
|
|
2490
|
+
emitBuilderEvent({ type: 'workflow:patched', workflowId: id, payload: { workflow: next } });
|
|
2491
|
+
res.json({ ok: true, validation: v });
|
|
2492
|
+
}
|
|
2493
|
+
catch (err) {
|
|
2494
|
+
res.status(500).json({ error: 'Failed to save workflow', detail: String(err) });
|
|
2495
|
+
}
|
|
2496
|
+
});
|
|
2497
|
+
app.get('/api/builder/mcp-discovery', async (_req, res) => {
|
|
2498
|
+
try {
|
|
2499
|
+
const { discoverMcpServers, loadToolInventory } = await import('../agent/mcp-bridge.js');
|
|
2500
|
+
const servers = discoverMcpServers();
|
|
2501
|
+
const inv = loadToolInventory();
|
|
2502
|
+
const out = servers.map((s) => ({
|
|
2503
|
+
name: s.name,
|
|
2504
|
+
enabled: s.enabled,
|
|
2505
|
+
tools: (inv?.tools ?? [])
|
|
2506
|
+
.filter((t) => t.startsWith(`mcp__${s.name}__`))
|
|
2507
|
+
.map((t) => t.split('__')[2])
|
|
2508
|
+
.filter((x) => !!x),
|
|
2509
|
+
}));
|
|
2510
|
+
res.json({ servers: out });
|
|
2511
|
+
}
|
|
2512
|
+
catch (err) {
|
|
2513
|
+
res.status(500).json({ error: 'mcp-discovery failed', detail: String(err) });
|
|
2514
|
+
}
|
|
2515
|
+
});
|
|
2516
|
+
app.post('/api/builder/workflows/:id/save-from-drawflow', async (req, res) => {
|
|
2517
|
+
try {
|
|
2518
|
+
const id = decodeURIComponent(req.params.id);
|
|
2519
|
+
const body = req.body;
|
|
2520
|
+
if (!body || !body.drawflow) {
|
|
2521
|
+
res.status(400).json({ error: 'Missing drawflow body' });
|
|
2522
|
+
return;
|
|
2523
|
+
}
|
|
2524
|
+
const [{ readWorkflow, drawflowToWorkflow, saveWorkflow, workflowToDrawflow }, { validateWorkflow }, { emitBuilderEvent }] = await Promise.all([
|
|
2525
|
+
import('../dashboard/builder/serializer.js'),
|
|
2526
|
+
import('../dashboard/builder/validation.js'),
|
|
2527
|
+
import('../dashboard/builder/events.js'),
|
|
2528
|
+
]);
|
|
2529
|
+
const existing = readWorkflow(id);
|
|
2530
|
+
if (!existing) {
|
|
2531
|
+
res.status(404).json({ error: 'Not found' });
|
|
2532
|
+
return;
|
|
2533
|
+
}
|
|
2534
|
+
const next = drawflowToWorkflow(body.drawflow, existing);
|
|
2535
|
+
const v = validateWorkflow(next);
|
|
2536
|
+
if (!v.ok && !body.force) {
|
|
2537
|
+
res.status(400).json({ error: 'validation', validation: v });
|
|
2538
|
+
return;
|
|
2539
|
+
}
|
|
2540
|
+
const result = saveWorkflow(id, next);
|
|
2541
|
+
if (!result.ok) {
|
|
2542
|
+
res.status(400).json({ error: result.error });
|
|
2543
|
+
return;
|
|
2544
|
+
}
|
|
2545
|
+
emitBuilderEvent({ type: 'workflow:patched', workflowId: id, payload: { workflow: next, saveToken: body.saveToken ?? null } });
|
|
2546
|
+
res.json({ ok: true, validation: v, drawflow: workflowToDrawflow(next) });
|
|
2547
|
+
}
|
|
2548
|
+
catch (err) {
|
|
2549
|
+
res.status(500).json({ error: 'Save failed', detail: String(err) });
|
|
2550
|
+
}
|
|
2551
|
+
});
|
|
2552
|
+
app.post('/api/builder/workflows/:id/validate', async (req, res) => {
|
|
2553
|
+
try {
|
|
2554
|
+
const id = decodeURIComponent(req.params.id);
|
|
2555
|
+
const [{ readWorkflow }, { validateWorkflow }] = await Promise.all([
|
|
2556
|
+
import('../dashboard/builder/serializer.js'),
|
|
2557
|
+
import('../dashboard/builder/validation.js'),
|
|
2558
|
+
]);
|
|
2559
|
+
const wf = readWorkflow(id);
|
|
2560
|
+
if (!wf) {
|
|
2561
|
+
res.status(404).json({ error: 'Not found' });
|
|
2562
|
+
return;
|
|
2563
|
+
}
|
|
2564
|
+
res.json(validateWorkflow(wf));
|
|
2565
|
+
}
|
|
2566
|
+
catch (err) {
|
|
2567
|
+
res.status(500).json({ error: 'Validate failed', detail: String(err) });
|
|
2568
|
+
}
|
|
2569
|
+
});
|
|
2570
|
+
app.post('/api/builder/workflows/:id/test', async (req, res) => {
|
|
2571
|
+
try {
|
|
2572
|
+
const id = decodeURIComponent(req.params.id);
|
|
2573
|
+
const body = (req.body ?? {});
|
|
2574
|
+
const [{ readWorkflow }, { runWorkflowTest }] = await Promise.all([
|
|
2575
|
+
import('../dashboard/builder/serializer.js'),
|
|
2576
|
+
import('../dashboard/builder/runner.js'),
|
|
2577
|
+
]);
|
|
2578
|
+
const wf = readWorkflow(id);
|
|
2579
|
+
if (!wf) {
|
|
2580
|
+
res.status(404).json({ error: 'Not found' });
|
|
2581
|
+
return;
|
|
2582
|
+
}
|
|
2583
|
+
const runId = (await import('node:crypto')).randomUUID();
|
|
2584
|
+
// Kick off async — respond immediately with the runId; events stream over SSE.
|
|
2585
|
+
res.json({ ok: true, runId });
|
|
2586
|
+
runWorkflowTest(wf, {
|
|
2587
|
+
workflowId: id,
|
|
2588
|
+
runId,
|
|
2589
|
+
mode: body.mode ?? 'mock',
|
|
2590
|
+
perStepTimeoutMs: body.perStepTimeoutMs,
|
|
2591
|
+
totalBudgetMs: body.totalBudgetMs,
|
|
2592
|
+
}).catch(() => { });
|
|
2593
|
+
}
|
|
2594
|
+
catch (err) {
|
|
2595
|
+
res.status(500).json({ error: 'Test failed to start', detail: String(err) });
|
|
2596
|
+
}
|
|
2597
|
+
});
|
|
2598
|
+
app.post('/api/builder/runs/:runId/cancel', async (req, res) => {
|
|
2599
|
+
try {
|
|
2600
|
+
const runId = decodeURIComponent(req.params.runId);
|
|
2601
|
+
const { cancelRun } = await import('../dashboard/builder/runner.js');
|
|
2602
|
+
const cancelled = cancelRun(runId);
|
|
2603
|
+
res.json({ ok: cancelled });
|
|
2604
|
+
}
|
|
2605
|
+
catch (err) {
|
|
2606
|
+
res.status(500).json({ error: 'Cancel failed', detail: String(err) });
|
|
2607
|
+
}
|
|
2608
|
+
});
|
|
2609
|
+
app.post('/api/builder/workflows/:id/dry-run', async (req, res) => {
|
|
2610
|
+
try {
|
|
2611
|
+
const id = decodeURIComponent(req.params.id);
|
|
2612
|
+
const [{ readWorkflow }, { dryRunWorkflow }] = await Promise.all([
|
|
2613
|
+
import('../dashboard/builder/serializer.js'),
|
|
2614
|
+
import('../dashboard/builder/dry-run.js'),
|
|
2615
|
+
]);
|
|
2616
|
+
const wf = readWorkflow(id);
|
|
2617
|
+
if (!wf) {
|
|
2618
|
+
res.status(404).json({ error: 'Not found' });
|
|
2619
|
+
return;
|
|
2620
|
+
}
|
|
2621
|
+
res.json(dryRunWorkflow(wf));
|
|
2622
|
+
}
|
|
2623
|
+
catch (err) {
|
|
2624
|
+
res.status(500).json({ error: 'Dry-run failed', detail: String(err) });
|
|
2625
|
+
}
|
|
2626
|
+
});
|
|
2627
|
+
app.post('/api/builder/workflows', async (req, res) => {
|
|
2628
|
+
try {
|
|
2629
|
+
const body = req.body;
|
|
2630
|
+
if (!body || !body.name) {
|
|
2631
|
+
res.status(400).json({ error: 'name required' });
|
|
2632
|
+
return;
|
|
2633
|
+
}
|
|
2634
|
+
const [{ saveWorkflow, workflowId }, { emitBuilderEvent }] = await Promise.all([
|
|
2635
|
+
import('../dashboard/builder/serializer.js'),
|
|
2636
|
+
import('../dashboard/builder/events.js'),
|
|
2637
|
+
]);
|
|
2638
|
+
const slug = body.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 64) || 'workflow';
|
|
2639
|
+
const wf = {
|
|
2640
|
+
name: body.name,
|
|
2641
|
+
description: body.description ?? '',
|
|
2642
|
+
enabled: true,
|
|
2643
|
+
trigger: body.schedule ? { schedule: body.schedule, manual: false } : { manual: true },
|
|
2644
|
+
inputs: {},
|
|
2645
|
+
steps: [{
|
|
2646
|
+
id: 's1',
|
|
2647
|
+
prompt: body.initialPrompt ?? 'Describe what this workflow should do.',
|
|
2648
|
+
dependsOn: [],
|
|
2649
|
+
tier: 1,
|
|
2650
|
+
maxTurns: 15,
|
|
2651
|
+
}],
|
|
2652
|
+
sourceFile: '',
|
|
2653
|
+
};
|
|
2654
|
+
const id = workflowId(slug);
|
|
2655
|
+
const result = saveWorkflow(id, wf);
|
|
2656
|
+
if (!result.ok) {
|
|
2657
|
+
res.status(400).json({ error: result.error });
|
|
2658
|
+
return;
|
|
2659
|
+
}
|
|
2660
|
+
emitBuilderEvent({ type: 'workflow:created', workflowId: id, payload: { workflow: wf } });
|
|
2661
|
+
res.json({ ok: true, id });
|
|
2662
|
+
}
|
|
2663
|
+
catch (err) {
|
|
2664
|
+
res.status(500).json({ error: 'Create failed', detail: String(err) });
|
|
2665
|
+
}
|
|
2666
|
+
});
|
|
2406
2667
|
// SSE events handler moved before auth middleware (see above)
|
|
2407
2668
|
// ── POST routes (actions) ──────────────────────────────────────
|
|
2408
2669
|
app.post('/api/cron/run/:job', (req, res) => {
|
|
@@ -4622,6 +4883,33 @@ If the tool returns nothing or errors, return an empty array \`[]\`.`,
|
|
|
4622
4883
|
res.status(500).json({ error: String(err) });
|
|
4623
4884
|
}
|
|
4624
4885
|
});
|
|
4886
|
+
app.post('/api/memory/health/action', async (req, res) => {
|
|
4887
|
+
try {
|
|
4888
|
+
const action = (req.body?.action ?? '');
|
|
4889
|
+
const gateway = await getGateway();
|
|
4890
|
+
const store = gateway.assistant?.memoryStore;
|
|
4891
|
+
if (!store) {
|
|
4892
|
+
res.status(503).json({ error: 'Memory store not available' });
|
|
4893
|
+
return;
|
|
4894
|
+
}
|
|
4895
|
+
if (action === 'janitor') {
|
|
4896
|
+
const { runJanitor } = await import('../memory/maintenance.js');
|
|
4897
|
+
const result = runJanitor(store);
|
|
4898
|
+
res.json({ ok: true, action, result });
|
|
4899
|
+
return;
|
|
4900
|
+
}
|
|
4901
|
+
if (action === 'rebuild-fts' || action === 'fix-orphans') {
|
|
4902
|
+
const { runIntegrityProbes } = await import('../memory/integrity.js');
|
|
4903
|
+
const report = runIntegrityProbes(store);
|
|
4904
|
+
res.json({ ok: true, action, report });
|
|
4905
|
+
return;
|
|
4906
|
+
}
|
|
4907
|
+
res.status(400).json({ error: 'Unknown action: ' + action });
|
|
4908
|
+
}
|
|
4909
|
+
catch (err) {
|
|
4910
|
+
res.status(500).json({ error: String(err) });
|
|
4911
|
+
}
|
|
4912
|
+
});
|
|
4625
4913
|
app.get('/api/memory/chunks/:id', async (req, res) => {
|
|
4626
4914
|
try {
|
|
4627
4915
|
const id = Number(req.params.id);
|
|
@@ -7831,6 +8119,7 @@ function getDashboardHTML(token) {
|
|
|
7831
8119
|
if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then(function(r){r.forEach(function(sw){sw.unregister()})});caches.keys().then(function(k){k.forEach(function(n){caches.delete(n)})})}
|
|
7832
8120
|
</script>
|
|
7833
8121
|
<link rel="icon" href="/icon.svg" type="image/svg+xml">
|
|
8122
|
+
<link rel="stylesheet" href="/static/drawflow.min.css">
|
|
7834
8123
|
<title>${name} Command Center</title>
|
|
7835
8124
|
<style>
|
|
7836
8125
|
:root {
|
|
@@ -8090,136 +8379,449 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
8090
8379
|
letter-spacing: -0.02em;
|
|
8091
8380
|
}
|
|
8092
8381
|
|
|
8093
|
-
/* ──
|
|
8094
|
-
.
|
|
8095
|
-
|
|
8096
|
-
|
|
8097
|
-
|
|
8098
|
-
|
|
8099
|
-
margin-bottom: 16px;
|
|
8100
|
-
overflow: hidden;
|
|
8101
|
-
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
|
|
8102
|
-
}
|
|
8103
|
-
.card-header {
|
|
8104
|
-
padding: 14px 18px;
|
|
8382
|
+
/* ── Standard page header (.page-head) — applied to most info pages ── */
|
|
8383
|
+
.page-head {
|
|
8384
|
+
display: flex;
|
|
8385
|
+
align-items: flex-start;
|
|
8386
|
+
gap: 14px;
|
|
8387
|
+
padding: 18px 22px 14px;
|
|
8105
8388
|
border-bottom: 1px solid var(--border);
|
|
8389
|
+
margin-bottom: 18px;
|
|
8390
|
+
flex-wrap: wrap;
|
|
8391
|
+
}
|
|
8392
|
+
.page-head .icon {
|
|
8393
|
+
width: 36px;
|
|
8394
|
+
height: 36px;
|
|
8395
|
+
border-radius: 8px;
|
|
8396
|
+
background: linear-gradient(135deg, rgba(255,140,33,0.15), rgba(255,140,33,0.04));
|
|
8397
|
+
color: var(--clementine);
|
|
8106
8398
|
display: flex;
|
|
8107
8399
|
align-items: center;
|
|
8108
|
-
justify-content:
|
|
8109
|
-
font-size:
|
|
8400
|
+
justify-content: center;
|
|
8401
|
+
font-size: 20px;
|
|
8402
|
+
flex-shrink: 0;
|
|
8403
|
+
}
|
|
8404
|
+
.page-head .title-block {
|
|
8405
|
+
flex: 1;
|
|
8406
|
+
min-width: 220px;
|
|
8407
|
+
}
|
|
8408
|
+
.page-head .title-block h1 {
|
|
8409
|
+
font-size: 20px;
|
|
8110
8410
|
font-weight: 600;
|
|
8411
|
+
margin: 0 0 2px;
|
|
8412
|
+
letter-spacing: -0.01em;
|
|
8111
8413
|
color: var(--text-primary);
|
|
8112
8414
|
}
|
|
8113
|
-
.
|
|
8114
|
-
padding: 18px;
|
|
8415
|
+
.page-head .title-block .desc {
|
|
8115
8416
|
font-size: 13px;
|
|
8116
|
-
|
|
8117
|
-
|
|
8118
|
-
|
|
8119
|
-
display: grid;
|
|
8120
|
-
grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
|
|
8121
|
-
gap: 20px;
|
|
8122
|
-
}
|
|
8123
|
-
|
|
8124
|
-
/* ── Getting Started ────────────────────── */
|
|
8125
|
-
.getting-started {
|
|
8126
|
-
background: linear-gradient(135deg, var(--clementine-bg), rgba(77,158,255,0.06));
|
|
8127
|
-
border: 1px solid var(--border);
|
|
8128
|
-
border-radius: 16px;
|
|
8129
|
-
padding: 24px;
|
|
8130
|
-
margin-bottom: 24px;
|
|
8131
|
-
position: relative;
|
|
8417
|
+
color: var(--text-muted);
|
|
8418
|
+
margin: 0;
|
|
8419
|
+
line-height: 1.4;
|
|
8132
8420
|
}
|
|
8133
|
-
.
|
|
8421
|
+
.page-head .actions {
|
|
8134
8422
|
display: flex;
|
|
8423
|
+
gap: 8px;
|
|
8135
8424
|
align-items: center;
|
|
8136
|
-
|
|
8137
|
-
margin-bottom: 20px;
|
|
8425
|
+
flex-wrap: wrap;
|
|
8138
8426
|
}
|
|
8139
|
-
.
|
|
8140
|
-
|
|
8141
|
-
font-weight: 700;
|
|
8142
|
-
color: var(--text-primary);
|
|
8427
|
+
.page-section {
|
|
8428
|
+
padding: 0 22px 22px;
|
|
8143
8429
|
}
|
|
8144
|
-
|
|
8145
|
-
|
|
8430
|
+
/* ── First-time empty state with CTA (use when there's truly no data yet) ── */
|
|
8431
|
+
.empty-cta {
|
|
8432
|
+
text-align: center;
|
|
8433
|
+
padding: 48px 24px;
|
|
8146
8434
|
color: var(--text-muted);
|
|
8147
|
-
flex: 1;
|
|
8148
8435
|
}
|
|
8149
|
-
.
|
|
8150
|
-
|
|
8151
|
-
|
|
8152
|
-
|
|
8153
|
-
font-size: 18px;
|
|
8436
|
+
.empty-cta .icon {
|
|
8437
|
+
font-size: 36px;
|
|
8438
|
+
margin-bottom: 12px;
|
|
8439
|
+
opacity: 0.5;
|
|
8154
8440
|
}
|
|
8155
|
-
.
|
|
8156
|
-
|
|
8157
|
-
|
|
8158
|
-
|
|
8441
|
+
.empty-cta .label {
|
|
8442
|
+
font-size: 14px;
|
|
8443
|
+
margin-bottom: 4px;
|
|
8444
|
+
color: var(--text-secondary);
|
|
8445
|
+
font-weight: 500;
|
|
8159
8446
|
}
|
|
8160
|
-
.
|
|
8161
|
-
|
|
8162
|
-
|
|
8163
|
-
|
|
8164
|
-
padding: 20px 16px;
|
|
8165
|
-
text-align: center;
|
|
8166
|
-
position: relative;
|
|
8167
|
-
display: flex;
|
|
8168
|
-
flex-direction: column;
|
|
8169
|
-
align-items: center;
|
|
8170
|
-
gap: 8px;
|
|
8171
|
-
transition: transform 0.15s, box-shadow 0.15s;
|
|
8447
|
+
.empty-cta .hint {
|
|
8448
|
+
font-size: 12px;
|
|
8449
|
+
color: var(--text-muted);
|
|
8450
|
+
margin-bottom: 18px;
|
|
8172
8451
|
}
|
|
8173
|
-
|
|
8174
|
-
|
|
8175
|
-
|
|
8452
|
+
/* ── Skeleton shimmer for loading lists ── */
|
|
8453
|
+
.skel-block {
|
|
8454
|
+
padding: 12px 18px;
|
|
8176
8455
|
}
|
|
8177
|
-
.
|
|
8178
|
-
|
|
8179
|
-
border-
|
|
8456
|
+
.skel-row {
|
|
8457
|
+
height: 14px;
|
|
8458
|
+
border-radius: 4px;
|
|
8459
|
+
background: linear-gradient(90deg, var(--bg-tertiary) 0%, var(--bg-hover) 50%, var(--bg-tertiary) 100%);
|
|
8460
|
+
background-size: 200% 100%;
|
|
8461
|
+
animation: skelShimmer 1.4s ease-in-out infinite;
|
|
8462
|
+
margin-bottom: 10px;
|
|
8180
8463
|
}
|
|
8181
|
-
.
|
|
8182
|
-
|
|
8183
|
-
|
|
8184
|
-
|
|
8185
|
-
|
|
8186
|
-
color: var(--green);
|
|
8187
|
-
font-size: 16px;
|
|
8188
|
-
font-weight: 700;
|
|
8464
|
+
.skel-row.short { width: 40%; }
|
|
8465
|
+
.skel-row.med { width: 65%; }
|
|
8466
|
+
@keyframes skelShimmer {
|
|
8467
|
+
0% { background-position: 200% 0; }
|
|
8468
|
+
100% { background-position: -200% 0; }
|
|
8189
8469
|
}
|
|
8190
|
-
|
|
8191
|
-
|
|
8192
|
-
|
|
8193
|
-
|
|
8194
|
-
background: var(--clementine);
|
|
8195
|
-
color: #fff;
|
|
8196
|
-
font-size: 12px;
|
|
8197
|
-
font-weight: 700;
|
|
8198
|
-
display: flex;
|
|
8470
|
+
/* ── Row actions (icon-style buttons that appear inline on a list row) ── */
|
|
8471
|
+
.row-actions {
|
|
8472
|
+
display: inline-flex;
|
|
8473
|
+
gap: 6px;
|
|
8199
8474
|
align-items: center;
|
|
8200
|
-
justify-content: center;
|
|
8201
|
-
flex-shrink: 0;
|
|
8202
8475
|
}
|
|
8203
|
-
.
|
|
8204
|
-
|
|
8205
|
-
|
|
8476
|
+
.row-actions button {
|
|
8477
|
+
background: none;
|
|
8478
|
+
border: 1px solid transparent;
|
|
8479
|
+
color: var(--text-muted);
|
|
8480
|
+
font-size: 11px;
|
|
8481
|
+
padding: 3px 8px;
|
|
8482
|
+
border-radius: 4px;
|
|
8483
|
+
cursor: pointer;
|
|
8484
|
+
transition: background 0.15s, border-color 0.15s, color 0.15s;
|
|
8206
8485
|
}
|
|
8207
|
-
.
|
|
8208
|
-
|
|
8209
|
-
|
|
8486
|
+
.row-actions button:hover {
|
|
8487
|
+
background: var(--bg-hover);
|
|
8488
|
+
border-color: var(--border);
|
|
8210
8489
|
color: var(--text-primary);
|
|
8211
8490
|
}
|
|
8212
|
-
.
|
|
8213
|
-
|
|
8214
|
-
|
|
8215
|
-
|
|
8216
|
-
|
|
8217
|
-
}
|
|
8218
|
-
@media (max-width: 1024px) {
|
|
8219
|
-
.gs-grid { grid-template-columns: repeat(3, 1fr); }
|
|
8491
|
+
.row-actions button.primary { color: var(--clementine); }
|
|
8492
|
+
.row-actions button.danger:hover { color: var(--red); border-color: rgba(239,68,68,0.3); }
|
|
8493
|
+
.clickable-row {
|
|
8494
|
+
cursor: pointer;
|
|
8495
|
+
transition: background 0.12s;
|
|
8220
8496
|
}
|
|
8221
|
-
|
|
8222
|
-
|
|
8497
|
+
.clickable-row:hover { background: var(--bg-hover); }
|
|
8498
|
+
|
|
8499
|
+
/* ── Status pip (used for daemon status, channel status, agent status) ── */
|
|
8500
|
+
.status-pip {
|
|
8501
|
+
display: inline-block;
|
|
8502
|
+
width: 8px;
|
|
8503
|
+
height: 8px;
|
|
8504
|
+
border-radius: 50%;
|
|
8505
|
+
background: #888;
|
|
8506
|
+
margin-right: 6px;
|
|
8507
|
+
flex-shrink: 0;
|
|
8508
|
+
}
|
|
8509
|
+
.status-pip.green { background: #22c55e; box-shadow: 0 0 6px rgba(34,197,94,0.5); }
|
|
8510
|
+
.status-pip.amber { background: #f59e0b; }
|
|
8511
|
+
.status-pip.red { background: #ef4444; box-shadow: 0 0 6px rgba(239,68,68,0.4); }
|
|
8512
|
+
.status-pip.muted { background: var(--text-muted); }
|
|
8513
|
+
|
|
8514
|
+
/* Cmd+K palette */
|
|
8515
|
+
.cmdk-row:hover { background: var(--bg-hover) !important; }
|
|
8516
|
+
|
|
8517
|
+
/* ── Home layout — chat-first daily driver ─── */
|
|
8518
|
+
.home-layout {
|
|
8519
|
+
display: grid;
|
|
8520
|
+
grid-template-columns: 1fr 320px;
|
|
8521
|
+
gap: 18px;
|
|
8522
|
+
height: calc(100vh - var(--header-h));
|
|
8523
|
+
padding: 18px;
|
|
8524
|
+
box-sizing: border-box;
|
|
8525
|
+
}
|
|
8526
|
+
.home-main {
|
|
8527
|
+
display: flex;
|
|
8528
|
+
flex-direction: column;
|
|
8529
|
+
gap: 14px;
|
|
8530
|
+
min-height: 0;
|
|
8531
|
+
overflow-y: auto;
|
|
8532
|
+
}
|
|
8533
|
+
.home-chat {
|
|
8534
|
+
display: flex;
|
|
8535
|
+
flex-direction: column;
|
|
8536
|
+
flex: 1;
|
|
8537
|
+
min-height: 320px;
|
|
8538
|
+
background: var(--bg-card);
|
|
8539
|
+
border: 1px solid var(--border);
|
|
8540
|
+
border-radius: 12px;
|
|
8541
|
+
overflow: hidden;
|
|
8542
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
|
|
8543
|
+
}
|
|
8544
|
+
.home-chat-messages {
|
|
8545
|
+
flex: 1;
|
|
8546
|
+
overflow-y: auto;
|
|
8547
|
+
padding: 18px 20px;
|
|
8548
|
+
min-height: 280px;
|
|
8549
|
+
}
|
|
8550
|
+
.home-chat-input-row {
|
|
8551
|
+
display: flex;
|
|
8552
|
+
gap: 8px;
|
|
8553
|
+
padding: 12px 16px;
|
|
8554
|
+
border-top: 1px solid var(--border);
|
|
8555
|
+
background: var(--bg-secondary);
|
|
8556
|
+
align-items: center;
|
|
8557
|
+
}
|
|
8558
|
+
.home-chat-input-row input[type="text"] {
|
|
8559
|
+
flex: 1;
|
|
8560
|
+
padding: 10px 14px;
|
|
8561
|
+
border: 1px solid var(--border);
|
|
8562
|
+
border-radius: 8px;
|
|
8563
|
+
background: var(--bg-input);
|
|
8564
|
+
color: var(--text-primary);
|
|
8565
|
+
font-size: 14px;
|
|
8566
|
+
}
|
|
8567
|
+
.home-chat-input-row select {
|
|
8568
|
+
padding: 6px 10px;
|
|
8569
|
+
border: 1px solid var(--border);
|
|
8570
|
+
border-radius: 6px;
|
|
8571
|
+
background: var(--bg-secondary);
|
|
8572
|
+
color: var(--text-primary);
|
|
8573
|
+
font-size: 12px;
|
|
8574
|
+
}
|
|
8575
|
+
.home-chat-input-row button { padding: 9px 18px; border-radius: 8px; }
|
|
8576
|
+
.home-activity { margin: 0; }
|
|
8577
|
+
|
|
8578
|
+
/* Right rail */
|
|
8579
|
+
.home-rail {
|
|
8580
|
+
display: flex;
|
|
8581
|
+
flex-direction: column;
|
|
8582
|
+
gap: 12px;
|
|
8583
|
+
overflow-y: auto;
|
|
8584
|
+
position: relative;
|
|
8585
|
+
}
|
|
8586
|
+
.home-rail.collapsed {
|
|
8587
|
+
display: none;
|
|
8588
|
+
}
|
|
8589
|
+
.rail-collapse-btn {
|
|
8590
|
+
position: absolute;
|
|
8591
|
+
top: -4px;
|
|
8592
|
+
right: -4px;
|
|
8593
|
+
background: none;
|
|
8594
|
+
border: 1px solid var(--border);
|
|
8595
|
+
color: var(--text-muted);
|
|
8596
|
+
width: 22px;
|
|
8597
|
+
height: 22px;
|
|
8598
|
+
border-radius: 50%;
|
|
8599
|
+
cursor: pointer;
|
|
8600
|
+
font-size: 14px;
|
|
8601
|
+
display: none;
|
|
8602
|
+
align-items: center;
|
|
8603
|
+
justify-content: center;
|
|
8604
|
+
z-index: 5;
|
|
8605
|
+
}
|
|
8606
|
+
.rail-card {
|
|
8607
|
+
background: var(--bg-card);
|
|
8608
|
+
border: 1px solid var(--border);
|
|
8609
|
+
border-radius: 10px;
|
|
8610
|
+
overflow: hidden;
|
|
8611
|
+
box-shadow: 0 1px 4px rgba(0,0,0,0.04);
|
|
8612
|
+
}
|
|
8613
|
+
.rail-header {
|
|
8614
|
+
display: flex;
|
|
8615
|
+
align-items: center;
|
|
8616
|
+
justify-content: space-between;
|
|
8617
|
+
padding: 10px 14px;
|
|
8618
|
+
font-size: 12px;
|
|
8619
|
+
font-weight: 600;
|
|
8620
|
+
color: var(--text-secondary);
|
|
8621
|
+
border-bottom: 1px solid var(--border);
|
|
8622
|
+
background: var(--bg-secondary);
|
|
8623
|
+
}
|
|
8624
|
+
.rail-body { padding: 12px 14px; font-size: 12.5px; line-height: 1.5; }
|
|
8625
|
+
.rail-body .empty-state, .rail-body .skel-row { font-size: 11px; }
|
|
8626
|
+
.rail-badge {
|
|
8627
|
+
display: inline-flex;
|
|
8628
|
+
align-items: center;
|
|
8629
|
+
justify-content: center;
|
|
8630
|
+
min-width: 18px;
|
|
8631
|
+
height: 18px;
|
|
8632
|
+
padding: 0 6px;
|
|
8633
|
+
border-radius: 9px;
|
|
8634
|
+
background: var(--clementine);
|
|
8635
|
+
color: #fff;
|
|
8636
|
+
font-size: 10px;
|
|
8637
|
+
font-weight: 600;
|
|
8638
|
+
}
|
|
8639
|
+
.rail-row {
|
|
8640
|
+
display: flex;
|
|
8641
|
+
align-items: center;
|
|
8642
|
+
gap: 8px;
|
|
8643
|
+
padding: 6px 0;
|
|
8644
|
+
font-size: 12px;
|
|
8645
|
+
border-bottom: 1px dashed var(--border);
|
|
8646
|
+
}
|
|
8647
|
+
.rail-row:last-child { border-bottom: none; }
|
|
8648
|
+
.rail-row .label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
8649
|
+
.rail-row .meta { font-size: 10px; color: var(--text-muted); flex-shrink: 0; }
|
|
8650
|
+
|
|
8651
|
+
/* Floating "open rail" button when collapsed */
|
|
8652
|
+
.home-rail-toggle {
|
|
8653
|
+
position: fixed;
|
|
8654
|
+
top: 80px;
|
|
8655
|
+
right: 18px;
|
|
8656
|
+
z-index: 100;
|
|
8657
|
+
width: 36px;
|
|
8658
|
+
height: 36px;
|
|
8659
|
+
border-radius: 50%;
|
|
8660
|
+
background: var(--clementine);
|
|
8661
|
+
color: #fff;
|
|
8662
|
+
border: none;
|
|
8663
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
|
8664
|
+
cursor: pointer;
|
|
8665
|
+
font-size: 14px;
|
|
8666
|
+
display: none;
|
|
8667
|
+
}
|
|
8668
|
+
.home-rail.collapsed ~ .home-rail-toggle,
|
|
8669
|
+
.home-rail.collapsed + .home-rail-toggle { display: block; }
|
|
8670
|
+
|
|
8671
|
+
/* Narrow screens: rail becomes a slide-out drawer */
|
|
8672
|
+
@media (max-width: 1024px) {
|
|
8673
|
+
.home-layout { grid-template-columns: 1fr; }
|
|
8674
|
+
.home-rail {
|
|
8675
|
+
position: fixed;
|
|
8676
|
+
right: 0;
|
|
8677
|
+
top: var(--header-h);
|
|
8678
|
+
bottom: 0;
|
|
8679
|
+
width: 320px;
|
|
8680
|
+
max-width: 90vw;
|
|
8681
|
+
transform: translateX(100%);
|
|
8682
|
+
transition: transform 0.2s ease;
|
|
8683
|
+
background: var(--bg);
|
|
8684
|
+
border-left: 1px solid var(--border);
|
|
8685
|
+
box-shadow: -4px 0 20px rgba(0,0,0,0.15);
|
|
8686
|
+
padding: 14px;
|
|
8687
|
+
z-index: 50;
|
|
8688
|
+
}
|
|
8689
|
+
.home-rail.open { transform: translateX(0); }
|
|
8690
|
+
.rail-collapse-btn { display: flex; }
|
|
8691
|
+
.home-rail-toggle { display: block; }
|
|
8692
|
+
.home-rail.open ~ .home-rail-toggle { display: none; }
|
|
8693
|
+
}
|
|
8694
|
+
|
|
8695
|
+
/* ── Cards ──────────────────────────────── */
|
|
8696
|
+
.card {
|
|
8697
|
+
background: var(--bg-card);
|
|
8698
|
+
backdrop-filter: blur(8px);
|
|
8699
|
+
border: 1px solid var(--border);
|
|
8700
|
+
border-radius: var(--radius);
|
|
8701
|
+
margin-bottom: 16px;
|
|
8702
|
+
overflow: hidden;
|
|
8703
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
|
|
8704
|
+
}
|
|
8705
|
+
.card-header {
|
|
8706
|
+
padding: 14px 18px;
|
|
8707
|
+
border-bottom: 1px solid var(--border);
|
|
8708
|
+
display: flex;
|
|
8709
|
+
align-items: center;
|
|
8710
|
+
justify-content: space-between;
|
|
8711
|
+
font-size: 13px;
|
|
8712
|
+
font-weight: 600;
|
|
8713
|
+
color: var(--text-primary);
|
|
8714
|
+
}
|
|
8715
|
+
.card-body {
|
|
8716
|
+
padding: 18px;
|
|
8717
|
+
font-size: 13px;
|
|
8718
|
+
line-height: 1.7;
|
|
8719
|
+
}
|
|
8720
|
+
.grid-2 {
|
|
8721
|
+
display: grid;
|
|
8722
|
+
grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
|
|
8723
|
+
gap: 20px;
|
|
8724
|
+
}
|
|
8725
|
+
|
|
8726
|
+
/* ── Getting Started ────────────────────── */
|
|
8727
|
+
.getting-started {
|
|
8728
|
+
background: linear-gradient(135deg, var(--clementine-bg), rgba(77,158,255,0.06));
|
|
8729
|
+
border: 1px solid var(--border);
|
|
8730
|
+
border-radius: 16px;
|
|
8731
|
+
padding: 24px;
|
|
8732
|
+
margin-bottom: 24px;
|
|
8733
|
+
position: relative;
|
|
8734
|
+
}
|
|
8735
|
+
.gs-header {
|
|
8736
|
+
display: flex;
|
|
8737
|
+
align-items: center;
|
|
8738
|
+
gap: 12px;
|
|
8739
|
+
margin-bottom: 20px;
|
|
8740
|
+
}
|
|
8741
|
+
.gs-title {
|
|
8742
|
+
font-size: 18px;
|
|
8743
|
+
font-weight: 700;
|
|
8744
|
+
color: var(--text-primary);
|
|
8745
|
+
}
|
|
8746
|
+
.gs-subtitle {
|
|
8747
|
+
font-size: 13px;
|
|
8748
|
+
color: var(--text-muted);
|
|
8749
|
+
flex: 1;
|
|
8750
|
+
}
|
|
8751
|
+
.gs-dismiss {
|
|
8752
|
+
position: absolute;
|
|
8753
|
+
top: 12px;
|
|
8754
|
+
right: 12px;
|
|
8755
|
+
font-size: 18px;
|
|
8756
|
+
}
|
|
8757
|
+
.gs-grid {
|
|
8758
|
+
display: grid;
|
|
8759
|
+
grid-template-columns: repeat(5, 1fr);
|
|
8760
|
+
gap: 16px;
|
|
8761
|
+
}
|
|
8762
|
+
.gs-card {
|
|
8763
|
+
background: var(--bg-card);
|
|
8764
|
+
border: 1px solid var(--border);
|
|
8765
|
+
border-radius: var(--radius);
|
|
8766
|
+
padding: 20px 16px;
|
|
8767
|
+
text-align: center;
|
|
8768
|
+
position: relative;
|
|
8769
|
+
display: flex;
|
|
8770
|
+
flex-direction: column;
|
|
8771
|
+
align-items: center;
|
|
8772
|
+
gap: 8px;
|
|
8773
|
+
transition: transform 0.15s, box-shadow 0.15s;
|
|
8774
|
+
}
|
|
8775
|
+
.gs-card:hover {
|
|
8776
|
+
transform: translateY(-2px);
|
|
8777
|
+
box-shadow: var(--shadow-sm);
|
|
8778
|
+
}
|
|
8779
|
+
.gs-card.gs-done {
|
|
8780
|
+
opacity: 0.5;
|
|
8781
|
+
border-color: var(--green);
|
|
8782
|
+
}
|
|
8783
|
+
.gs-card.gs-done::after {
|
|
8784
|
+
content: '\\2713';
|
|
8785
|
+
position: absolute;
|
|
8786
|
+
top: 8px;
|
|
8787
|
+
right: 10px;
|
|
8788
|
+
color: var(--green);
|
|
8789
|
+
font-size: 16px;
|
|
8790
|
+
font-weight: 700;
|
|
8791
|
+
}
|
|
8792
|
+
.gs-step-num {
|
|
8793
|
+
width: 24px;
|
|
8794
|
+
height: 24px;
|
|
8795
|
+
border-radius: 50%;
|
|
8796
|
+
background: var(--clementine);
|
|
8797
|
+
color: #fff;
|
|
8798
|
+
font-size: 12px;
|
|
8799
|
+
font-weight: 700;
|
|
8800
|
+
display: flex;
|
|
8801
|
+
align-items: center;
|
|
8802
|
+
justify-content: center;
|
|
8803
|
+
flex-shrink: 0;
|
|
8804
|
+
}
|
|
8805
|
+
.gs-card-icon {
|
|
8806
|
+
font-size: 28px;
|
|
8807
|
+
line-height: 1;
|
|
8808
|
+
}
|
|
8809
|
+
.gs-card-title {
|
|
8810
|
+
font-size: 14px;
|
|
8811
|
+
font-weight: 600;
|
|
8812
|
+
color: var(--text-primary);
|
|
8813
|
+
}
|
|
8814
|
+
.gs-card-desc {
|
|
8815
|
+
font-size: 12px;
|
|
8816
|
+
color: var(--text-muted);
|
|
8817
|
+
line-height: 1.4;
|
|
8818
|
+
margin-bottom: 4px;
|
|
8819
|
+
}
|
|
8820
|
+
@media (max-width: 1024px) {
|
|
8821
|
+
.gs-grid { grid-template-columns: repeat(3, 1fr); }
|
|
8822
|
+
}
|
|
8823
|
+
@media (max-width: 768px) {
|
|
8824
|
+
.gs-grid { grid-template-columns: 1fr 1fr; }
|
|
8223
8825
|
}
|
|
8224
8826
|
@media (max-width: 400px) {
|
|
8225
8827
|
.gs-grid { grid-template-columns: 1fr; }
|
|
@@ -10009,11 +10611,92 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
10009
10611
|
flex-shrink: 0;
|
|
10010
10612
|
margin-top: 2px;
|
|
10011
10613
|
}
|
|
10012
|
-
|
|
10013
|
-
|
|
10014
|
-
|
|
10015
|
-
|
|
10614
|
+
/* Build page is full-height flex (canvas + chat). Home page handles its own layout. */
|
|
10615
|
+
#page-build.active {
|
|
10616
|
+
display: flex !important;
|
|
10617
|
+
flex-direction: column;
|
|
10618
|
+
height: calc(100vh - var(--header-h));
|
|
10619
|
+
}
|
|
10620
|
+
/* === Builder canvas (Drawflow node kinds) === */
|
|
10621
|
+
#builder-canvas .drawflow-node {
|
|
10622
|
+
background: #2a3040;
|
|
10623
|
+
border: 1px solid #3a4255;
|
|
10624
|
+
border-radius: 8px;
|
|
10625
|
+
color: #fff;
|
|
10626
|
+
padding: 10px 12px;
|
|
10627
|
+
min-width: 180px;
|
|
10628
|
+
max-width: 240px;
|
|
10629
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.25);
|
|
10630
|
+
}
|
|
10631
|
+
#builder-canvas .drawflow-node.selected { border-color: var(--clementine); }
|
|
10632
|
+
#builder-canvas .drawflow-node.cl-node-prompt { background: #2a3550; border-color: #3a4570; }
|
|
10633
|
+
#builder-canvas .drawflow-node.cl-node-mcp { background: #2c4039; border-color: #3a5a4f; }
|
|
10634
|
+
#builder-canvas .drawflow-node.cl-node-channel { background: #44324a; border-color: #5b3f6a; }
|
|
10635
|
+
#builder-canvas .drawflow-node.cl-node-transform { background: #3d3a28; border-color: #564f33; }
|
|
10636
|
+
#builder-canvas .drawflow-node.cl-node-conditional { background: #4a352a; border-color: #6b4737; }
|
|
10637
|
+
#builder-canvas .drawflow-node.cl-node-loop { background: #2e4350; border-color: #3f5a6c; }
|
|
10638
|
+
#builder-canvas .drawflow-node .input, #builder-canvas .drawflow-node .output {
|
|
10639
|
+
background: #555c70;
|
|
10640
|
+
border: 2px solid #2a3040;
|
|
10641
|
+
}
|
|
10642
|
+
#builder-canvas .drawflow .connection .main-path {
|
|
10643
|
+
stroke: #5b6580;
|
|
10644
|
+
stroke-width: 2px;
|
|
10645
|
+
}
|
|
10646
|
+
#builder-canvas .drawflow .connection .main-path:hover { stroke: var(--clementine); }
|
|
10647
|
+
/* Per-step run status (Phase 2e) */
|
|
10648
|
+
#builder-canvas .drawflow-node.cl-step-running {
|
|
10649
|
+
box-shadow: 0 0 0 2px var(--clementine), 0 2px 8px rgba(0,0,0,0.3);
|
|
10650
|
+
animation: cl-step-pulse 1.6s ease-in-out infinite;
|
|
10651
|
+
}
|
|
10652
|
+
#builder-canvas .drawflow-node.cl-step-done {
|
|
10653
|
+
box-shadow: 0 0 0 2px #4caf50, 0 2px 8px rgba(0,0,0,0.3);
|
|
10654
|
+
}
|
|
10655
|
+
#builder-canvas .drawflow-node.cl-step-failed,
|
|
10656
|
+
#builder-canvas .drawflow-node.cl-step-timeout {
|
|
10657
|
+
box-shadow: 0 0 0 2px #e64a4a, 0 2px 8px rgba(0,0,0,0.3);
|
|
10658
|
+
}
|
|
10659
|
+
#builder-canvas .drawflow-node.cl-step-skipped,
|
|
10660
|
+
#builder-canvas .drawflow-node.cl-step-cancelled {
|
|
10661
|
+
opacity: 0.55;
|
|
10662
|
+
box-shadow: 0 0 0 1px #888, 0 2px 8px rgba(0,0,0,0.2);
|
|
10663
|
+
}
|
|
10664
|
+
@keyframes cl-step-pulse {
|
|
10665
|
+
0%, 100% { box-shadow: 0 0 0 2px var(--clementine), 0 2px 8px rgba(0,0,0,0.3); }
|
|
10666
|
+
50% { box-shadow: 0 0 0 4px rgba(255,140,33,0.5), 0 2px 8px rgba(0,0,0,0.3); }
|
|
10667
|
+
}
|
|
10668
|
+
/* Node palette popover */
|
|
10669
|
+
.builder-palette-item {
|
|
10670
|
+
padding: 7px 12px;
|
|
10671
|
+
border-radius: 5px;
|
|
10672
|
+
font-size: 12px;
|
|
10673
|
+
color: var(--text-primary);
|
|
10674
|
+
cursor: pointer;
|
|
10675
|
+
}
|
|
10676
|
+
.builder-palette-item:hover { background: var(--bg-hover); }
|
|
10677
|
+
/* Config panel rows */
|
|
10678
|
+
#builder-config-panel .cfg-row { margin-bottom: 10px; }
|
|
10679
|
+
#builder-config-panel .cfg-row label {
|
|
10680
|
+
display: block;
|
|
10681
|
+
font-size: 10px;
|
|
10682
|
+
text-transform: uppercase;
|
|
10683
|
+
letter-spacing: 0.04em;
|
|
10684
|
+
color: var(--text-muted);
|
|
10685
|
+
margin-bottom: 4px;
|
|
10686
|
+
}
|
|
10687
|
+
#builder-config-panel .cfg-row input,
|
|
10688
|
+
#builder-config-panel .cfg-row select,
|
|
10689
|
+
#builder-config-panel .cfg-row textarea {
|
|
10690
|
+
width: 100%;
|
|
10691
|
+
padding: 6px 8px;
|
|
10692
|
+
border: 1px solid var(--border);
|
|
10693
|
+
border-radius: 5px;
|
|
10694
|
+
background: var(--bg-input);
|
|
10695
|
+
color: var(--text-primary);
|
|
10696
|
+
font-size: 12px;
|
|
10697
|
+
font-family: inherit;
|
|
10016
10698
|
}
|
|
10699
|
+
#builder-config-panel .cfg-row textarea { font-family: monospace; resize: vertical; }
|
|
10017
10700
|
#builder-preview .preview-field {
|
|
10018
10701
|
margin-bottom:12px;
|
|
10019
10702
|
}
|
|
@@ -10178,10 +10861,7 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
10178
10861
|
.stat-value { font-size: 20px; }
|
|
10179
10862
|
.stat-label { font-size: 10px; }
|
|
10180
10863
|
|
|
10181
|
-
/*
|
|
10182
|
-
#page-chat.active {
|
|
10183
|
-
height: calc(100vh - var(--header-h));
|
|
10184
|
-
}
|
|
10864
|
+
/* Mobile-friendly chat input (lives inside Home now). */
|
|
10185
10865
|
#chat-input {
|
|
10186
10866
|
font-size: 16px; /* prevents iOS zoom on focus */
|
|
10187
10867
|
min-height: 40px;
|
|
@@ -10453,98 +11133,39 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
10453
11133
|
</div>
|
|
10454
11134
|
</header>
|
|
10455
11135
|
|
|
10456
|
-
<!-- Sidebar -->
|
|
11136
|
+
<!-- Sidebar — 5 destinations only. Sub-pages live as tabs within each. -->
|
|
10457
11137
|
<nav class="sidebar">
|
|
10458
11138
|
<div class="nav-section">
|
|
10459
|
-
<div class="nav-
|
|
10460
|
-
|
|
10461
|
-
<span class="nav-icon">●</span> Home
|
|
10462
|
-
</div>
|
|
10463
|
-
<div class="nav-item" data-page="chat">
|
|
10464
|
-
<span class="nav-icon">💬</span> Chat
|
|
10465
|
-
</div>
|
|
10466
|
-
<div class="nav-item" data-page="daily-plan">
|
|
10467
|
-
<span class="nav-icon">📅</span> Daily Plan
|
|
10468
|
-
</div>
|
|
10469
|
-
<div class="nav-item" data-page="goals">
|
|
10470
|
-
<span class="nav-icon">🎯</span> Goals
|
|
10471
|
-
</div>
|
|
10472
|
-
</div>
|
|
10473
|
-
<div class="nav-section">
|
|
10474
|
-
<div class="nav-section-title">Brain</div>
|
|
10475
|
-
<div class="nav-item" data-page="intelligence">
|
|
10476
|
-
<span class="nav-icon">🧠</span> Brain
|
|
11139
|
+
<div class="nav-item active" data-page="home" title="Chat, today, activity">
|
|
11140
|
+
<span class="nav-icon">🍋</span> Home
|
|
10477
11141
|
</div>
|
|
10478
|
-
|
|
10479
|
-
|
|
10480
|
-
<div class="nav-section-title">Automate</div>
|
|
10481
|
-
<div class="nav-item" data-page="automations">
|
|
10482
|
-
<span class="nav-icon">⏰</span> Scheduled Tasks
|
|
11142
|
+
<div class="nav-item" data-page="build" title="Workflows, crons, skills">
|
|
11143
|
+
<span class="nav-icon">🛠</span> Build
|
|
10483
11144
|
<span class="nav-badge" id="nav-cron-count">0</span>
|
|
10484
11145
|
</div>
|
|
10485
|
-
<div class="nav-item"
|
|
10486
|
-
<span class="nav-icon">&#
|
|
10487
|
-
<span class="nav-badge" id="nav-skill-count" style="display:none">0</span>
|
|
10488
|
-
</div>
|
|
10489
|
-
<div class="nav-item" onclick="openAutomationsTab('timers')">
|
|
10490
|
-
<span class="nav-icon">⏱</span> Timers
|
|
11146
|
+
<div class="nav-item" data-page="team" title="Agents, activity, goals">
|
|
11147
|
+
<span class="nav-icon">👥</span> Team
|
|
10491
11148
|
</div>
|
|
10492
|
-
<div class="nav-item" data-page="
|
|
10493
|
-
<span class="nav-icon">&#
|
|
11149
|
+
<div class="nav-item" data-page="brain" title="Memory, knowledge, ingestion, health">
|
|
11150
|
+
<span class="nav-icon">🧠</span> Brain
|
|
10494
11151
|
</div>
|
|
10495
|
-
<div class="nav-item" data-page="
|
|
10496
|
-
<span class="nav-icon">&#
|
|
10497
|
-
<span class="nav-badge" id="nav-unleashed-count" style="display:none">0</span>
|
|
11152
|
+
<div class="nav-item" data-page="settings" title="Channels, integrations, system">
|
|
11153
|
+
<span class="nav-icon">⚙</span> Settings
|
|
10498
11154
|
</div>
|
|
10499
11155
|
</div>
|
|
10500
|
-
|
|
10501
|
-
|
|
10502
|
-
<div class="nav-
|
|
10503
|
-
<span class="nav-icon">👥</span> The Office
|
|
10504
|
-
</div>
|
|
10505
|
-
<div class="nav-item" data-page="team-status">
|
|
10506
|
-
<span class="nav-icon">📊</span> Team Status
|
|
10507
|
-
</div>
|
|
11156
|
+
<!-- Per-agent quick-jump (loaded from team-nav helper). -->
|
|
11157
|
+
<div class="nav-section" style="margin-top:18px">
|
|
11158
|
+
<div class="nav-section-title">Agents</div>
|
|
10508
11159
|
<div id="team-nav"></div>
|
|
10509
11160
|
<div class="team-hire-btn" onclick="showAgentCreateModal()">
|
|
10510
|
-
<span style="font-size:14px">+</span> Hire
|
|
10511
|
-
</div>
|
|
10512
|
-
</div>
|
|
10513
|
-
<div class="nav-section">
|
|
10514
|
-
<div class="nav-section-title">Build</div>
|
|
10515
|
-
<div class="nav-item" data-page="builder">
|
|
10516
|
-
<span class="nav-icon">🛠</span> Builder
|
|
10517
|
-
</div>
|
|
10518
|
-
<div class="nav-item" onclick="openSkillStudio()">
|
|
10519
|
-
<span class="nav-icon">💡</span> Skill Studio
|
|
10520
|
-
</div>
|
|
10521
|
-
<div class="nav-item" data-page="projects">
|
|
10522
|
-
<span class="nav-icon">📂</span> Projects
|
|
10523
|
-
</div>
|
|
10524
|
-
</div>
|
|
10525
|
-
<div class="nav-section">
|
|
10526
|
-
<div class="nav-section-title">Insights</div>
|
|
10527
|
-
<div class="nav-item" data-page="claims">
|
|
10528
|
-
<span class="nav-icon">🔒</span> Trust & Claims
|
|
10529
|
-
<span class="nav-badge" id="nav-trust-score" style="display:none">--</span>
|
|
10530
|
-
</div>
|
|
10531
|
-
<div class="nav-item" data-page="metrics">
|
|
10532
|
-
<span class="nav-icon">📈</span> Metrics
|
|
10533
|
-
</div>
|
|
10534
|
-
<div class="nav-item" data-page="memory-health">
|
|
10535
|
-
<span class="nav-icon">🧠</span> Memory Health
|
|
10536
|
-
</div>
|
|
10537
|
-
<div class="nav-item" data-page="logs">
|
|
10538
|
-
<span class="nav-icon">📜</span> Logs
|
|
10539
|
-
</div>
|
|
10540
|
-
<div class="nav-item" data-page="sessions">
|
|
10541
|
-
<span class="nav-icon">📝</span> Sessions
|
|
11161
|
+
<span style="font-size:14px">+</span> Hire
|
|
10542
11162
|
</div>
|
|
10543
11163
|
</div>
|
|
11164
|
+
<div style="flex:1"></div>
|
|
10544
11165
|
<div class="nav-section">
|
|
10545
|
-
<div class="nav-
|
|
10546
|
-
|
|
10547
|
-
<
|
|
11166
|
+
<div class="nav-item" onclick="openCommandK()" style="font-size:12px;color:var(--text-muted);justify-content:space-between" title="Quick search (Cmd+K)">
|
|
11167
|
+
<span><span class="nav-icon">🔍</span> Search</span>
|
|
11168
|
+
<kbd style="font-size:10px;padding:1px 5px;border:1px solid var(--border);border-radius:3px">⌘K</kbd>
|
|
10548
11169
|
</div>
|
|
10549
11170
|
</div>
|
|
10550
11171
|
</nav>
|
|
@@ -10553,133 +11174,171 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
10553
11174
|
<!-- Content -->
|
|
10554
11175
|
<div class="content">
|
|
10555
11176
|
|
|
10556
|
-
<!-- ═══ Home
|
|
11177
|
+
<!-- ═══ Home — chat-first daily driver ═══ -->
|
|
10557
11178
|
<div class="page active" id="page-home">
|
|
10558
|
-
<div class="
|
|
10559
|
-
<
|
|
10560
|
-
|
|
10561
|
-
<div class="
|
|
10562
|
-
<div class="
|
|
10563
|
-
|
|
10564
|
-
<
|
|
10565
|
-
<
|
|
11179
|
+
<div class="home-layout">
|
|
11180
|
+
<main class="home-main">
|
|
11181
|
+
<!-- Getting Started (shown for new users only) -->
|
|
11182
|
+
<div id="getting-started" class="getting-started" style="display:none">
|
|
11183
|
+
<div class="gs-header">
|
|
11184
|
+
<div class="gs-title">Get Started</div>
|
|
11185
|
+
<div class="gs-subtitle">Set up your AI assistant in 5 steps</div>
|
|
11186
|
+
<button class="btn-ghost btn-sm gs-dismiss" onclick="dismissGettingStarted()" title="Dismiss">×</button>
|
|
11187
|
+
</div>
|
|
11188
|
+
<div class="gs-grid">
|
|
11189
|
+
<div class="gs-card" id="gs-step-auth">
|
|
11190
|
+
<div class="gs-step-num">1</div>
|
|
11191
|
+
<div class="gs-card-icon">🔒</div>
|
|
11192
|
+
<div class="gs-card-title">Login with Anthropic</div>
|
|
11193
|
+
<div class="gs-card-desc" id="gs-auth-desc">Connect your Anthropic account to power your agents.</div>
|
|
11194
|
+
<button class="btn btn-sm btn-primary" id="gs-auth-btn" onclick="startAnthropicOAuth()">Login with Claude</button>
|
|
11195
|
+
</div>
|
|
11196
|
+
<div class="gs-card" id="gs-step-agent">
|
|
11197
|
+
<div class="gs-step-num">2</div>
|
|
11198
|
+
<div class="gs-card-icon">👥</div>
|
|
11199
|
+
<div class="gs-card-title">Create an Agent</div>
|
|
11200
|
+
<div class="gs-card-desc">Hire your first AI team member with a role, tools, and a channel.</div>
|
|
11201
|
+
<button class="btn btn-sm btn-primary" onclick="navigateTo('team')">Go to The Office</button>
|
|
11202
|
+
</div>
|
|
11203
|
+
<div class="gs-card" id="gs-step-channel">
|
|
11204
|
+
<div class="gs-step-num">3</div>
|
|
11205
|
+
<div class="gs-card-icon">💬</div>
|
|
11206
|
+
<div class="gs-card-title">Connect a Channel</div>
|
|
11207
|
+
<div class="gs-card-desc">Link Discord or Slack so your agents can communicate.</div>
|
|
11208
|
+
<button class="btn btn-sm" onclick="navigateTo('settings', { tab: 'channels' })">Open Settings</button>
|
|
11209
|
+
</div>
|
|
11210
|
+
<div class="gs-card" id="gs-step-task">
|
|
11211
|
+
<div class="gs-step-num">4</div>
|
|
11212
|
+
<div class="gs-card-icon">⏰</div>
|
|
11213
|
+
<div class="gs-card-title">Schedule a Task</div>
|
|
11214
|
+
<div class="gs-card-desc">Set up cron jobs so agents work on autopilot.</div>
|
|
11215
|
+
<button class="btn btn-sm" onclick="navigateTo('build', { tab: 'crons' })">Add a Task</button>
|
|
11216
|
+
</div>
|
|
11217
|
+
<div class="gs-card" id="gs-step-project">
|
|
11218
|
+
<div class="gs-step-num">5</div>
|
|
11219
|
+
<div class="gs-card-icon">📂</div>
|
|
11220
|
+
<div class="gs-card-title">Link a Project</div>
|
|
11221
|
+
<div class="gs-card-desc">Give agents context about your codebases and tools.</div>
|
|
11222
|
+
<button class="btn btn-sm" onclick="navigateTo('settings', { tab: 'projects' })">Browse Projects</button>
|
|
11223
|
+
</div>
|
|
10566
11224
|
</div>
|
|
10567
|
-
<div class="agent-meta" id="agent-meta"></div>
|
|
10568
|
-
<div class="agent-channels" id="agent-channels"></div>
|
|
10569
11225
|
</div>
|
|
10570
|
-
<div class="agent-controls" id="hero-controls"></div>
|
|
10571
|
-
</div>
|
|
10572
|
-
</div>
|
|
10573
11226
|
|
|
10574
|
-
|
|
10575
|
-
|
|
10576
|
-
|
|
10577
|
-
|
|
10578
|
-
|
|
10579
|
-
|
|
10580
|
-
|
|
10581
|
-
|
|
10582
|
-
|
|
10583
|
-
|
|
10584
|
-
|
|
10585
|
-
|
|
10586
|
-
|
|
10587
|
-
<
|
|
10588
|
-
|
|
10589
|
-
|
|
10590
|
-
|
|
10591
|
-
|
|
10592
|
-
|
|
10593
|
-
|
|
10594
|
-
<button class="btn btn-sm btn-primary" onclick="navigateTo('team')">Go to The Office</button>
|
|
10595
|
-
</div>
|
|
10596
|
-
<div class="gs-card" id="gs-step-channel">
|
|
10597
|
-
<div class="gs-step-num">3</div>
|
|
10598
|
-
<div class="gs-card-icon">💬</div>
|
|
10599
|
-
<div class="gs-card-title">Connect a Channel</div>
|
|
10600
|
-
<div class="gs-card-desc">Link Discord or Slack so your agents can communicate.</div>
|
|
10601
|
-
<button class="btn btn-sm" onclick="navigateTo('settings')">Open Settings</button>
|
|
10602
|
-
</div>
|
|
10603
|
-
<div class="gs-card" id="gs-step-task">
|
|
10604
|
-
<div class="gs-step-num">4</div>
|
|
10605
|
-
<div class="gs-card-icon">⏰</div>
|
|
10606
|
-
<div class="gs-card-title">Schedule a Task</div>
|
|
10607
|
-
<div class="gs-card-desc">Set up cron jobs so agents work on autopilot.</div>
|
|
10608
|
-
<button class="btn btn-sm" onclick="navigateTo('automations')">Add a Task</button>
|
|
11227
|
+
<!-- Chat panel — primary surface -->
|
|
11228
|
+
<div class="home-chat">
|
|
11229
|
+
<div id="chat-messages" class="home-chat-messages">
|
|
11230
|
+
<div class="empty-state" style="margin-top:32px">
|
|
11231
|
+
<p style="margin-bottom:14px;color:var(--text-muted)">What can I help with?</p>
|
|
11232
|
+
<div style="display:flex;flex-wrap:wrap;gap:8px;justify-content:center">
|
|
11233
|
+
<button class="btn btn-sm quick-pill" onclick="quickChat("What's on my schedule?")">What's on my schedule?</button>
|
|
11234
|
+
<button class="btn btn-sm quick-pill" onclick="quickChat('Check my email')">Check my email</button>
|
|
11235
|
+
<button class="btn btn-sm quick-pill" onclick="quickChat('Run morning briefing')">Run morning briefing</button>
|
|
11236
|
+
<button class="btn btn-sm quick-pill" onclick="quickChat('What did you do today?')">What did you do today?</button>
|
|
11237
|
+
</div>
|
|
11238
|
+
</div>
|
|
11239
|
+
</div>
|
|
11240
|
+
<div class="home-chat-input-row">
|
|
11241
|
+
<input type="text" id="chat-input" placeholder="Ask Clementine anything..." onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();sendChat()}">
|
|
11242
|
+
<select id="chat-profile-select" onchange="switchProfile(this.value)" title="Active profile">
|
|
11243
|
+
<option value="">Default</option>
|
|
11244
|
+
</select>
|
|
11245
|
+
<button class="btn-primary" id="chat-send-btn" onclick="sendChat()">Send</button>
|
|
11246
|
+
</div>
|
|
10609
11247
|
</div>
|
|
10610
|
-
|
|
10611
|
-
|
|
10612
|
-
|
|
10613
|
-
<div class="
|
|
10614
|
-
|
|
10615
|
-
|
|
11248
|
+
|
|
11249
|
+
<!-- Activity feed -->
|
|
11250
|
+
<div class="card home-activity">
|
|
11251
|
+
<div class="card-header" style="display:flex;justify-content:space-between;align-items:center">
|
|
11252
|
+
<span>Live activity</span>
|
|
11253
|
+
<div style="display:flex;gap:6px;align-items:center">
|
|
11254
|
+
<select id="activity-source-filter" onchange="refreshActivity()" style="font-size:11px;padding:2px 6px;border:1px solid var(--border);border-radius:4px;background:var(--bg-secondary);color:var(--text-primary)">
|
|
11255
|
+
<option value="">All Sources</option>
|
|
11256
|
+
<option value="cron">Cron</option>
|
|
11257
|
+
<option value="activity">Activities</option>
|
|
11258
|
+
<option value="send">Emails</option>
|
|
11259
|
+
<option value="approval">Approvals</option>
|
|
11260
|
+
<option value="memory">Memory</option>
|
|
11261
|
+
</select>
|
|
11262
|
+
<select id="activity-agent-filter" onchange="refreshActivity()" style="font-size:11px;padding:2px 6px;border:1px solid var(--border);border-radius:4px;background:var(--bg-secondary);color:var(--text-primary)">
|
|
11263
|
+
<option value="">All Agents</option>
|
|
11264
|
+
</select>
|
|
11265
|
+
</div>
|
|
11266
|
+
</div>
|
|
11267
|
+
<div class="card-body" id="panel-activity"><div class="skel-block"><div class="skel-row med"></div><div class="skel-row"></div><div class="skel-row short"></div></div></div>
|
|
10616
11268
|
</div>
|
|
10617
|
-
</
|
|
10618
|
-
</div>
|
|
11269
|
+
</main>
|
|
10619
11270
|
|
|
10620
|
-
|
|
11271
|
+
<!-- Right rail: Today / Upcoming / Active / Time-saved / Approvals -->
|
|
11272
|
+
<aside class="home-rail" id="home-rail">
|
|
11273
|
+
<button class="rail-collapse-btn" onclick="toggleHomeRail()" title="Hide rail">×</button>
|
|
10621
11274
|
|
|
10622
|
-
|
|
10623
|
-
|
|
10624
|
-
|
|
10625
|
-
|
|
10626
|
-
<span>Today's Plan</span>
|
|
10627
|
-
<div style="display:flex;gap:8px;align-items:center">
|
|
10628
|
-
<input type="date" id="plan-date-picker" style="padding:4px 8px;font-size:11px;background:var(--bg-input);border:1px solid var(--border);border-radius:4px;color:var(--text-primary)">
|
|
10629
|
-
<button class="btn btn-sm" onclick="loadPlanForDate(document.getElementById('plan-date-picker').value)" style="font-size:11px">Load</button>
|
|
11275
|
+
<section class="rail-card">
|
|
11276
|
+
<div class="rail-header">
|
|
11277
|
+
<span><span class="status-pip green"></span>Daemon</span>
|
|
11278
|
+
<span id="rail-daemon-uptime" style="font-size:11px;color:var(--text-muted)">--</span>
|
|
10630
11279
|
</div>
|
|
10631
|
-
|
|
10632
|
-
|
|
10633
|
-
|
|
10634
|
-
|
|
10635
|
-
<div class="card-header" style="display:flex;justify-content:space-between;align-items:center">
|
|
10636
|
-
<span>Team Pulse</span>
|
|
10637
|
-
<span style="font-size:11px;color:var(--text-muted)" id="team-pulse-count"></span>
|
|
10638
|
-
</div>
|
|
10639
|
-
<div class="card-body" id="home-team-pulse" style="max-height:320px;overflow-y:auto"><div class="empty-state">Loading team...</div></div>
|
|
10640
|
-
</div>
|
|
10641
|
-
</div>
|
|
11280
|
+
<div class="rail-body" id="rail-daemon-body">
|
|
11281
|
+
<div class="agent-activity" id="agent-activity"><span class="agent-activity-dot"></span><span>Loading...</span></div>
|
|
11282
|
+
</div>
|
|
11283
|
+
</section>
|
|
10642
11284
|
|
|
10643
|
-
|
|
10644
|
-
|
|
10645
|
-
|
|
10646
|
-
|
|
10647
|
-
|
|
10648
|
-
<
|
|
10649
|
-
|
|
10650
|
-
|
|
10651
|
-
|
|
10652
|
-
|
|
10653
|
-
|
|
10654
|
-
|
|
10655
|
-
|
|
10656
|
-
|
|
10657
|
-
|
|
10658
|
-
|
|
10659
|
-
</
|
|
10660
|
-
|
|
10661
|
-
|
|
11285
|
+
<section class="rail-card">
|
|
11286
|
+
<div class="rail-header">
|
|
11287
|
+
<span>Today</span>
|
|
11288
|
+
<input type="date" id="plan-date-picker" style="padding:2px 6px;font-size:10px;background:var(--bg-input);border:1px solid var(--border);border-radius:3px;color:var(--text-primary)">
|
|
11289
|
+
</div>
|
|
11290
|
+
<div class="rail-body" id="home-plan-content"><div class="skel-row med"></div><div class="skel-row"></div></div>
|
|
11291
|
+
</section>
|
|
11292
|
+
|
|
11293
|
+
<section class="rail-card">
|
|
11294
|
+
<div class="rail-header"><span>Upcoming runs</span><span id="rail-upcoming-count" class="rail-badge">0</span></div>
|
|
11295
|
+
<div class="rail-body" id="rail-upcoming"><div class="skel-row short"></div></div>
|
|
11296
|
+
</section>
|
|
11297
|
+
|
|
11298
|
+
<section class="rail-card">
|
|
11299
|
+
<div class="rail-header"><span>Active runs</span><span id="rail-active-count" class="rail-badge" style="display:none">0</span></div>
|
|
11300
|
+
<div class="rail-body" id="rail-active"><div style="font-size:12px;color:var(--text-muted)">Nothing running.</div></div>
|
|
11301
|
+
</section>
|
|
11302
|
+
|
|
11303
|
+
<section class="rail-card">
|
|
11304
|
+
<div class="rail-header"><span>Time saved this week</span></div>
|
|
11305
|
+
<div class="rail-body" id="rail-time-saved"><div class="skel-row short"></div></div>
|
|
11306
|
+
</section>
|
|
11307
|
+
|
|
11308
|
+
<section class="rail-card">
|
|
11309
|
+
<div class="rail-header"><span>Approvals</span><span id="rail-approvals-count" class="rail-badge" style="display:none">0</span></div>
|
|
11310
|
+
<div class="rail-body" id="rail-approvals"><div style="font-size:12px;color:var(--text-muted)">Nothing pending.</div></div>
|
|
11311
|
+
</section>
|
|
11312
|
+
</aside>
|
|
11313
|
+
<button class="home-rail-toggle" onclick="toggleHomeRail()" title="Open status rail">☰</button>
|
|
10662
11314
|
</div>
|
|
10663
11315
|
</div>
|
|
10664
11316
|
|
|
10665
11317
|
<!-- ═══ Builder Page — Conversational Artifact Creation ═══ -->
|
|
10666
|
-
<div class="page" id="page-
|
|
10667
|
-
<div style="
|
|
10668
|
-
<
|
|
10669
|
-
<
|
|
10670
|
-
|
|
10671
|
-
|
|
10672
|
-
|
|
10673
|
-
|
|
11318
|
+
<div class="page" id="page-build">
|
|
11319
|
+
<div class="tab-bar" id="build-tabs" style="margin:0;padding:0 18px;background:var(--bg-secondary);border-bottom:1px solid var(--border)">
|
|
11320
|
+
<button class="active" data-build-tab="workflows" onclick="switchBuildTab('workflows')">🔗 Workflows</button>
|
|
11321
|
+
<button data-build-tab="crons" onclick="switchBuildTab('crons')">⏰ Crons <span class="tab-badge" id="build-tab-cron-count" style="display:none">0</span></button>
|
|
11322
|
+
<button data-build-tab="skills" onclick="switchBuildTab('skills')">🛡 Skills <span class="tab-badge" id="build-tab-skill-count" style="display:none">0</span></button>
|
|
11323
|
+
<button data-build-tab="templates" onclick="switchBuildTab('templates')">📝 Templates</button>
|
|
11324
|
+
</div>
|
|
11325
|
+
<!-- Builder header strip — persists across tabs (except Templates) -->
|
|
11326
|
+
<div id="build-header-strip" style="display:flex;align-items:center;gap:12px;padding:10px 18px;border-bottom:1px solid var(--border)">
|
|
11327
|
+
<select id="builder-type" onchange="resetBuilder();updateBuilderMode()" style="display:none">
|
|
11328
|
+
<option value="skill">skill</option>
|
|
11329
|
+
<option value="cron">cron</option>
|
|
11330
|
+
<option value="agent">agent</option>
|
|
11331
|
+
<option value="workflow">workflow</option>
|
|
10674
11332
|
</select>
|
|
10675
|
-
<span id="builder-agent-label" style="padding:
|
|
11333
|
+
<span id="builder-agent-label" style="padding:0;font-size:13px;color:var(--text-secondary);font-weight:500"></span>
|
|
10676
11334
|
<input type="hidden" id="builder-agent" value="">
|
|
10677
11335
|
<span style="flex:1"></span>
|
|
10678
11336
|
<button class="btn-sm" onclick="resetBuilder()" style="background:var(--bg-tertiary);border:1px solid var(--border);color:var(--text-secondary);padding:4px 12px;border-radius:6px;cursor:pointer;font-size:12px">New</button>
|
|
10679
11337
|
<button class="btn-sm" id="builder-test-btn" onclick="testBuilderSkill()" style="background:var(--bg-tertiary);border:1px solid var(--border);color:var(--text-secondary);padding:4px 12px;border-radius:6px;cursor:pointer;font-size:12px;display:none">Test</button>
|
|
10680
11338
|
<button class="btn-sm btn-primary" id="builder-save-btn" onclick="saveBuilderArtifact()" style="padding:4px 16px;font-size:12px;display:none">Save</button>
|
|
10681
11339
|
</div>
|
|
10682
|
-
|
|
11340
|
+
<!-- Build tab content area -->
|
|
11341
|
+
<div id="build-tab-workflows" data-build-tabpane="workflows" style="display:flex;flex:1;min-height:0;overflow:hidden">
|
|
10683
11342
|
<!-- Left: Chat -->
|
|
10684
11343
|
<div style="flex:1;display:flex;flex-direction:column;border-right:1px solid var(--border)">
|
|
10685
11344
|
<div id="builder-messages" style="flex:1;overflow-y:auto;padding:16px">
|
|
@@ -10712,15 +11371,44 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
10712
11371
|
<button class="btn-primary" onclick="sendBuilderChat()" style="padding:10px 18px;border-radius:8px">Send</button>
|
|
10713
11372
|
</div>
|
|
10714
11373
|
</div>
|
|
10715
|
-
<!-- Right: Live Preview + Existing Skills -->
|
|
10716
|
-
<div style="width:
|
|
10717
|
-
<div style="padding:12px 16px;border-bottom:1px solid var(--border);font-weight:600;font-size:13px;color:var(--text-secondary)">
|
|
10718
|
-
Live Preview
|
|
10719
|
-
<span id="builder-preview-status" style="font-size:11px;color:var(--text-muted)
|
|
11374
|
+
<!-- Right: Live Preview / Canvas + Existing Skills -->
|
|
11375
|
+
<div id="builder-right-pane" style="width:520px;display:flex;flex-direction:column;background:var(--bg-secondary)">
|
|
11376
|
+
<div style="padding:12px 16px;border-bottom:1px solid var(--border);font-weight:600;font-size:13px;color:var(--text-secondary);display:flex;align-items:center;gap:10px;flex-wrap:wrap">
|
|
11377
|
+
<span id="builder-right-pane-title">Live Preview</span>
|
|
11378
|
+
<span id="builder-preview-status" style="font-size:11px;color:var(--text-muted)"></span>
|
|
11379
|
+
<span style="flex:1"></span>
|
|
11380
|
+
<select id="builder-canvas-picker" onchange="openBuilderWorkflow(this.value)" style="display:none;padding:4px 8px;border:1px solid var(--border);border-radius:6px;background:var(--bg-secondary);color:var(--text-primary);font-size:12px;max-width:240px">
|
|
11381
|
+
<option value="">— pick a workflow —</option>
|
|
11382
|
+
</select>
|
|
11383
|
+
<button id="builder-canvas-validate-btn" onclick="validateBuilderCanvas()" title="Static checks (cycles, missing fields, deps)" style="display:none;background:var(--bg-tertiary);border:1px solid var(--border);color:var(--text-secondary);padding:3px 8px;border-radius:4px;cursor:pointer;font-size:11px">Validate</button>
|
|
11384
|
+
<button id="builder-canvas-dryrun-btn" onclick="dryRunBuilderCanvas()" title="Describe what each step would do (no execution)" style="display:none;background:var(--bg-tertiary);border:1px solid var(--border);color:var(--text-secondary);padding:3px 8px;border-radius:4px;cursor:pointer;font-size:11px">Dry-run</button>
|
|
11385
|
+
<button id="builder-canvas-test-btn" onclick="testBuilderCanvas()" title="Test run (mock-safe by default)" style="display:none;background:var(--clementine);border:none;color:#fff;padding:3px 10px;border-radius:4px;cursor:pointer;font-size:11px">Test</button>
|
|
11386
|
+
<button id="builder-canvas-cancel-btn" onclick="cancelBuilderTest()" title="Cancel test run" style="display:none;background:var(--red);border:none;color:#fff;padding:3px 10px;border-radius:4px;cursor:pointer;font-size:11px">Cancel</button>
|
|
10720
11387
|
</div>
|
|
10721
11388
|
<div id="builder-preview" style="flex:1;overflow-y:auto;padding:16px">
|
|
10722
11389
|
<div class="empty-state" style="font-size:13px;color:var(--text-muted)">The artifact will appear here as you build it</div>
|
|
10723
11390
|
</div>
|
|
11391
|
+
<div id="builder-canvas-host" style="display:none;flex:1;flex-direction:column;min-height:0;position:relative">
|
|
11392
|
+
<div id="builder-canvas-banner" style="padding:8px 14px;background:var(--bg-tertiary);border-bottom:1px solid var(--border);font-size:11px;color:var(--text-muted);display:none"></div>
|
|
11393
|
+
<div id="builder-canvas" style="flex:1;background:var(--bg-tertiary);position:relative;overflow:hidden"></div>
|
|
11394
|
+
<!-- Floating add-node FAB + palette popover -->
|
|
11395
|
+
<button id="builder-palette-btn" onclick="toggleBuilderPalette()" title="Add a step" style="position:absolute;left:14px;bottom:48px;width:40px;height:40px;border-radius:50%;background:var(--clementine);color:#fff;border:none;font-size:20px;font-weight:600;cursor:pointer;box-shadow:0 2px 8px rgba(0,0,0,0.25);z-index:10">+</button>
|
|
11396
|
+
<div id="builder-palette-pop" style="display:none;position:absolute;left:60px;bottom:48px;background:var(--bg-secondary);border:1px solid var(--border);border-radius:8px;padding:6px;box-shadow:0 4px 16px rgba(0,0,0,0.2);z-index:11;min-width:160px">
|
|
11397
|
+
<div onclick="_builderAddNodeOfKind('prompt')" class="builder-palette-item" data-kind="prompt">prompt</div>
|
|
11398
|
+
<div onclick="_builderAddNodeOfKind('mcp')" class="builder-palette-item" data-kind="mcp">mcp tool</div>
|
|
11399
|
+
<div onclick="_builderAddNodeOfKind('channel')" class="builder-palette-item" data-kind="channel">channel</div>
|
|
11400
|
+
<div onclick="_builderAddNodeOfKind('transform')" class="builder-palette-item" data-kind="transform">transform</div>
|
|
11401
|
+
<div onclick="_builderAddNodeOfKind('conditional')" class="builder-palette-item" data-kind="conditional">conditional</div>
|
|
11402
|
+
<div onclick="_builderAddNodeOfKind('loop')" class="builder-palette-item" data-kind="loop">loop</div>
|
|
11403
|
+
</div>
|
|
11404
|
+
<!-- Slide-out config panel -->
|
|
11405
|
+
<div id="builder-config-panel" style="display:none;position:absolute;right:0;top:0;bottom:0;width:340px;background:var(--bg-secondary);border-left:1px solid var(--border);box-shadow:-4px 0 16px rgba(0,0,0,0.15);z-index:12;display:flex;flex-direction:column"></div>
|
|
11406
|
+
<div id="builder-canvas-footer" style="padding:6px 14px;border-top:1px solid var(--border);font-size:11px;color:var(--text-muted);display:flex;gap:14px;align-items:center">
|
|
11407
|
+
<span id="builder-canvas-status"></span>
|
|
11408
|
+
<span style="flex:1"></span>
|
|
11409
|
+
<span id="builder-canvas-id" style="font-family:monospace;opacity:0.6"></span>
|
|
11410
|
+
</div>
|
|
11411
|
+
</div>
|
|
10724
11412
|
<!-- Existing skills drawer (visible in skill mode) -->
|
|
10725
11413
|
<div id="builder-skills-drawer" style="display:none;border-top:2px solid var(--border);max-height:260px;overflow-y:auto">
|
|
10726
11414
|
<div style="padding:10px 16px;display:flex;align-items:center;justify-content:space-between;position:sticky;top:0;background:var(--bg-secondary);z-index:1">
|
|
@@ -10731,192 +11419,132 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
10731
11419
|
</div>
|
|
10732
11420
|
</div>
|
|
10733
11421
|
</div>
|
|
10734
|
-
</div>
|
|
10735
|
-
|
|
10736
|
-
<!-- ═══ Agent Detail Page (full-screen management console) ═══ -->
|
|
10737
|
-
<div class="page" id="page-agent-detail">
|
|
10738
|
-
<div id="agent-detail-content"><div class="empty-state">Select an agent from the sidebar</div></div>
|
|
10739
|
-
</div>
|
|
10740
11422
|
|
|
10741
|
-
|
|
10742
|
-
|
|
10743
|
-
|
|
10744
|
-
|
|
10745
|
-
|
|
10746
|
-
|
|
10747
|
-
|
|
10748
|
-
|
|
10749
|
-
|
|
10750
|
-
|
|
10751
|
-
|
|
10752
|
-
<div id="automations-tab-content">
|
|
10753
|
-
<div class="tab-pane active" id="tab-automations-scheduled">
|
|
10754
|
-
<div id="panel-cron"><div class="empty-state">Loading...</div></div>
|
|
10755
|
-
</div>
|
|
10756
|
-
<div class="tab-pane" id="tab-automations-broken">
|
|
10757
|
-
<div class="card">
|
|
10758
|
-
<div class="card-header" style="display:flex;align-items:center;justify-content:space-between">
|
|
10759
|
-
<span>Repeatedly Failing Jobs (last 48h)</span>
|
|
10760
|
-
<span class="badge badge-gray" id="broken-count-badge" style="font-size:10px">0 jobs</span>
|
|
11423
|
+
<!-- Templates tab — starter patterns -->
|
|
11424
|
+
<div id="build-tab-templates" data-build-tabpane="templates" style="display:none;padding:24px;overflow-y:auto">
|
|
11425
|
+
<div style="max-width:920px;margin:0 auto">
|
|
11426
|
+
<h2 style="font-size:18px;font-weight:600;margin:0 0 6px;color:var(--text-primary)">Start from a template</h2>
|
|
11427
|
+
<p style="font-size:13px;color:var(--text-muted);margin:0 0 18px">Pick a pre-built pattern to fork into a new editable workflow.</p>
|
|
11428
|
+
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:14px">
|
|
11429
|
+
<div class="card clickable-row" onclick="forkBuildTemplate('daily-news-digest')" style="padding:18px">
|
|
11430
|
+
<div style="font-size:24px;margin-bottom:8px">📰</div>
|
|
11431
|
+
<div style="font-weight:600;font-size:14px;margin-bottom:4px">Daily news digest</div>
|
|
11432
|
+
<div style="font-size:12px;color:var(--text-muted);line-height:1.4">Cron 7am: pull RSS sources, summarize, send to Slack/email.</div>
|
|
11433
|
+
<div style="margin-top:10px;font-size:11px;color:var(--clementine);font-weight:500">cron · 4 steps</div>
|
|
10761
11434
|
</div>
|
|
10762
|
-
<div class="card-
|
|
10763
|
-
|
|
10764
|
-
|
|
10765
|
-
|
|
10766
|
-
|
|
10767
|
-
<div class="card-body" id="panel-timers"><div class="empty-state">Loading...</div></div>
|
|
10768
|
-
</div>
|
|
10769
|
-
</div>
|
|
10770
|
-
<div class="tab-pane" id="tab-automations-self-improve">
|
|
10771
|
-
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px">
|
|
10772
|
-
<div style="font-size:13px;color:var(--text-secondary)">Self-improvement runs nightly at 1 AM. You can also trigger it manually.</div>
|
|
10773
|
-
<button class="btn-sm btn-primary" onclick="siRunCycle()" id="si-run-btn">Run Now</button>
|
|
10774
|
-
</div>
|
|
10775
|
-
<div class="grid-2" id="si-status-cards"></div>
|
|
10776
|
-
<div class="card" style="margin-top:16px">
|
|
10777
|
-
<div class="card-header">Pending Proposals</div>
|
|
10778
|
-
<div class="card-body" id="si-pending-list"><div class="empty-state">No pending proposals</div></div>
|
|
10779
|
-
</div>
|
|
10780
|
-
<div class="card" style="margin-top:16px">
|
|
10781
|
-
<div class="card-header">Experiment History</div>
|
|
10782
|
-
<div class="card-body" id="si-history-list"><div class="empty-state">No experiments yet</div></div>
|
|
10783
|
-
</div>
|
|
10784
|
-
</div>
|
|
10785
|
-
<div class="tab-pane" id="tab-automations-skills">
|
|
10786
|
-
<div class="card" style="margin-bottom:16px">
|
|
10787
|
-
<div class="card-header" style="display:flex;align-items:center;justify-content:space-between">
|
|
10788
|
-
<span>Teach a Skill</span>
|
|
10789
|
-
<button class="btn-sm btn-primary" onclick="toggleTeachSkill()" id="teach-skill-toggle" style="font-size:12px">+ New Skill</button>
|
|
11435
|
+
<div class="card clickable-row" onclick="forkBuildTemplate('lead-picker')" style="padding:18px">
|
|
11436
|
+
<div style="font-size:24px;margin-bottom:8px">📊</div>
|
|
11437
|
+
<div style="font-weight:600;font-size:14px;margin-bottom:4px">Lead picker → Salesforce</div>
|
|
11438
|
+
<div style="font-size:12px;color:var(--text-muted);line-height:1.4">Manual workflow: search leads by ICP, review, push selected to SF.</div>
|
|
11439
|
+
<div style="margin-top:10px;font-size:11px;color:var(--clementine);font-weight:500">manual · 3 steps</div>
|
|
10790
11440
|
</div>
|
|
10791
|
-
<div class="card-
|
|
10792
|
-
<div style="
|
|
10793
|
-
|
|
10794
|
-
|
|
10795
|
-
|
|
10796
|
-
</div>
|
|
10797
|
-
<div>
|
|
10798
|
-
<label style="font-size:12px;font-weight:600;color:var(--text-secondary)">Description</label>
|
|
10799
|
-
<input type="text" id="skill-description" placeholder="1-2 sentence summary of what this skill does" style="width:100%;padding:8px;border:1px solid var(--border);border-radius:6px;background:var(--bg-input);color:var(--text-primary);font-size:13px">
|
|
10800
|
-
</div>
|
|
10801
|
-
<div>
|
|
10802
|
-
<label style="font-size:12px;font-weight:600;color:var(--text-secondary)">Triggers <span style="font-weight:400;color:var(--text-muted)">(comma-separated keywords)</span></label>
|
|
10803
|
-
<input type="text" id="skill-triggers" placeholder="deploy, production, release, ship" style="width:100%;padding:8px;border:1px solid var(--border);border-radius:6px;background:var(--bg-input);color:var(--text-primary);font-size:13px">
|
|
10804
|
-
</div>
|
|
10805
|
-
<div>
|
|
10806
|
-
<label style="font-size:12px;font-weight:600;color:var(--text-secondary)">Procedure <span style="font-weight:400;color:var(--text-muted)">(markdown steps)</span></label>
|
|
10807
|
-
<textarea id="skill-steps" rows="6" placeholder="1. Run tests: npm test\n2. Build: npm run build\n3. Push: git push origin main\n4. Verify deploy succeeded" style="width:100%;padding:8px;border:1px solid var(--border);border-radius:6px;background:var(--bg-input);color:var(--text-primary);font-size:13px;font-family:monospace;resize:vertical"></textarea>
|
|
10808
|
-
</div>
|
|
10809
|
-
<div style="display:flex;gap:8px;justify-content:flex-end">
|
|
10810
|
-
<button class="btn-sm" onclick="toggleTeachSkill()" style="background:var(--bg-tertiary);border:1px solid var(--border);color:var(--text-primary);padding:6px 16px;border-radius:6px;cursor:pointer">Cancel</button>
|
|
10811
|
-
<button class="btn-sm btn-primary" onclick="saveSkill()" style="padding:6px 16px">Save Skill</button>
|
|
10812
|
-
</div>
|
|
10813
|
-
</div>
|
|
11441
|
+
<div class="card clickable-row" onclick="forkBuildTemplate('pr-review-queue')" style="padding:18px">
|
|
11442
|
+
<div style="font-size:24px;margin-bottom:8px">📝</div>
|
|
11443
|
+
<div style="font-weight:600;font-size:14px;margin-bottom:4px">PR review queue</div>
|
|
11444
|
+
<div style="font-size:12px;color:var(--text-muted);line-height:1.4">Cron 9am M-F: list open PRs, summarize risk, message to Slack.</div>
|
|
11445
|
+
<div style="margin-top:10px;font-size:11px;color:var(--clementine);font-weight:500">cron · 3 steps</div>
|
|
10814
11446
|
</div>
|
|
10815
|
-
|
|
10816
|
-
|
|
10817
|
-
|
|
10818
|
-
<
|
|
10819
|
-
<
|
|
11447
|
+
<div class="card clickable-row" onclick="forkBuildTemplate('email-triage')" style="padding:18px">
|
|
11448
|
+
<div style="font-size:24px;margin-bottom:8px">📧</div>
|
|
11449
|
+
<div style="font-weight:600;font-size:14px;margin-bottom:4px">Email triage</div>
|
|
11450
|
+
<div style="font-size:12px;color:var(--text-muted);line-height:1.4">Cron 8am: list unread emails, classify by intent, draft replies for review.</div>
|
|
11451
|
+
<div style="margin-top:10px;font-size:11px;color:var(--clementine);font-weight:500">cron · 4 steps</div>
|
|
10820
11452
|
</div>
|
|
10821
|
-
<div class="card-
|
|
10822
|
-
|
|
10823
|
-
|
|
10824
|
-
|
|
10825
|
-
<
|
|
10826
|
-
|
|
11453
|
+
<div class="card clickable-row" onclick="forkBuildTemplate('weekly-review')" style="padding:18px">
|
|
11454
|
+
<div style="font-size:24px;margin-bottom:8px">📅</div>
|
|
11455
|
+
<div style="font-weight:600;font-size:14px;margin-bottom:4px">Weekly review</div>
|
|
11456
|
+
<div style="font-size:12px;color:var(--text-muted);line-height:1.4">Cron Fri 6pm: review the week's daily notes, generate review note.</div>
|
|
11457
|
+
<div style="margin-top:10px;font-size:11px;color:var(--clementine);font-weight:500">cron · 3 steps</div>
|
|
11458
|
+
</div>
|
|
11459
|
+
<div class="card clickable-row" onclick="forkBuildTemplate('blank-workflow')" style="padding:18px;border-style:dashed">
|
|
11460
|
+
<div style="font-size:24px;margin-bottom:8px">➕</div>
|
|
11461
|
+
<div style="font-weight:600;font-size:14px;margin-bottom:4px">Blank workflow</div>
|
|
11462
|
+
<div style="font-size:12px;color:var(--text-muted);line-height:1.4">Start from scratch with a single prompt step.</div>
|
|
11463
|
+
<div style="margin-top:10px;font-size:11px;color:var(--clementine);font-weight:500">manual · 1 step</div>
|
|
10827
11464
|
</div>
|
|
10828
|
-
<div class="card-body" id="panel-skills"><div class="empty-state">No skills learned yet. Skills are auto-extracted from successful tasks or taught manually above.</div></div>
|
|
10829
11465
|
</div>
|
|
10830
11466
|
</div>
|
|
10831
|
-
<div class="tab-pane" id="tab-automations-workflows">
|
|
10832
|
-
<div id="panel-workflows"><div class="empty-state">Loading workflows...</div></div>
|
|
10833
|
-
</div>
|
|
10834
|
-
<div class="tab-pane" id="tab-automations-analytics">
|
|
10835
|
-
<div id="advisor-analytics-content"><div class="empty-state">Loading analytics...</div></div>
|
|
10836
|
-
</div>
|
|
10837
11467
|
</div>
|
|
10838
11468
|
</div>
|
|
10839
11469
|
|
|
10840
|
-
<!--
|
|
10841
|
-
|
|
10842
|
-
|
|
10843
|
-
|
|
10844
|
-
|
|
10845
|
-
|
|
10846
|
-
|
|
10847
|
-
|
|
10848
|
-
|
|
10849
|
-
|
|
10850
|
-
|
|
10851
|
-
|
|
10852
|
-
|
|
10853
|
-
|
|
10854
|
-
|
|
10855
|
-
|
|
10856
|
-
|
|
10857
|
-
|
|
10858
|
-
|
|
10859
|
-
|
|
10860
|
-
|
|
10861
|
-
|
|
10862
|
-
|
|
10863
|
-
|
|
10864
|
-
|
|
10865
|
-
|
|
10866
|
-
|
|
10867
|
-
: ''
|
|
10868
|
-
|
|
10869
|
-
|
|
10870
|
-
|
|
10871
|
-
: ''
|
|
10872
|
-
|
|
10873
|
-
|
|
10874
|
-
|
|
10875
|
-
|
|
10876
|
-
|
|
10877
|
-
|
|
10878
|
-
|
|
10879
|
-
|
|
10880
|
-
|
|
10881
|
-
|
|
10882
|
-
|
|
10883
|
-
|
|
10884
|
-
|
|
10885
|
-
|
|
10886
|
-
|
|
10887
|
-
|
|
10888
|
-
|
|
10889
|
-
|
|
10890
|
-
document.addEventListener('DOMContentLoaded', () => {
|
|
10891
|
-
const obs = new MutationObserver(() => {
|
|
10892
|
-
const page = document.getElementById('page-team-status');
|
|
10893
|
-
if (page && page.classList.contains('active')) renderTeamStatus();
|
|
10894
|
-
});
|
|
10895
|
-
const content = document.querySelector('.content');
|
|
10896
|
-
if (content) obs.observe(content, { subtree: true, attributes: true, attributeFilter: ['class'] });
|
|
11470
|
+
<!-- page-agent-detail merged into Team page; click an agent in Roster to drill down. -->
|
|
11471
|
+
|
|
11472
|
+
|
|
11473
|
+
<!-- DELETED in Session 5 — automations content moved to Build / Brain → Learning.
|
|
11474
|
+
(Block formerly housed: panel-cron, panel-broken-jobs, panel-timers,
|
|
11475
|
+
si-* status cards/proposals/history, teach-skill-form, panel-skills,
|
|
11476
|
+
pending-skills-card, panel-workflows, advisor-analytics-content.) -->
|
|
11477
|
+
<!-- (Session 5) page-automations parking removed. Self-Improve now lives in
|
|
11478
|
+
Brain → Learning; Build (Workflows/Crons/Skills/Templates) is the home for
|
|
11479
|
+
everything else that was here. -->
|
|
11480
|
+
|
|
11481
|
+
<!-- page-team-status merged into Team → Activity tab.
|
|
11482
|
+
Render is now triggered by switchTab('team','activity'). -->
|
|
11483
|
+
<script>
|
|
11484
|
+
(function() {
|
|
11485
|
+
window.renderTeamStatus = function renderTeamStatus() {
|
|
11486
|
+
fetch('/api/team/status').then(function(r) { return r.json(); }).then(function(data) {
|
|
11487
|
+
var grid = document.getElementById('team-status-grid');
|
|
11488
|
+
if (!grid) return;
|
|
11489
|
+
if (!data.agents || data.agents.length === 0) {
|
|
11490
|
+
grid.innerHTML = '<div class="empty-cta"><div class="label">No agents yet</div><div class="hint">Hire your first agent from the Roster tab.</div></div>';
|
|
11491
|
+
return;
|
|
11492
|
+
}
|
|
11493
|
+
grid.innerHTML = data.agents.map(function(a) {
|
|
11494
|
+
var avatarLetter = (a.name || a.slug || '?').charAt(0).toUpperCase();
|
|
11495
|
+
var tasksHtml = a.tasks
|
|
11496
|
+
? '<div style="margin:4px 0"><span style="font-size:12px;color:var(--text-secondary)">Tasks: </span><strong>' + (a.tasks.pending || 0) + ' pending</strong>' +
|
|
11497
|
+
(a.tasks.overdue > 0 ? ' <span style="color:#ef4444;font-weight:600">' + a.tasks.overdue + ' overdue</span>' : '') + '</div>'
|
|
11498
|
+
: '';
|
|
11499
|
+
var goalsHtml = a.activeGoals > 0
|
|
11500
|
+
? '<div style="margin:4px 0;font-size:12px"><span style="color:var(--text-secondary)">Goals: </span><strong>' + a.activeGoals + ' active</strong>' +
|
|
11501
|
+
(a.firstGoalTitle ? ' — ' + a.firstGoalTitle.slice(0, 60) : '') + '</div>'
|
|
11502
|
+
: '';
|
|
11503
|
+
var noteHtml = a.lastNoteDate
|
|
11504
|
+
? '<div style="margin:4px 0;font-size:12px;color:var(--text-secondary)">Last log: ' + a.lastNoteDate +
|
|
11505
|
+
(a.lastNoteSnippet ? ' — ' + a.lastNoteSnippet.slice(0, 100) : '') + '</div>'
|
|
11506
|
+
: '';
|
|
11507
|
+
var wmHtml = a.workingMemorySnippet
|
|
11508
|
+
? '<div style="margin-top:8px;padding:8px;background:var(--bg-input);border-radius:6px;font-size:11px;color:var(--text-secondary)">' + a.workingMemorySnippet + '</div>'
|
|
11509
|
+
: '';
|
|
11510
|
+
return '<div class="clickable-row" style="background:var(--bg-card);border:1px solid var(--border);border-radius:12px;padding:16px" onclick="navigateTo(\\x27team\\x27,{tab:\\x27roster\\x27,agentSlug:\\x27' + a.slug + '\\x27})">' +
|
|
11511
|
+
'<div style="display:flex;align-items:center;gap:10px;margin-bottom:10px">' +
|
|
11512
|
+
'<div style="width:40px;height:40px;border-radius:50%;background:var(--clementine);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:18px">' + avatarLetter + '</div>' +
|
|
11513
|
+
'<div><div style="font-weight:600">' + (a.name || a.slug) + '</div>' +
|
|
11514
|
+
'<div style="font-size:11px;color:var(--text-secondary)">' + (a.description || a.slug) + '</div></div></div>' +
|
|
11515
|
+
tasksHtml + goalsHtml + noteHtml + wmHtml + '</div>';
|
|
11516
|
+
}).join('');
|
|
11517
|
+
}).catch(function() {
|
|
11518
|
+
var grid = document.getElementById('team-status-grid');
|
|
11519
|
+
if (grid) grid.innerHTML = '<div class="empty-state">Failed to load team status.</div>';
|
|
10897
11520
|
});
|
|
10898
|
-
}
|
|
10899
|
-
|
|
10900
|
-
</
|
|
11521
|
+
};
|
|
11522
|
+
})();
|
|
11523
|
+
</script>
|
|
10901
11524
|
|
|
10902
11525
|
<!-- ═══ Brain Page (unified: Search + Graph + Stats + Sources + Seed + Runs) ═══ -->
|
|
10903
|
-
<div class="page" id="page-
|
|
10904
|
-
<div class="page-
|
|
10905
|
-
|
|
10906
|
-
|
|
10907
|
-
|
|
10908
|
-
|
|
10909
|
-
|
|
10910
|
-
<
|
|
11526
|
+
<div class="page" id="page-brain">
|
|
11527
|
+
<div class="page-head">
|
|
11528
|
+
<div class="icon">🧠</div>
|
|
11529
|
+
<div class="title-block">
|
|
11530
|
+
<h1>Brain</h1>
|
|
11531
|
+
<p class="desc">Query what you know, feed new knowledge in, and watch the system learn.</p>
|
|
11532
|
+
</div>
|
|
11533
|
+
<div class="actions" style="flex:1;max-width:480px;display:flex;gap:8px">
|
|
11534
|
+
<input type="text" id="memory-search-input" placeholder="Search vault, notes, memory..." style="flex:1;padding:6px 10px;border:1px solid var(--border);border-radius:6px;background:var(--bg-input);color:var(--text-primary);font-size:13px" onkeydown="if(event.key==='Enter')runMemorySearch()">
|
|
11535
|
+
<button class="btn-primary btn-sm" onclick="runMemorySearch()">Search</button>
|
|
11536
|
+
</div>
|
|
10911
11537
|
</div>
|
|
10912
|
-
<div class="tab-bar" id="intelligence-tabs">
|
|
10913
|
-
<button class="active" onclick="switchTab('intelligence','search')">
|
|
10914
|
-
<button onclick="switchTab('intelligence','graph')">Knowledge
|
|
11538
|
+
<div class="tab-bar" id="intelligence-tabs" style="margin:0 0 0 18px">
|
|
11539
|
+
<button class="active" onclick="switchTab('intelligence','search')">Memory</button>
|
|
11540
|
+
<button onclick="switchTab('intelligence','graph')">Knowledge</button>
|
|
11541
|
+
<button onclick="switchTab('intelligence','sources')">Ingestion</button>
|
|
11542
|
+
<button onclick="switchTab('intelligence','health')">Health <span class="tab-badge" id="brain-health-badge" style="display:none;background:#ef4444;color:#fff">0</span></button>
|
|
10915
11543
|
<button onclick="switchTab('intelligence','user-model')">User Model</button>
|
|
10916
|
-
<button onclick="switchTab('intelligence','
|
|
10917
|
-
<button onclick="switchTab('intelligence','
|
|
10918
|
-
<button onclick="switchTab('intelligence','
|
|
10919
|
-
<button onclick="switchTab('intelligence','runs')">
|
|
11544
|
+
<button onclick="switchTab('intelligence','learning')">Learning <span class="tab-badge" id="brain-learning-badge" style="display:none;background:#f59e0b;color:#000">0</span></button>
|
|
11545
|
+
<button onclick="switchTab('intelligence','memory')">Stats</button>
|
|
11546
|
+
<button onclick="switchTab('intelligence','seed')">Seed</button>
|
|
11547
|
+
<button onclick="switchTab('intelligence','runs')">Runs</button>
|
|
10920
11548
|
</div>
|
|
10921
11549
|
<div id="intelligence-tab-content">
|
|
10922
11550
|
<div class="tab-pane active" id="tab-intelligence-search">
|
|
@@ -11118,6 +11746,68 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
11118
11746
|
<div class="tab-pane" id="tab-intelligence-runs">
|
|
11119
11747
|
<div id="brain-runs-list"></div>
|
|
11120
11748
|
</div>
|
|
11749
|
+
<div class="tab-pane" id="tab-intelligence-health">
|
|
11750
|
+
<div style="display:flex;align-items:center;gap:8px;margin-bottom:14px;flex-wrap:wrap">
|
|
11751
|
+
<button class="btn-sm" onclick="memoryHealthAction('janitor')" title="Run the janitor cleanup pass now">Run cleanup</button>
|
|
11752
|
+
<button class="btn-sm" onclick="memoryHealthAction('rebuild-fts')" title="Rebuild the FTS5 index">Rebuild FTS</button>
|
|
11753
|
+
<button class="btn-sm" onclick="memoryHealthAction('fix-orphans')" title="Null out missing derived_from refs">Fix orphans</button>
|
|
11754
|
+
<button class="btn-sm" onclick="refreshMemoryHealth()">Refresh</button>
|
|
11755
|
+
</div>
|
|
11756
|
+
<div id="memory-health-content">
|
|
11757
|
+
<div class="skel-block"><div class="skel-row med"></div><div class="skel-row"></div><div class="skel-row short"></div></div>
|
|
11758
|
+
</div>
|
|
11759
|
+
<div class="card" style="margin-top:18px">
|
|
11760
|
+
<div class="card-header" style="display:flex;align-items:center;justify-content:space-between">
|
|
11761
|
+
<span>Trust & claim verification</span>
|
|
11762
|
+
<span id="trust-score-detail" style="font-size:11px;color:var(--text-muted)">Rolling 30-claim score</span>
|
|
11763
|
+
</div>
|
|
11764
|
+
<div class="card-body" style="padding:14px;display:flex;align-items:center;gap:14px">
|
|
11765
|
+
<span style="font-size:28px;font-weight:700" id="trust-score-big">--</span>
|
|
11766
|
+
<div style="flex:1;display:flex;gap:6px;flex-wrap:wrap">
|
|
11767
|
+
<button class="btn-sm" onclick="refreshClaims('all')" id="claims-filter-all">All</button>
|
|
11768
|
+
<button class="btn-sm" onclick="refreshClaims('pending')" id="claims-filter-pending">Pending</button>
|
|
11769
|
+
<button class="btn-sm" onclick="refreshClaims('verified')" id="claims-filter-verified">Verified</button>
|
|
11770
|
+
<button class="btn-sm" onclick="refreshClaims('failed')" id="claims-filter-failed">Failed</button>
|
|
11771
|
+
</div>
|
|
11772
|
+
</div>
|
|
11773
|
+
</div>
|
|
11774
|
+
<div class="card" style="margin-top:14px">
|
|
11775
|
+
<div class="card-header">Recent claims</div>
|
|
11776
|
+
<div class="card-body" id="panel-claims">
|
|
11777
|
+
<div class="skel-block"><div class="skel-row med"></div><div class="skel-row short"></div></div>
|
|
11778
|
+
</div>
|
|
11779
|
+
</div>
|
|
11780
|
+
<div class="card" style="margin-top:14px">
|
|
11781
|
+
<div class="card-header" style="display:flex;align-items:center;justify-content:space-between">
|
|
11782
|
+
<span>Team routing decisions</span>
|
|
11783
|
+
<span style="font-size:11px;color:var(--text-muted)">Owner-facing sessions only</span>
|
|
11784
|
+
</div>
|
|
11785
|
+
<div class="card-body" id="panel-routing-audit">
|
|
11786
|
+
<div class="skel-block"><div class="skel-row med"></div></div>
|
|
11787
|
+
</div>
|
|
11788
|
+
</div>
|
|
11789
|
+
</div>
|
|
11790
|
+
<div class="tab-pane" id="tab-intelligence-learning">
|
|
11791
|
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px">
|
|
11792
|
+
<div style="font-size:13px;color:var(--text-secondary)">Self-improvement runs nightly at 1 AM. You can also trigger it manually.</div>
|
|
11793
|
+
<button class="btn-sm btn-primary" onclick="siRunCycle()" id="si-run-btn">Run Now</button>
|
|
11794
|
+
</div>
|
|
11795
|
+
<div class="grid-2" id="si-status-cards">
|
|
11796
|
+
<div class="skel-block"><div class="skel-row med"></div><div class="skel-row short"></div></div>
|
|
11797
|
+
<div class="skel-block"><div class="skel-row med"></div><div class="skel-row short"></div></div>
|
|
11798
|
+
</div>
|
|
11799
|
+
<div class="card" style="margin-top:16px">
|
|
11800
|
+
<div class="card-header" style="display:flex;align-items:center;justify-content:space-between">
|
|
11801
|
+
<span>Pending Proposals</span>
|
|
11802
|
+
<span class="tab-badge" id="tab-si-pending" style="display:none;background:#f59e0b;color:#000">0</span>
|
|
11803
|
+
</div>
|
|
11804
|
+
<div class="card-body" id="si-pending-list"><div class="empty-state">No pending proposals</div></div>
|
|
11805
|
+
</div>
|
|
11806
|
+
<div class="card" style="margin-top:16px">
|
|
11807
|
+
<div class="card-header">Experiment History</div>
|
|
11808
|
+
<div class="card-body" id="si-history-list"><div class="empty-state">No experiments yet</div></div>
|
|
11809
|
+
</div>
|
|
11810
|
+
</div>
|
|
11121
11811
|
</div>
|
|
11122
11812
|
|
|
11123
11813
|
<script>
|
|
@@ -12072,143 +12762,47 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
12072
12762
|
</script>
|
|
12073
12763
|
</div>
|
|
12074
12764
|
|
|
12075
|
-
<!--
|
|
12076
|
-
|
|
12077
|
-
|
|
12078
|
-
|
|
12079
|
-
|
|
12080
|
-
|
|
12081
|
-
|
|
12082
|
-
|
|
12083
|
-
<div
|
|
12084
|
-
<div
|
|
12085
|
-
<div
|
|
12086
|
-
|
|
12087
|
-
<div style="font-size:36px;font-weight:700" id="trust-score-big">--</div>
|
|
12088
|
-
<div style="flex:1">
|
|
12089
|
-
<div style="font-size:13px;font-weight:600">Clementine's trust score</div>
|
|
12090
|
-
<div style="font-size:11px;color:var(--text-muted)" id="trust-score-detail">
|
|
12091
|
-
Rolling over the last 30 verified or failed claims.
|
|
12092
|
-
</div>
|
|
12093
|
-
</div>
|
|
12094
|
-
<div style="display:flex;gap:6px">
|
|
12095
|
-
<button class="btn-sm" onclick="refreshClaims('all')" id="claims-filter-all" style="padding:4px 10px">All</button>
|
|
12096
|
-
<button class="btn-sm" onclick="refreshClaims('pending')" id="claims-filter-pending" style="padding:4px 10px">Pending</button>
|
|
12097
|
-
<button class="btn-sm" onclick="refreshClaims('verified')" id="claims-filter-verified" style="padding:4px 10px">Verified</button>
|
|
12098
|
-
<button class="btn-sm" onclick="refreshClaims('failed')" id="claims-filter-failed" style="padding:4px 10px">Failed</button>
|
|
12099
|
-
</div>
|
|
12100
|
-
</div>
|
|
12101
|
-
</div>
|
|
12102
|
-
<div class="card">
|
|
12103
|
-
<div class="card-header">Recent claims</div>
|
|
12104
|
-
<div class="card-body" id="panel-claims"><div class="empty-state">Loading...</div></div>
|
|
12105
|
-
</div>
|
|
12106
|
-
<div class="card" style="margin-top:16px">
|
|
12107
|
-
<div class="card-header" style="display:flex;align-items:center;justify-content:space-between">
|
|
12108
|
-
<span>Team routing decisions</span>
|
|
12109
|
-
<span style="font-size:11px;color:var(--text-muted)">Only owner-facing Clementine sessions are classified — agent-bot DMs bypass routing entirely.</span>
|
|
12110
|
-
</div>
|
|
12111
|
-
<div class="card-body" id="panel-routing-audit"><div class="empty-state">Loading...</div></div>
|
|
12112
|
-
</div>
|
|
12113
|
-
</div>
|
|
12114
|
-
|
|
12115
|
-
<!-- ═══ Logs Page ═══ -->
|
|
12116
|
-
<div class="page" id="page-logs">
|
|
12117
|
-
<div class="page-title">Logs</div>
|
|
12118
|
-
<div class="log-toolbar">
|
|
12119
|
-
<input type="text" class="log-filter" id="log-filter" placeholder="Filter logs...">
|
|
12120
|
-
<select id="log-level-filter" style="background:var(--bg-input);border:1px solid var(--border);border-radius:var(--radius-sm);padding:6px 10px;font-size:12px;color:var(--text-primary);font-family:inherit;cursor:pointer" onchange="applyLogFilter()">
|
|
12121
|
-
<option value="">All Levels</option>
|
|
12122
|
-
<option value="error">Error+</option>
|
|
12123
|
-
<option value="warn">Warn+</option>
|
|
12124
|
-
<option value="info">Info+</option>
|
|
12125
|
-
<option value="debug">Debug+</option>
|
|
12126
|
-
</select>
|
|
12127
|
-
<button onclick="refreshLogs()">Refresh</button>
|
|
12128
|
-
<label style="display:flex;align-items:center;gap:6px;font-size:12px;color:var(--text-secondary);cursor:pointer">
|
|
12129
|
-
<input type="checkbox" id="log-autoscroll" checked> Auto-scroll
|
|
12130
|
-
</label>
|
|
12131
|
-
</div>
|
|
12132
|
-
<div class="log-viewer" id="panel-logs"><div class="empty-state">Loading...</div></div>
|
|
12133
|
-
</div>
|
|
12134
|
-
|
|
12135
|
-
<!-- ═══ Chat Page ═══ -->
|
|
12136
|
-
<div class="page" id="page-chat">
|
|
12137
|
-
<div id="chat-messages" style="flex:1;overflow-y:auto;padding:16px">
|
|
12138
|
-
<div class="empty-state">
|
|
12139
|
-
<p style="margin-bottom:14px;color:var(--text-muted)">Send a message to start a conversation.</p>
|
|
12140
|
-
<div style="display:flex;flex-wrap:wrap;gap:8px;justify-content:center">
|
|
12141
|
-
<button class="btn btn-sm quick-pill" onclick="quickChat("What's on my schedule?")">What's on my schedule?</button>
|
|
12142
|
-
<button class="btn btn-sm quick-pill" onclick="quickChat('Check my email')">Check my email</button>
|
|
12143
|
-
<button class="btn btn-sm quick-pill" onclick="quickChat('Run morning briefing')">Run morning briefing</button>
|
|
12144
|
-
<button class="btn btn-sm quick-pill" onclick="quickChat('What did you do today?')">What did you do today?</button>
|
|
12145
|
-
</div>
|
|
12146
|
-
</div>
|
|
12147
|
-
</div>
|
|
12148
|
-
<div style="border-top:1px solid var(--border);padding:8px 14px 0;display:flex;align-items:center;gap:8px" id="chat-profile-bar">
|
|
12149
|
-
<span style="font-size:11px;color:var(--text-muted)">Profile:</span>
|
|
12150
|
-
<select id="chat-profile-select" onchange="switchProfile(this.value)" style="font-size:11px;padding:2px 6px;border:1px solid var(--border);border-radius:4px;background:var(--bg-secondary);color:var(--text-primary)">
|
|
12151
|
-
<option value="">Default</option>
|
|
12152
|
-
</select>
|
|
12153
|
-
</div>
|
|
12154
|
-
<div style="border-top:1px solid var(--border);padding:14px;display:flex;gap:10px">
|
|
12155
|
-
<input type="text" id="chat-input" placeholder="Type a message..." style="flex:1" onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();sendChat()}">
|
|
12156
|
-
<button class="btn-primary" id="chat-send-btn" onclick="sendChat()">Send</button>
|
|
12157
|
-
</div>
|
|
12158
|
-
</div>
|
|
12159
|
-
|
|
12160
|
-
<!-- ═══ Metrics Page ═══ -->
|
|
12161
|
-
<div class="page" id="page-metrics">
|
|
12162
|
-
<div class="page-title">Metrics</div>
|
|
12163
|
-
<p style="color:var(--text-muted);margin-bottom:16px">Time-saved estimates, run success rates, and per-job breakdowns.</p>
|
|
12164
|
-
<div id="metrics-content"><div class="empty-state">Loading metrics...</div></div>
|
|
12165
|
-
</div>
|
|
12166
|
-
|
|
12167
|
-
<!-- ═══ Memory Health Page ═══ -->
|
|
12168
|
-
<div class="page" id="page-memory-health">
|
|
12169
|
-
<div class="page-title">Memory Health</div>
|
|
12170
|
-
<p style="color:var(--text-muted);margin-bottom:16px">Bounded growth, retrieval signal, and curation drift — everything the janitor manages.</p>
|
|
12171
|
-
<div style="display:flex;gap:8px;margin-bottom:16px">
|
|
12172
|
-
<button class="btn btn-sm" onclick="refreshMemoryHealth()">Refresh</button>
|
|
12173
|
-
</div>
|
|
12174
|
-
<div id="memory-health-content"><div class="empty-state">Loading memory health...</div></div>
|
|
12765
|
+
<!-- Sessions, Trust & Claims, Logs, Chat, Metrics, Daily Plan pages
|
|
12766
|
+
removed in Session 2 — content lives on Home or migrates to other
|
|
12767
|
+
destinations in Sessions 3-4. Page references for these IDs are
|
|
12768
|
+
resolved via ROUTE_REDIRECTS in navigateTo. -->
|
|
12769
|
+
|
|
12770
|
+
<!-- Hidden mounting points for daily-plan content used by the Home rail
|
|
12771
|
+
(refreshDailyPlan looks for these by ID). Keeping the IDs avoids
|
|
12772
|
+
touching every refreshDailyPlan callsite right now. -->
|
|
12773
|
+
<div style="display:none">
|
|
12774
|
+
<div id="plan-diff-content"></div>
|
|
12775
|
+
<div id="plan-history-list"></div>
|
|
12776
|
+
<input type="date" id="plan-date-picker-secondary" disabled>
|
|
12175
12777
|
</div>
|
|
12176
12778
|
|
|
12177
|
-
<!--
|
|
12178
|
-
<div class="page" id="page-daily-plan">
|
|
12179
|
-
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:20px">
|
|
12180
|
-
<div class="page-title" style="margin-bottom:0">Daily Plan</div>
|
|
12181
|
-
<div style="display:flex;gap:8px;align-items:center">
|
|
12182
|
-
<input type="date" id="plan-date-picker" style="padding:6px 10px;background:var(--bg-input);border:1px solid var(--border);border-radius:6px;color:var(--text-primary)">
|
|
12183
|
-
<button class="btn btn-sm" onclick="loadPlanForDate(document.getElementById('plan-date-picker').value)">Load</button>
|
|
12184
|
-
</div>
|
|
12185
|
-
</div>
|
|
12186
|
-
<div id="daily-plan-content"><div class="empty-state">Loading plan...</div></div>
|
|
12187
|
-
<div id="plan-diff-content" style="margin-top:16px"></div>
|
|
12188
|
-
<details style="margin-top:16px">
|
|
12189
|
-
<summary style="cursor:pointer;font-weight:600;color:var(--text-secondary);font-size:13px;padding:8px 0;user-select:none">Plan History</summary>
|
|
12190
|
-
<div id="plan-history-list" style="margin-top:8px"><div class="empty-state">Loading...</div></div>
|
|
12191
|
-
</details>
|
|
12192
|
-
</div>
|
|
12779
|
+
<!-- (Session 5) page-memory-health parking stub removed. Brain → Health is the live home. -->
|
|
12193
12780
|
|
|
12194
|
-
<!--
|
|
12195
|
-
<div class="page" id="page-goals">
|
|
12196
|
-
<div class="page-title">Goals</div>
|
|
12197
|
-
<p style="color:var(--text-muted);margin-bottom:16px">Long-running objectives the team is contributing to. Tracks per-agent contribution and run success rate.</p>
|
|
12198
|
-
<div id="goals-progress-content"><div class="empty-state">Loading goals...</div></div>
|
|
12199
|
-
</div>
|
|
12781
|
+
<!-- page-goals merged into Team → Goals tab. -->
|
|
12200
12782
|
|
|
12201
12783
|
<!-- ═══ Team Page — The Office ═══ -->
|
|
12202
12784
|
<div class="page" id="page-team">
|
|
12203
|
-
<div
|
|
12204
|
-
<div class="
|
|
12205
|
-
<div
|
|
12206
|
-
<
|
|
12207
|
-
<
|
|
12208
|
-
|
|
12209
|
-
|
|
12785
|
+
<div class="page-head">
|
|
12786
|
+
<div class="icon">👥</div>
|
|
12787
|
+
<div class="title-block">
|
|
12788
|
+
<h1>The Office</h1>
|
|
12789
|
+
<p class="desc">Your team of agents — what they're doing, what they're contributing to.</p>
|
|
12790
|
+
</div>
|
|
12791
|
+
<div class="actions">
|
|
12792
|
+
<button class="btn-primary btn-sm" onclick="startHiringInterview()" style="background:var(--green);color:#000">Hire</button>
|
|
12793
|
+
<button class="btn-sm" onclick="applyRoleTemplate('sdr')" title="Pre-filled SDR role template">+ SDR</button>
|
|
12794
|
+
<button class="btn-sm" onclick="applyRoleTemplate('researcher')" title="Pre-filled researcher role template">+ Researcher</button>
|
|
12795
|
+
<button class="btn-sm" onclick="showAgentCreateModal()">Manual</button>
|
|
12210
12796
|
</div>
|
|
12211
12797
|
</div>
|
|
12798
|
+
<div class="tab-bar" id="team-tabs" style="margin:0 0 0 18px">
|
|
12799
|
+
<button class="active" onclick="switchTab('team','roster')">Roster</button>
|
|
12800
|
+
<button onclick="switchTab('team','activity')">Activity</button>
|
|
12801
|
+
<button onclick="switchTab('team','goals')">Goals</button>
|
|
12802
|
+
<button onclick="switchTab('team','comms')">Comms</button>
|
|
12803
|
+
</div>
|
|
12804
|
+
<div id="team-tab-content">
|
|
12805
|
+
<div class="tab-pane active" id="tab-team-roster">
|
|
12212
12806
|
<div id="office-hero-section"></div>
|
|
12213
12807
|
<div class="office-floor" id="team-agent-grid">
|
|
12214
12808
|
<div class="empty-state">No agents configured</div>
|
|
@@ -12413,17 +13007,60 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
12413
13007
|
</form>
|
|
12414
13008
|
</div>
|
|
12415
13009
|
</div>
|
|
13010
|
+
</div><!-- /tab-team-roster -->
|
|
13011
|
+
|
|
13012
|
+
<!-- Team → Activity tab (migrated from page-team-status) -->
|
|
13013
|
+
<div class="tab-pane" id="tab-team-activity" style="padding:18px">
|
|
13014
|
+
<div id="team-status-grid" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:16px">
|
|
13015
|
+
<div class="skel-block"><div class="skel-row med"></div><div class="skel-row"></div><div class="skel-row short"></div></div>
|
|
13016
|
+
</div>
|
|
13017
|
+
</div>
|
|
13018
|
+
|
|
13019
|
+
<!-- Team → Goals tab (migrated from page-goals) -->
|
|
13020
|
+
<div class="tab-pane" id="tab-team-goals" style="padding:18px">
|
|
13021
|
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:14px">
|
|
13022
|
+
<p style="margin:0;font-size:13px;color:var(--text-muted)">Long-running objectives the team contributes to. Per-agent contribution + run success rate.</p>
|
|
13023
|
+
<button class="btn-primary btn-sm" onclick="openNewGoalForm()">+ New Goal</button>
|
|
13024
|
+
</div>
|
|
13025
|
+
<div id="new-goal-form" style="display:none;background:var(--bg-secondary);border:1px solid var(--border);border-radius:8px;padding:16px;margin-bottom:14px">
|
|
13026
|
+
<div style="font-weight:600;font-size:13px;margin-bottom:10px">New goal</div>
|
|
13027
|
+
<input type="text" id="new-goal-title" placeholder="Goal title (e.g., 'Pipeline: 12 qualified leads/week')" style="width:100%;padding:8px 10px;border:1px solid var(--border);border-radius:6px;background:var(--bg-input);color:var(--text-primary);margin-bottom:8px">
|
|
13028
|
+
<textarea id="new-goal-desc" placeholder="Why does this matter? (optional)" rows="2" style="width:100%;padding:8px 10px;border:1px solid var(--border);border-radius:6px;background:var(--bg-input);color:var(--text-primary);font-family:inherit;margin-bottom:10px"></textarea>
|
|
13029
|
+
<div style="display:flex;gap:8px;justify-content:flex-end">
|
|
13030
|
+
<button class="btn-sm" onclick="document.getElementById('new-goal-form').style.display='none'">Cancel</button>
|
|
13031
|
+
<button class="btn-sm btn-primary" onclick="submitNewGoal()">Create</button>
|
|
13032
|
+
</div>
|
|
13033
|
+
</div>
|
|
13034
|
+
<div id="goals-progress-content">
|
|
13035
|
+
<div class="skel-block"><div class="skel-row med"></div><div class="skel-row"></div><div class="skel-row short"></div></div>
|
|
13036
|
+
</div>
|
|
13037
|
+
</div>
|
|
13038
|
+
|
|
13039
|
+
<!-- Team → Comms tab — placeholder (Session 5 will fill). -->
|
|
13040
|
+
<div class="tab-pane" id="tab-team-comms" style="padding:18px">
|
|
13041
|
+
<div class="empty-cta">
|
|
13042
|
+
<div class="icon">📣</div>
|
|
13043
|
+
<div class="label">Inter-agent communications</div>
|
|
13044
|
+
<div class="hint">Coming soon — message log + topology visualization across agents.</div>
|
|
13045
|
+
</div>
|
|
13046
|
+
</div>
|
|
13047
|
+
</div><!-- /team-tab-content -->
|
|
12416
13048
|
</div>
|
|
12417
13049
|
|
|
13050
|
+
<!-- (Session 5) team-status / agent-detail / goals parking divs removed.
|
|
13051
|
+
Team's Roster / Activity / Goals tabs are the live homes. -->
|
|
13052
|
+
|
|
12418
13053
|
<!-- ═══ Settings Page (merged: General + Remote + Integrations + Projects) ═══ -->
|
|
12419
13054
|
<div class="page" id="page-settings">
|
|
12420
13055
|
<div class="page-title">Settings</div>
|
|
12421
13056
|
<div class="tab-bar" id="settings-tabs">
|
|
12422
|
-
<button class="active" onclick="switchTab('settings','general')">
|
|
12423
|
-
<button onclick="switchTab('settings','remote')">Remote Access</button>
|
|
12424
|
-
<button onclick="switchTab('settings','security')">Security</button>
|
|
13057
|
+
<button class="active" onclick="switchTab('settings','general')">Channels & Env</button>
|
|
12425
13058
|
<button onclick="switchTab('settings','integrations')">Integrations</button>
|
|
12426
|
-
<button onclick="switchTab('settings','
|
|
13059
|
+
<button onclick="switchTab('settings','projects')">Projects</button>
|
|
13060
|
+
<button onclick="switchTab('settings','security')">Security</button>
|
|
13061
|
+
<button onclick="switchTab('settings','logs')">Logs</button>
|
|
13062
|
+
<button onclick="switchTab('settings','remote')">Remote Access</button>
|
|
13063
|
+
<button onclick="switchTab('settings','advanced')">Advanced</button>
|
|
12427
13064
|
</div>
|
|
12428
13065
|
<div id="settings-tab-content">
|
|
12429
13066
|
<div class="tab-pane active" id="tab-settings-general">
|
|
@@ -12537,41 +13174,62 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
12537
13174
|
</div>
|
|
12538
13175
|
</div>
|
|
12539
13176
|
</div>
|
|
12540
|
-
<div class="tab-pane" id="tab-settings-
|
|
12541
|
-
<
|
|
13177
|
+
<div class="tab-pane" id="tab-settings-projects">
|
|
13178
|
+
<p style="color:var(--text-muted);margin-bottom:16px;font-size:13px">Link projects to give Clementine automatic access to their tools and MCP servers. When you mention a linked project's keywords in chat, Clementine switches into that project's context automatically.</p>
|
|
13179
|
+
<div class="card" style="margin-bottom:20px">
|
|
13180
|
+
<div class="card-header" style="display:flex;align-items:center;justify-content:space-between">
|
|
13181
|
+
<span>Workspace Directories</span>
|
|
13182
|
+
<button class="btn btn-sm btn-primary" onclick="promptAddWorkspaceDir()" style="font-size:11px">+ Add Path</button>
|
|
13183
|
+
</div>
|
|
13184
|
+
<div class="card-body" id="workspace-dirs-list-projects" style="font-size:13px">
|
|
13185
|
+
<div class="skel-block"><div class="skel-row med"></div><div class="skel-row short"></div></div>
|
|
13186
|
+
</div>
|
|
13187
|
+
</div>
|
|
13188
|
+
<div id="panel-projects-page">
|
|
13189
|
+
<div class="skel-block"><div class="skel-row med"></div><div class="skel-row"></div></div>
|
|
13190
|
+
</div>
|
|
12542
13191
|
</div>
|
|
12543
|
-
|
|
12544
|
-
|
|
12545
|
-
|
|
12546
|
-
|
|
12547
|
-
|
|
12548
|
-
|
|
12549
|
-
|
|
12550
|
-
|
|
12551
|
-
|
|
12552
|
-
|
|
12553
|
-
|
|
13192
|
+
<div class="tab-pane" id="tab-settings-logs">
|
|
13193
|
+
<div style="display:flex;gap:8px;align-items:center;margin-bottom:14px;flex-wrap:wrap">
|
|
13194
|
+
<input type="text" class="log-filter" id="log-filter" placeholder="Filter logs..." style="padding:6px 10px;border:1px solid var(--border);border-radius:6px;font-size:12px;background:var(--bg-input);color:var(--text-primary);min-width:180px;flex:1">
|
|
13195
|
+
<select id="log-level-filter" style="background:var(--bg-input);border:1px solid var(--border);border-radius:6px;padding:6px 10px;font-size:12px;color:var(--text-primary);font-family:inherit;cursor:pointer" onchange="applyLogFilter()">
|
|
13196
|
+
<option value="">All Levels</option>
|
|
13197
|
+
<option value="error">Error+</option>
|
|
13198
|
+
<option value="warn">Warn+</option>
|
|
13199
|
+
<option value="info">Info+</option>
|
|
13200
|
+
<option value="debug">Debug+</option>
|
|
13201
|
+
</select>
|
|
13202
|
+
<label style="display:flex;align-items:center;gap:6px;font-size:12px;color:var(--text-secondary);cursor:pointer">
|
|
13203
|
+
<input type="checkbox" id="log-autoscroll" checked> Auto-scroll
|
|
13204
|
+
</label>
|
|
13205
|
+
<button class="btn-sm" onclick="refreshLogs()">Refresh</button>
|
|
13206
|
+
</div>
|
|
13207
|
+
<div class="log-viewer" id="panel-logs">
|
|
13208
|
+
<div class="skel-block" style="padding:18px"><div class="skel-row med"></div><div class="skel-row"></div><div class="skel-row short"></div></div>
|
|
13209
|
+
</div>
|
|
12554
13210
|
</div>
|
|
12555
|
-
<div class="
|
|
12556
|
-
<div class="
|
|
13211
|
+
<div class="tab-pane" id="tab-settings-advanced">
|
|
13212
|
+
<div class="card" style="margin-bottom:16px">
|
|
13213
|
+
<div class="card-header">Diagnostics & maintenance</div>
|
|
13214
|
+
<div class="card-body" style="padding:16px;display:flex;gap:8px;flex-wrap:wrap">
|
|
13215
|
+
<button class="btn-sm" onclick="restartDashboard()">Restart Dashboard</button>
|
|
13216
|
+
<button class="btn-sm" onclick="if(confirm('Restart the daemon? Active sessions drain first.')) apiPost('/api/restart')">Restart Daemon</button>
|
|
13217
|
+
<button class="btn-sm" onclick="apiFetch('/api/doctor').then(function(r){return r.text()}).then(function(t){alert(t)})">Run Doctor</button>
|
|
13218
|
+
<button class="btn-sm" onclick="apiFetch('/api/version').then(function(r){return r.json()}).then(function(d){alert('Version: '+(d.version||'?')+'\\nNode: '+(d.node||'?'))})">Build info</button>
|
|
13219
|
+
</div>
|
|
13220
|
+
</div>
|
|
13221
|
+
<div class="card">
|
|
13222
|
+
<div class="card-header">Notifications & digest</div>
|
|
13223
|
+
<div class="card-body" style="padding:16px" id="digest-settings-content">
|
|
13224
|
+
<div class="skel-block"><div class="skel-row med"></div><div class="skel-row short"></div></div>
|
|
13225
|
+
</div>
|
|
13226
|
+
</div>
|
|
12557
13227
|
</div>
|
|
12558
13228
|
</div>
|
|
12559
|
-
<div id="panel-projects-page"><div class="empty-state">Loading...</div></div>
|
|
12560
|
-
</div>
|
|
12561
|
-
|
|
12562
|
-
<!-- ═══ Workflows Page (promoted from Automations) ═══ -->
|
|
12563
|
-
<div class="page" id="page-workflows">
|
|
12564
|
-
<div class="page-title">Workflows</div>
|
|
12565
|
-
<p style="color:var(--text-muted);margin-bottom:16px">Multi-step pipelines that orchestrate agents, tools, and data. Define workflows as .md files in <code>vault/00-System/workflows/</code>.</p>
|
|
12566
|
-
<div id="panel-workflows-page"><div class="empty-state">Loading workflows...</div></div>
|
|
12567
13229
|
</div>
|
|
12568
13230
|
|
|
12569
|
-
<!--
|
|
12570
|
-
|
|
12571
|
-
<div class="page-title">Unleashed</div>
|
|
12572
|
-
<p style="color:var(--text-muted);margin-bottom:16px">Long-running autonomous tasks that work in phases with checkpointing. Cancel a task by dropping a CANCEL marker — the agent stops at the next phase boundary.</p>
|
|
12573
|
-
<div id="panel-unleashed"><div class="empty-state">Loading...</div></div>
|
|
12574
|
-
</div>
|
|
13231
|
+
<!-- (Session 5) page-workflows / page-unleashed parking divs removed.
|
|
13232
|
+
Build → Workflows + Home rail Active Runs are the live homes. -->
|
|
12575
13233
|
|
|
12576
13234
|
</div><!-- /content -->
|
|
12577
13235
|
</div><!-- /layout -->
|
|
@@ -13198,59 +13856,323 @@ let currentPage = 'home';
|
|
|
13198
13856
|
var currentAgentSlug = null;
|
|
13199
13857
|
var prevAgentSlugs = null;
|
|
13200
13858
|
|
|
13201
|
-
|
|
13202
|
-
|
|
13859
|
+
// ── Routing ────────────────────────────────────────────────────
|
|
13860
|
+
//
|
|
13861
|
+
// Five top-level destinations: home, build, team, brain, settings.
|
|
13862
|
+
// Sub-pages live as tabs within each destination.
|
|
13863
|
+
// Old routes redirect once for back-compat.
|
|
13864
|
+
|
|
13865
|
+
var DESTINATIONS = ['home', 'build', 'team', 'brain', 'settings'];
|
|
13866
|
+
|
|
13867
|
+
var ROUTE_REDIRECTS = {
|
|
13868
|
+
// old hash → new {page, tab}
|
|
13869
|
+
'chat': { page: 'home', tab: 'chat' },
|
|
13870
|
+
'sessions': { page: 'home', tab: 'activity' },
|
|
13871
|
+
'daily-plan': { page: 'home', tab: 'today' },
|
|
13872
|
+
'goals': { page: 'team', tab: 'goals' },
|
|
13873
|
+
'workflows': { page: 'build', tab: 'workflows' },
|
|
13874
|
+
'automations': { page: 'build', tab: 'crons' },
|
|
13875
|
+
'unleashed': { page: 'build', tab: 'workflows' },
|
|
13876
|
+
'builder': { page: 'build', tab: 'workflows' },
|
|
13877
|
+
'skill-studio': { page: 'build', tab: 'skills' },
|
|
13878
|
+
'team-status': { page: 'team', tab: 'activity' },
|
|
13879
|
+
'agent-detail': { page: 'team', tab: 'roster' },
|
|
13880
|
+
'intelligence': { page: 'brain', tab: 'memory' },
|
|
13881
|
+
'memory-health': { page: 'brain', tab: 'health' },
|
|
13882
|
+
'claims': { page: 'brain', tab: 'health' },
|
|
13883
|
+
'metrics': { page: 'team', tab: 'activity' },
|
|
13884
|
+
'logs': { page: 'settings', tab: 'logs' },
|
|
13885
|
+
'projects': { page: 'settings', tab: 'projects' },
|
|
13886
|
+
};
|
|
13887
|
+
|
|
13888
|
+
function navigateTo(page, opts) {
|
|
13889
|
+
opts = opts || {};
|
|
13890
|
+
|
|
13891
|
+
// Redirect old route names to the new IA so existing callers + bookmarks work.
|
|
13892
|
+
if (ROUTE_REDIRECTS[page]) {
|
|
13893
|
+
var r = ROUTE_REDIRECTS[page];
|
|
13894
|
+
return navigateTo(r.page, Object.assign({ tab: r.tab }, opts));
|
|
13895
|
+
}
|
|
13896
|
+
|
|
13897
|
+
if (DESTINATIONS.indexOf(page) === -1) page = 'home';
|
|
13203
13898
|
currentPage = page;
|
|
13204
|
-
|
|
13205
|
-
document.querySelectorAll('.nav-item').forEach(n
|
|
13206
|
-
document.querySelectorAll('.team-nav-item').forEach(n
|
|
13207
|
-
document.querySelectorAll('.page').forEach(p
|
|
13208
|
-
|
|
13899
|
+
|
|
13900
|
+
document.querySelectorAll('.nav-item').forEach(function(n) { n.classList.remove('active'); });
|
|
13901
|
+
document.querySelectorAll('.team-nav-item').forEach(function(n) { n.classList.remove('active'); });
|
|
13902
|
+
document.querySelectorAll('.page').forEach(function(p) { p.classList.remove('active'); });
|
|
13903
|
+
|
|
13209
13904
|
var navEl = document.querySelector('.nav-item[data-page="' + page + '"]');
|
|
13210
13905
|
if (navEl) navEl.classList.add('active');
|
|
13211
13906
|
if (opts.agentSlug != null) {
|
|
13212
13907
|
var teamEl = document.querySelector('.team-nav-item[data-slug="' + opts.agentSlug + '"]');
|
|
13213
13908
|
if (teamEl) teamEl.classList.add('active');
|
|
13214
13909
|
}
|
|
13215
|
-
|
|
13910
|
+
|
|
13216
13911
|
var el = document.getElementById('page-' + page);
|
|
13217
13912
|
if (el) { el.style.display = ''; el.classList.add('active'); }
|
|
13218
|
-
|
|
13219
|
-
|
|
13220
|
-
|
|
13221
|
-
|
|
13222
|
-
|
|
13223
|
-
|
|
13224
|
-
|
|
13225
|
-
|
|
13226
|
-
|
|
13227
|
-
|
|
13228
|
-
|
|
13229
|
-
|
|
13230
|
-
|
|
13231
|
-
|
|
13232
|
-
|
|
13233
|
-
|
|
13234
|
-
|
|
13235
|
-
|
|
13236
|
-
|
|
13237
|
-
|
|
13238
|
-
|
|
13239
|
-
|
|
13240
|
-
|
|
13241
|
-
|
|
13242
|
-
|
|
13243
|
-
|
|
13913
|
+
|
|
13914
|
+
// Per-destination init + tab routing
|
|
13915
|
+
switch (page) {
|
|
13916
|
+
case 'home':
|
|
13917
|
+
refreshAll();
|
|
13918
|
+
// tab is a soft hint on Home (one cohesive layout): focus the relevant area.
|
|
13919
|
+
var t = opts.tab || 'chat';
|
|
13920
|
+
setTimeout(function() {
|
|
13921
|
+
if (t === 'chat') {
|
|
13922
|
+
var ci = document.getElementById('chat-input');
|
|
13923
|
+
if (ci) ci.focus();
|
|
13924
|
+
} else if (t === 'today') {
|
|
13925
|
+
var rail = document.getElementById('home-rail');
|
|
13926
|
+
if (rail && window.matchMedia('(max-width: 1024px)').matches) rail.classList.add('open');
|
|
13927
|
+
var p = document.getElementById('home-plan-content');
|
|
13928
|
+
if (p) p.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
13929
|
+
} else if (t === 'activity') {
|
|
13930
|
+
var act = document.getElementById('panel-activity');
|
|
13931
|
+
if (act) act.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
13932
|
+
}
|
|
13933
|
+
}, 80);
|
|
13934
|
+
break;
|
|
13935
|
+
case 'build':
|
|
13936
|
+
switchBuildTab(opts.tab || 'workflows');
|
|
13937
|
+
var bp = currentAgentSlug || '';
|
|
13938
|
+
refreshBuilderAgents(bp);
|
|
13939
|
+
break;
|
|
13940
|
+
case 'team':
|
|
13941
|
+
refreshTeam();
|
|
13942
|
+
switchDestTab('team', opts.tab || 'roster');
|
|
13943
|
+
if (opts.agentSlug) {
|
|
13944
|
+
currentAgentSlug = opts.agentSlug;
|
|
13945
|
+
if (typeof openAgentDrawer === 'function') openAgentDrawer(opts.agentSlug);
|
|
13946
|
+
}
|
|
13947
|
+
break;
|
|
13948
|
+
case 'brain':
|
|
13949
|
+
if (typeof refreshMemory === 'function') refreshMemory();
|
|
13950
|
+
var bt = opts.tab || 'memory';
|
|
13951
|
+
// Spec tab names → internal intelligence-tab ids
|
|
13952
|
+
var intelTab = bt === 'memory' ? 'search'
|
|
13953
|
+
: bt === 'knowledge' ? 'graph'
|
|
13954
|
+
: bt === 'ingestion' ? 'sources'
|
|
13955
|
+
: bt === 'health' ? 'health'
|
|
13956
|
+
: bt === 'user-model' ? 'user-model'
|
|
13957
|
+
: bt === 'learning' ? 'learning'
|
|
13958
|
+
: bt;
|
|
13959
|
+
try { switchTab('intelligence', intelTab); } catch (e) { /* */ }
|
|
13960
|
+
if (bt === 'health') {
|
|
13961
|
+
if (typeof refreshMemoryHealth === 'function') refreshMemoryHealth();
|
|
13962
|
+
if (typeof refreshClaims === 'function') refreshClaims();
|
|
13963
|
+
if (typeof refreshRoutingAudit === 'function') refreshRoutingAudit();
|
|
13964
|
+
}
|
|
13965
|
+
if (bt === 'learning' && typeof refreshSelfImprove === 'function') refreshSelfImprove();
|
|
13966
|
+
break;
|
|
13967
|
+
case 'settings':
|
|
13968
|
+
switchDestTab('settings', opts.tab || 'channels');
|
|
13969
|
+
break;
|
|
13244
13970
|
}
|
|
13971
|
+
|
|
13245
13972
|
closeSidebar();
|
|
13246
13973
|
}
|
|
13247
13974
|
|
|
13248
|
-
|
|
13249
|
-
function
|
|
13250
|
-
|
|
13251
|
-
|
|
13975
|
+
/** Switch the active tab inside a destination. Tabs use [data-tab] markup. */
|
|
13976
|
+
function switchDestTab(page, tab) {
|
|
13977
|
+
if (!tab) return;
|
|
13978
|
+
var pageEl = document.getElementById('page-' + page);
|
|
13979
|
+
if (!pageEl) return;
|
|
13980
|
+
pageEl.querySelectorAll('[data-tab]').forEach(function(el) {
|
|
13981
|
+
var match = el.getAttribute('data-tab') === tab;
|
|
13982
|
+
if (el.tagName === 'BUTTON' || el.classList.contains('tab-btn')) {
|
|
13983
|
+
el.classList.toggle('active', match);
|
|
13984
|
+
} else {
|
|
13985
|
+
el.style.display = match ? '' : 'none';
|
|
13986
|
+
}
|
|
13987
|
+
});
|
|
13988
|
+
// Per-tab init hook (called after DOM update)
|
|
13989
|
+
var initFn = window['_init_' + page + '_' + tab];
|
|
13990
|
+
if (typeof initFn === 'function') {
|
|
13991
|
+
try { initFn(); } catch (err) { console.warn('Tab init failed:', err); }
|
|
13992
|
+
}
|
|
13993
|
+
}
|
|
13994
|
+
|
|
13995
|
+
// (Session 5) openAutomationsTab compat shim removed — all callers route via navigateTo + ROUTE_REDIRECTS.
|
|
13996
|
+
|
|
13997
|
+
// ── Build (Workflows / Crons / Skills / Templates) tabs ─────────────
|
|
13998
|
+
function switchBuildTab(tab) {
|
|
13999
|
+
if (!tab) tab = 'workflows';
|
|
14000
|
+
// Update tab-bar active state
|
|
14001
|
+
document.querySelectorAll('#build-tabs button').forEach(function(b) {
|
|
14002
|
+
b.classList.toggle('active', b.getAttribute('data-build-tab') === tab);
|
|
14003
|
+
});
|
|
14004
|
+
// Show/hide tab panes
|
|
14005
|
+
var workPane = document.getElementById('build-tab-workflows');
|
|
14006
|
+
var tplPane = document.getElementById('build-tab-templates');
|
|
14007
|
+
var headerStrip = document.getElementById('build-header-strip');
|
|
14008
|
+
if (tab === 'templates') {
|
|
14009
|
+
if (workPane) workPane.style.display = 'none';
|
|
14010
|
+
if (tplPane) tplPane.style.display = '';
|
|
14011
|
+
if (headerStrip) headerStrip.style.display = 'none';
|
|
14012
|
+
} else {
|
|
14013
|
+
if (workPane) workPane.style.display = 'flex';
|
|
14014
|
+
if (tplPane) tplPane.style.display = 'none';
|
|
14015
|
+
if (headerStrip) headerStrip.style.display = 'flex';
|
|
14016
|
+
// Map build-tab → builder-type so the canvas + chat reflect the tab.
|
|
14017
|
+
var typeSel = document.getElementById('builder-type');
|
|
14018
|
+
if (typeSel) {
|
|
14019
|
+
var nextType = tab === 'crons' ? 'cron' : tab === 'skills' ? 'skill' : 'workflow';
|
|
14020
|
+
if (typeSel.value !== nextType) {
|
|
14021
|
+
typeSel.value = nextType;
|
|
14022
|
+
if (typeof resetBuilder === 'function') resetBuilder();
|
|
14023
|
+
if (typeof updateBuilderMode === 'function') updateBuilderMode();
|
|
14024
|
+
} else if (typeof updateBuilderMode === 'function') {
|
|
14025
|
+
updateBuilderMode();
|
|
14026
|
+
}
|
|
14027
|
+
}
|
|
14028
|
+
// Focus chat input
|
|
14029
|
+
setTimeout(function() {
|
|
14030
|
+
var bi = document.getElementById('builder-input');
|
|
14031
|
+
if (bi) bi.focus();
|
|
14032
|
+
}, 60);
|
|
14033
|
+
}
|
|
14034
|
+
}
|
|
14035
|
+
|
|
14036
|
+
// ── Build templates: fork a starter pattern into a new workflow ─────
|
|
14037
|
+
async function forkBuildTemplate(templateId) {
|
|
14038
|
+
var templates = {
|
|
14039
|
+
'daily-news-digest': {
|
|
14040
|
+
name: 'Daily news digest',
|
|
14041
|
+
description: 'Morning news roundup pulled from configured RSS feeds.',
|
|
14042
|
+
schedule: '0 7 * * *',
|
|
14043
|
+
initialPrompt: 'Pull today headlines from configured RSS sources, summarize the top 5 stories, format as a brief digest, then send to my preferred channel.',
|
|
14044
|
+
},
|
|
14045
|
+
'lead-picker': {
|
|
14046
|
+
name: 'Lead picker (manual)',
|
|
14047
|
+
description: 'Search leads matching ICP, pick from canvas, push selected to Salesforce.',
|
|
14048
|
+
schedule: undefined,
|
|
14049
|
+
initialPrompt: 'Use Salesforce search to find prospects matching the ICP I describe. Show results so I can pick which to push as leads.',
|
|
14050
|
+
},
|
|
14051
|
+
'pr-review-queue': {
|
|
14052
|
+
name: 'PR review queue',
|
|
14053
|
+
description: 'Weekday morning summary of open PRs that need review.',
|
|
14054
|
+
schedule: '0 9 * * 1-5',
|
|
14055
|
+
initialPrompt: 'List open GitHub PRs that need my review, summarize each PRs risk level and key changes, send a digest to Slack.',
|
|
14056
|
+
},
|
|
14057
|
+
'email-triage': {
|
|
14058
|
+
name: 'Email triage',
|
|
14059
|
+
description: 'Morning unread-email triage with classified suggested actions.',
|
|
14060
|
+
schedule: '0 8 * * *',
|
|
14061
|
+
initialPrompt: 'List unread emails, classify each by intent (reply / archive / snooze / delegate), draft replies for the ones needing one, surface for my approval.',
|
|
14062
|
+
},
|
|
14063
|
+
'weekly-review': {
|
|
14064
|
+
name: 'Weekly review',
|
|
14065
|
+
description: 'Friday-evening review of the week and next-week priorities.',
|
|
14066
|
+
schedule: '0 18 * * 5',
|
|
14067
|
+
initialPrompt: 'Read this past 7 days of daily notes, summarize what got done, list whats still pending, suggest top 3 priorities for next week, append to today daily note as ## Weekly Review.',
|
|
14068
|
+
},
|
|
14069
|
+
'blank-workflow': {
|
|
14070
|
+
name: 'New workflow',
|
|
14071
|
+
description: '',
|
|
14072
|
+
schedule: undefined,
|
|
14073
|
+
initialPrompt: 'Describe what this workflow should do.',
|
|
14074
|
+
},
|
|
14075
|
+
};
|
|
14076
|
+
var tpl = templates[templateId];
|
|
14077
|
+
if (!tpl) { toast('Unknown template', 'error'); return; }
|
|
14078
|
+
var name = prompt('Name for the new workflow:', tpl.name);
|
|
14079
|
+
if (!name) return;
|
|
14080
|
+
try {
|
|
14081
|
+
var r = await apiJson('POST', '/api/builder/workflows', {
|
|
14082
|
+
name: name,
|
|
14083
|
+
description: tpl.description,
|
|
14084
|
+
schedule: tpl.schedule,
|
|
14085
|
+
initialPrompt: tpl.initialPrompt,
|
|
14086
|
+
});
|
|
14087
|
+
if (r && r.error) { toast('Create failed: ' + r.error, 'error'); return; }
|
|
14088
|
+
if (r && r.id) {
|
|
14089
|
+
switchBuildTab(tpl.schedule ? 'crons' : 'workflows');
|
|
14090
|
+
refreshBuilderCanvasPicker(tpl.schedule ? 'cron' : 'workflow');
|
|
14091
|
+
setTimeout(function() { openBuilderWorkflow(r.id); }, 200);
|
|
14092
|
+
toast('Forked template: ' + name, 'success');
|
|
14093
|
+
}
|
|
14094
|
+
} catch (err) {
|
|
14095
|
+
toast('Fork failed: ' + err, 'error');
|
|
14096
|
+
}
|
|
14097
|
+
}
|
|
14098
|
+
|
|
14099
|
+
// Cmd+K palette — keyboard-only quick navigation.
|
|
14100
|
+
function openCommandK() {
|
|
14101
|
+
var existing = document.getElementById('cmdk-overlay');
|
|
14102
|
+
if (existing) { existing.remove(); return; }
|
|
14103
|
+
var overlay = document.createElement('div');
|
|
14104
|
+
overlay.id = 'cmdk-overlay';
|
|
14105
|
+
overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.4);z-index:2000;display:flex;align-items:flex-start;justify-content:center;padding-top:120px';
|
|
14106
|
+
overlay.onclick = function(e) { if (e.target === overlay) overlay.remove(); };
|
|
14107
|
+
var box = document.createElement('div');
|
|
14108
|
+
box.style.cssText = 'width:520px;max-width:92vw;background:var(--bg-secondary);border:1px solid var(--border);border-radius:10px;box-shadow:0 12px 40px rgba(0,0,0,0.35);overflow:hidden';
|
|
14109
|
+
box.innerHTML =
|
|
14110
|
+
'<input id="cmdk-input" type="text" placeholder="Jump to… (home, build/workflows, team/goals, brain/health, settings/logs)" style="width:100%;padding:14px 18px;border:none;background:transparent;color:var(--text-primary);font-size:14px;outline:none;border-bottom:1px solid var(--border)">' +
|
|
14111
|
+
'<div id="cmdk-results" style="max-height:320px;overflow-y:auto"></div>';
|
|
14112
|
+
overlay.appendChild(box);
|
|
14113
|
+
document.body.appendChild(overlay);
|
|
14114
|
+
var input = document.getElementById('cmdk-input');
|
|
14115
|
+
var results = document.getElementById('cmdk-results');
|
|
14116
|
+
var entries = [
|
|
14117
|
+
{ kw: 'home chat', page: 'home', tab: 'chat', label: 'Home · Chat' },
|
|
14118
|
+
{ kw: 'home today plan', page: 'home', tab: 'today', label: 'Home · Today' },
|
|
14119
|
+
{ kw: 'home activity', page: 'home', tab: 'activity', label: 'Home · Activity' },
|
|
14120
|
+
{ kw: 'build workflows', page: 'build', tab: 'workflows', label: 'Build · Workflows' },
|
|
14121
|
+
{ kw: 'build crons', page: 'build', tab: 'crons', label: 'Build · Crons' },
|
|
14122
|
+
{ kw: 'build skills', page: 'build', tab: 'skills', label: 'Build · Skills' },
|
|
14123
|
+
{ kw: 'build templates', page: 'build', tab: 'templates', label: 'Build · Templates' },
|
|
14124
|
+
{ kw: 'team roster', page: 'team', tab: 'roster', label: 'Team · Roster' },
|
|
14125
|
+
{ kw: 'team activity', page: 'team', tab: 'activity', label: 'Team · Activity' },
|
|
14126
|
+
{ kw: 'team comms', page: 'team', tab: 'comms', label: 'Team · Comms' },
|
|
14127
|
+
{ kw: 'team goals', page: 'team', tab: 'goals', label: 'Team · Goals' },
|
|
14128
|
+
{ kw: 'brain memory', page: 'brain', tab: 'memory', label: 'Brain · Memory' },
|
|
14129
|
+
{ kw: 'brain knowledge', page: 'brain', tab: 'knowledge', label: 'Brain · Knowledge' },
|
|
14130
|
+
{ kw: 'brain ingestion', page: 'brain', tab: 'ingestion', label: 'Brain · Ingestion' },
|
|
14131
|
+
{ kw: 'brain health', page: 'brain', tab: 'health', label: 'Brain · Health' },
|
|
14132
|
+
{ kw: 'brain user model', page: 'brain', tab: 'user-model', label: 'Brain · User Model' },
|
|
14133
|
+
{ kw: 'settings channels', page: 'settings', tab: 'channels', label: 'Settings · Channels' },
|
|
14134
|
+
{ kw: 'settings integrations mcp', page: 'settings', tab: 'integrations', label: 'Settings · Integrations' },
|
|
14135
|
+
{ kw: 'settings projects', page: 'settings', tab: 'projects', label: 'Settings · Projects' },
|
|
14136
|
+
{ kw: 'settings security', page: 'settings', tab: 'security', label: 'Settings · Security' },
|
|
14137
|
+
{ kw: 'settings logs', page: 'settings', tab: 'logs', label: 'Settings · Logs' },
|
|
14138
|
+
{ kw: 'settings advanced', page: 'settings', tab: 'advanced', label: 'Settings · Advanced' },
|
|
14139
|
+
];
|
|
14140
|
+
function render() {
|
|
14141
|
+
var q = (input.value || '').toLowerCase().trim();
|
|
14142
|
+
var hits = q ? entries.filter(function(e) { return e.kw.indexOf(q) !== -1 || e.label.toLowerCase().indexOf(q) !== -1; }) : entries;
|
|
14143
|
+
results.innerHTML = hits.slice(0, 12).map(function(e, i) {
|
|
14144
|
+
return '<div class="cmdk-row" data-page="' + e.page + '" data-tab="' + e.tab + '" style="padding:10px 18px;font-size:13px;cursor:pointer;display:flex;justify-content:space-between;align-items:center' + (i === 0 ? ';background:var(--bg-hover)' : '') + '">' +
|
|
14145
|
+
'<span>' + e.label + '</span>' +
|
|
14146
|
+
'<span style="font-size:11px;color:var(--text-muted)">' + e.page + '/' + e.tab + '</span>' +
|
|
14147
|
+
'</div>';
|
|
14148
|
+
}).join('') || '<div style="padding:14px 18px;color:var(--text-muted);font-size:12px">No matches</div>';
|
|
14149
|
+
results.querySelectorAll('.cmdk-row').forEach(function(row) {
|
|
14150
|
+
row.onclick = function() {
|
|
14151
|
+
navigateTo(row.getAttribute('data-page'), { tab: row.getAttribute('data-tab') });
|
|
14152
|
+
overlay.remove();
|
|
14153
|
+
};
|
|
14154
|
+
});
|
|
14155
|
+
}
|
|
14156
|
+
input.oninput = render;
|
|
14157
|
+
input.onkeydown = function(e) {
|
|
14158
|
+
if (e.key === 'Escape') overlay.remove();
|
|
14159
|
+
if (e.key === 'Enter') {
|
|
14160
|
+
var first = results.querySelector('.cmdk-row');
|
|
14161
|
+
if (first) first.click();
|
|
14162
|
+
}
|
|
14163
|
+
};
|
|
14164
|
+
render();
|
|
14165
|
+
setTimeout(function() { input.focus(); }, 30);
|
|
13252
14166
|
}
|
|
13253
14167
|
|
|
14168
|
+
// Global keyboard shortcut for Cmd+K / Ctrl+K
|
|
14169
|
+
document.addEventListener('keydown', function(e) {
|
|
14170
|
+
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
|
|
14171
|
+
e.preventDefault();
|
|
14172
|
+
openCommandK();
|
|
14173
|
+
}
|
|
14174
|
+
});
|
|
14175
|
+
|
|
13254
14176
|
async function refreshUnleashed() {
|
|
13255
14177
|
var el = document.getElementById('panel-unleashed');
|
|
13256
14178
|
if (!el) return;
|
|
@@ -13333,23 +14255,27 @@ function switchTab(group, tab) {
|
|
|
13333
14255
|
if (pane) pane.classList.add('active');
|
|
13334
14256
|
}
|
|
13335
14257
|
// Tab-specific refresh
|
|
13336
|
-
if (group === 'automations') {
|
|
13337
|
-
if (tab === 'scheduled') refreshCron();
|
|
13338
|
-
if (tab === 'broken') refreshBrokenJobs();
|
|
13339
|
-
if (tab === 'timers') refreshTimers();
|
|
13340
|
-
if (tab === 'self-improve') refreshSelfImprove();
|
|
13341
|
-
if (tab === 'workflows') refreshWorkflows();
|
|
13342
|
-
if (tab === 'analytics') refreshAdvisorAnalytics();
|
|
13343
|
-
}
|
|
13344
14258
|
if (group === 'intelligence') {
|
|
13345
14259
|
if (tab === 'graph') refreshGraph();
|
|
13346
14260
|
if (tab === 'memory') refreshMemory();
|
|
14261
|
+
if (tab === 'health') {
|
|
14262
|
+
if (typeof refreshMemoryHealth === 'function') refreshMemoryHealth();
|
|
14263
|
+
if (typeof refreshClaims === 'function') refreshClaims();
|
|
14264
|
+
if (typeof refreshRoutingAudit === 'function') refreshRoutingAudit();
|
|
14265
|
+
}
|
|
14266
|
+
if (tab === 'learning' && typeof refreshSelfImprove === 'function') refreshSelfImprove();
|
|
13347
14267
|
}
|
|
13348
14268
|
if (group === 'settings') {
|
|
13349
14269
|
if (tab === 'integrations') refreshSalesforce();
|
|
13350
14270
|
if (tab === 'remote') refreshRemoteAccess();
|
|
13351
14271
|
if (tab === 'security') refreshAuthSessions();
|
|
13352
|
-
if (tab === '
|
|
14272
|
+
if (tab === 'projects' && typeof refreshProjects === 'function') refreshProjects();
|
|
14273
|
+
if (tab === 'logs' && typeof refreshLogs === 'function') refreshLogs();
|
|
14274
|
+
if (tab === 'advanced' && typeof refreshDigestSettings === 'function') refreshDigestSettings();
|
|
14275
|
+
}
|
|
14276
|
+
if (group === 'team') {
|
|
14277
|
+
if (tab === 'activity' && typeof renderTeamStatus === 'function') renderTeamStatus();
|
|
14278
|
+
if (tab === 'goals' && typeof refreshGoalsProgress === 'function') refreshGoalsProgress();
|
|
13353
14279
|
}
|
|
13354
14280
|
}
|
|
13355
14281
|
|
|
@@ -14416,7 +15342,7 @@ async function refreshSessions() {
|
|
|
14416
15342
|
var s = d[key];
|
|
14417
15343
|
var friendly = friendlySession(key);
|
|
14418
15344
|
var safeKey = esc(key).replace(/'/g, '');
|
|
14419
|
-
html += '<div class="session-card
|
|
15345
|
+
html += '<div class="session-card clickable-row" onclick="viewSession(\\x27' + encodeURIComponent(key) + '\\x27)">'
|
|
14420
15346
|
+ '<div class="session-card-header">'
|
|
14421
15347
|
+ '<span class="session-card-icon">' + friendly.icon + '</span>'
|
|
14422
15348
|
+ '<span class="session-card-name">' + esc(friendly.label) + '</span>'
|
|
@@ -14425,6 +15351,7 @@ async function refreshSessions() {
|
|
|
14425
15351
|
+ '<div class="session-card-meta">Last active: ' + timeAgo(s.timestamp) + '</div>'
|
|
14426
15352
|
+ '<div class="session-card-meta" style="font-family:monospace;font-size:10px">' + esc(key) + '</div>'
|
|
14427
15353
|
+ '<div class="session-card-actions">'
|
|
15354
|
+
+ '<button class="btn-sm btn-primary" onclick="event.stopPropagation();resumeSession(\\x27' + encodeURIComponent(key) + '\\x27)" title="Open in chat">Resume</button>'
|
|
14428
15355
|
+ '<button class="btn-danger btn-sm" onclick="event.stopPropagation();if(confirm(\\x27Clear session ' + safeKey + '?\\x27))apiPost(\\x27/api/sessions/' + encodeURIComponent(key) + '/clear\\x27)">Clear</button>'
|
|
14429
15356
|
+ '</div></div>';
|
|
14430
15357
|
}
|
|
@@ -14433,6 +15360,17 @@ async function refreshSessions() {
|
|
|
14433
15360
|
} catch(e) { }
|
|
14434
15361
|
}
|
|
14435
15362
|
|
|
15363
|
+
function resumeSession(encodedKey) {
|
|
15364
|
+
var key = decodeURIComponent(encodedKey);
|
|
15365
|
+
// The dashboard chat session is fixed at "dashboard:web" today, so we
|
|
15366
|
+
// navigate to chat and surface a hint. If/when chat gains multi-session
|
|
15367
|
+
// support, this is the place to bind the active session id.
|
|
15368
|
+
navigateTo('chat');
|
|
15369
|
+
var indicator = document.getElementById('chat-session-indicator');
|
|
15370
|
+
if (indicator) indicator.textContent = key;
|
|
15371
|
+
toast('Switched to ' + (friendlySession(key).label || key), 'info');
|
|
15372
|
+
}
|
|
15373
|
+
|
|
14436
15374
|
async function viewSession(encodedKey) {
|
|
14437
15375
|
var key = decodeURIComponent(encodedKey);
|
|
14438
15376
|
var panel = document.getElementById('panel-sessions');
|
|
@@ -17229,6 +18167,12 @@ function updateBuilderMode() {
|
|
|
17229
18167
|
var type = (document.getElementById('builder-type') || {}).value || 'skill';
|
|
17230
18168
|
var title = document.getElementById('builder-page-title');
|
|
17231
18169
|
var drawer = document.getElementById('builder-skills-drawer');
|
|
18170
|
+
var preview = document.getElementById('builder-preview');
|
|
18171
|
+
var canvasHost = document.getElementById('builder-canvas-host');
|
|
18172
|
+
var picker = document.getElementById('builder-canvas-picker');
|
|
18173
|
+
var validateBtn = document.getElementById('builder-canvas-validate-btn');
|
|
18174
|
+
var dryrunBtn = document.getElementById('builder-canvas-dryrun-btn');
|
|
18175
|
+
var paneTitle = document.getElementById('builder-right-pane-title');
|
|
17232
18176
|
|
|
17233
18177
|
if (type === 'skill') {
|
|
17234
18178
|
if (title) title.textContent = 'Skill Studio';
|
|
@@ -17239,6 +18183,682 @@ function updateBuilderMode() {
|
|
|
17239
18183
|
if (drawer) drawer.style.display = 'none';
|
|
17240
18184
|
document.getElementById('builder-input').placeholder = 'Describe what you want to build...';
|
|
17241
18185
|
}
|
|
18186
|
+
|
|
18187
|
+
// Canvas mode for cron + workflow types
|
|
18188
|
+
var canvasMode = (type === 'cron' || type === 'workflow');
|
|
18189
|
+
var testBtn = document.getElementById('builder-canvas-test-btn');
|
|
18190
|
+
if (canvasMode) {
|
|
18191
|
+
if (preview) preview.style.display = 'none';
|
|
18192
|
+
if (canvasHost) canvasHost.style.display = 'flex';
|
|
18193
|
+
if (picker) picker.style.display = '';
|
|
18194
|
+
if (validateBtn) validateBtn.style.display = '';
|
|
18195
|
+
if (dryrunBtn) dryrunBtn.style.display = '';
|
|
18196
|
+
if (testBtn) testBtn.style.display = '';
|
|
18197
|
+
if (paneTitle) paneTitle.textContent = (type === 'cron' ? 'Crons' : 'Workflows');
|
|
18198
|
+
refreshBuilderCanvasPicker(type);
|
|
18199
|
+
} else {
|
|
18200
|
+
if (preview) preview.style.display = '';
|
|
18201
|
+
if (canvasHost) canvasHost.style.display = 'none';
|
|
18202
|
+
if (picker) picker.style.display = 'none';
|
|
18203
|
+
if (validateBtn) validateBtn.style.display = 'none';
|
|
18204
|
+
if (dryrunBtn) dryrunBtn.style.display = 'none';
|
|
18205
|
+
if (testBtn) testBtn.style.display = 'none';
|
|
18206
|
+
if (paneTitle) paneTitle.textContent = 'Live Preview';
|
|
18207
|
+
closeBuilderCanvas();
|
|
18208
|
+
}
|
|
18209
|
+
}
|
|
18210
|
+
|
|
18211
|
+
// ── Builder visual canvas (Phase 1: read-only, agent edits via MCP tools) ──
|
|
18212
|
+
|
|
18213
|
+
var _builderCanvasEditor = null;
|
|
18214
|
+
var _builderCanvasOpenId = null;
|
|
18215
|
+
var _builderCanvasLastWorkflow = null;
|
|
18216
|
+
var _builderDrawflowLoading = null;
|
|
18217
|
+
|
|
18218
|
+
function _ensureDrawflowLoaded() {
|
|
18219
|
+
if (window.Drawflow) return Promise.resolve();
|
|
18220
|
+
if (_builderDrawflowLoading) return _builderDrawflowLoading;
|
|
18221
|
+
_builderDrawflowLoading = new Promise(function(resolve, reject) {
|
|
18222
|
+
var s = document.createElement('script');
|
|
18223
|
+
s.src = '/static/drawflow.min.js';
|
|
18224
|
+
s.onload = function() { resolve(); };
|
|
18225
|
+
s.onerror = function() { reject(new Error('Failed to load Drawflow')); };
|
|
18226
|
+
document.head.appendChild(s);
|
|
18227
|
+
});
|
|
18228
|
+
return _builderDrawflowLoading;
|
|
18229
|
+
}
|
|
18230
|
+
|
|
18231
|
+
async function refreshBuilderCanvasPicker(type) {
|
|
18232
|
+
var picker = document.getElementById('builder-canvas-picker');
|
|
18233
|
+
if (!picker) return;
|
|
18234
|
+
try {
|
|
18235
|
+
var r = await apiFetch('/api/builder/workflows');
|
|
18236
|
+
var d = await r.json();
|
|
18237
|
+
var items = (d.workflows || []).filter(function(w) { return w.origin === type; });
|
|
18238
|
+
var opts = '<option value="">' + (items.length ? '— pick a ' + type + ' —' : '(none yet)') + '</option>';
|
|
18239
|
+
for (var i = 0; i < items.length; i++) {
|
|
18240
|
+
var w = items[i];
|
|
18241
|
+
var lbl = w.name + (w.schedule ? ' · ' + w.schedule : '') + (w.enabled ? '' : ' · off');
|
|
18242
|
+
opts += '<option value="' + esc(w.id) + '">' + esc(lbl) + '</option>';
|
|
18243
|
+
}
|
|
18244
|
+
picker.innerHTML = opts;
|
|
18245
|
+
if (_builderCanvasOpenId) picker.value = _builderCanvasOpenId;
|
|
18246
|
+
} catch (err) {
|
|
18247
|
+
picker.innerHTML = '<option value="">(failed to load)</option>';
|
|
18248
|
+
}
|
|
18249
|
+
}
|
|
18250
|
+
|
|
18251
|
+
async function openBuilderWorkflow(id) {
|
|
18252
|
+
if (!id) { closeBuilderCanvas(); return; }
|
|
18253
|
+
try {
|
|
18254
|
+
await _ensureDrawflowLoaded();
|
|
18255
|
+
var r = await apiFetch('/api/builder/workflows/' + encodeURIComponent(id));
|
|
18256
|
+
if (!r.ok) {
|
|
18257
|
+
var msg = await r.json().catch(function() { return {}; });
|
|
18258
|
+
toast('Failed to open: ' + (msg.error || r.status), 'error');
|
|
18259
|
+
return;
|
|
18260
|
+
}
|
|
18261
|
+
var d = await r.json();
|
|
18262
|
+
_builderCanvasOpenId = id;
|
|
18263
|
+
_builderCanvasLastWorkflow = d.workflow;
|
|
18264
|
+
_renderBuilderCanvas(d.drawflow);
|
|
18265
|
+
|
|
18266
|
+
var idEl = document.getElementById('builder-canvas-id');
|
|
18267
|
+
if (idEl) idEl.textContent = id;
|
|
18268
|
+
|
|
18269
|
+
var banner = document.getElementById('builder-canvas-banner');
|
|
18270
|
+
if (banner) {
|
|
18271
|
+
var issues = (d.validation && d.validation.issues) || [];
|
|
18272
|
+
var errors = issues.filter(function(i) { return i.severity === 'error'; });
|
|
18273
|
+
if (errors.length) {
|
|
18274
|
+
banner.style.display = '';
|
|
18275
|
+
banner.style.background = 'rgba(255,80,80,0.12)';
|
|
18276
|
+
banner.style.color = 'var(--red)';
|
|
18277
|
+
banner.textContent = errors.length + ' validation error' + (errors.length === 1 ? '' : 's') + ' — open Validate for details';
|
|
18278
|
+
} else {
|
|
18279
|
+
banner.style.display = 'none';
|
|
18280
|
+
}
|
|
18281
|
+
}
|
|
18282
|
+
} catch (err) {
|
|
18283
|
+
toast('Canvas error: ' + err, 'error');
|
|
18284
|
+
}
|
|
18285
|
+
}
|
|
18286
|
+
|
|
18287
|
+
function _renderBuilderCanvas(drawflowData) {
|
|
18288
|
+
var host = document.getElementById('builder-canvas');
|
|
18289
|
+
if (!host) return;
|
|
18290
|
+
// Tear down previous editor
|
|
18291
|
+
if (_builderCanvasEditor) {
|
|
18292
|
+
try { _builderCanvasEditor.clear(); } catch (e) { /* ignore */ }
|
|
18293
|
+
host.innerHTML = '';
|
|
18294
|
+
}
|
|
18295
|
+
var editor = new window.Drawflow(host);
|
|
18296
|
+
editor.reroute = true;
|
|
18297
|
+
editor.editor_mode = 'edit';
|
|
18298
|
+
editor.start();
|
|
18299
|
+
try {
|
|
18300
|
+
editor.import(drawflowData || { drawflow: { Home: { data: {} } } });
|
|
18301
|
+
_decorateBuilderNodes(host, _builderCanvasLastWorkflow);
|
|
18302
|
+
} catch (err) {
|
|
18303
|
+
host.innerHTML = '<div style="padding:24px;color:var(--red)">Failed to render canvas: ' + esc(String(err)) + '</div>';
|
|
18304
|
+
}
|
|
18305
|
+
_builderCanvasEditor = editor;
|
|
18306
|
+
_bindBuilderCanvasEvents(editor);
|
|
18307
|
+
}
|
|
18308
|
+
|
|
18309
|
+
var _builderSaveTimer = null;
|
|
18310
|
+
var _builderSavePending = false;
|
|
18311
|
+
var _builderRecentSaveTokens = new Set();
|
|
18312
|
+
|
|
18313
|
+
function _bindBuilderCanvasEvents(editor) {
|
|
18314
|
+
// Drawflow event names: nodeMoved, connectionCreated, connectionRemoved,
|
|
18315
|
+
// nodeDataChanged, nodeRemoved.
|
|
18316
|
+
['nodeMoved', 'connectionCreated', 'connectionRemoved', 'nodeDataChanged', 'nodeRemoved'].forEach(function(evt) {
|
|
18317
|
+
try {
|
|
18318
|
+
editor.on(evt, function() {
|
|
18319
|
+
if (evt === 'nodeMoved') _scheduleBuilderSave(800);
|
|
18320
|
+
else _scheduleBuilderSave(300);
|
|
18321
|
+
});
|
|
18322
|
+
} catch (e) { /* drawflow always exposes .on, but be safe */ }
|
|
18323
|
+
});
|
|
18324
|
+
try {
|
|
18325
|
+
editor.on('nodeSelected', function(nodeId) { _openNodeConfigPanel(nodeId); });
|
|
18326
|
+
editor.on('nodeUnselected', function() { _closeNodeConfigPanel(); });
|
|
18327
|
+
} catch (e) { /* */ }
|
|
18328
|
+
}
|
|
18329
|
+
|
|
18330
|
+
function _scheduleBuilderSave(delay) {
|
|
18331
|
+
if (_builderSaveTimer) clearTimeout(_builderSaveTimer);
|
|
18332
|
+
_builderSaveTimer = setTimeout(function() { _flushBuilderSave(); }, delay || 500);
|
|
18333
|
+
}
|
|
18334
|
+
|
|
18335
|
+
async function _flushBuilderSave() {
|
|
18336
|
+
if (!_builderCanvasEditor || !_builderCanvasOpenId) return;
|
|
18337
|
+
if (_builderSavePending) {
|
|
18338
|
+
_scheduleBuilderSave(400);
|
|
18339
|
+
return;
|
|
18340
|
+
}
|
|
18341
|
+
_builderSavePending = true;
|
|
18342
|
+
_setBuilderSaveStatus('saving');
|
|
18343
|
+
var saveToken = 'sv_' + Date.now() + '_' + Math.random().toString(36).slice(2, 8);
|
|
18344
|
+
_builderRecentSaveTokens.add(saveToken);
|
|
18345
|
+
setTimeout(function() { _builderRecentSaveTokens.delete(saveToken); }, 5000);
|
|
18346
|
+
try {
|
|
18347
|
+
var data = _builderCanvasEditor.export();
|
|
18348
|
+
var r = await apiJson('POST', '/api/builder/workflows/' + encodeURIComponent(_builderCanvasOpenId) + '/save-from-drawflow', { drawflow: data, saveToken: saveToken });
|
|
18349
|
+
if (r.error) {
|
|
18350
|
+
if (r.validation && r.validation.issues) {
|
|
18351
|
+
_setBuilderSaveStatus('error', r.validation.issues.length + ' validation error' + (r.validation.issues.length === 1 ? '' : 's'));
|
|
18352
|
+
} else {
|
|
18353
|
+
_setBuilderSaveStatus('error', r.error);
|
|
18354
|
+
}
|
|
18355
|
+
} else {
|
|
18356
|
+
_setBuilderSaveStatus('saved');
|
|
18357
|
+
// Refresh banner from validation if warnings
|
|
18358
|
+
_updateBuilderBannerFromValidation(r.validation);
|
|
18359
|
+
}
|
|
18360
|
+
} catch (err) {
|
|
18361
|
+
_setBuilderSaveStatus('error', String(err));
|
|
18362
|
+
} finally {
|
|
18363
|
+
_builderSavePending = false;
|
|
18364
|
+
}
|
|
18365
|
+
}
|
|
18366
|
+
|
|
18367
|
+
function _setBuilderSaveStatus(state, detail) {
|
|
18368
|
+
var el = document.getElementById('builder-canvas-status');
|
|
18369
|
+
if (!el) return;
|
|
18370
|
+
if (state === 'saving') { el.textContent = 'Saving…'; el.style.color = 'var(--text-muted)'; }
|
|
18371
|
+
else if (state === 'saved') { el.textContent = 'Saved'; el.style.color = 'var(--green, #4caf50)'; setTimeout(function() { if (el.textContent === 'Saved') el.textContent = ''; }, 1500); }
|
|
18372
|
+
else if (state === 'error') { el.textContent = 'Save error: ' + (detail || ''); el.style.color = 'var(--red)'; }
|
|
18373
|
+
}
|
|
18374
|
+
|
|
18375
|
+
function _updateBuilderBannerFromValidation(v) {
|
|
18376
|
+
var banner = document.getElementById('builder-canvas-banner');
|
|
18377
|
+
if (!banner || !v) return;
|
|
18378
|
+
var issues = v.issues || [];
|
|
18379
|
+
var errors = issues.filter(function(i) { return i.severity === 'error'; });
|
|
18380
|
+
var warnings = issues.filter(function(i) { return i.severity === 'warning'; });
|
|
18381
|
+
if (errors.length) {
|
|
18382
|
+
banner.style.display = '';
|
|
18383
|
+
banner.style.background = 'rgba(255,80,80,0.12)';
|
|
18384
|
+
banner.style.color = 'var(--red)';
|
|
18385
|
+
banner.textContent = errors.length + ' validation error' + (errors.length === 1 ? '' : 's') + ' — open Validate for details';
|
|
18386
|
+
} else if (warnings.length) {
|
|
18387
|
+
banner.style.display = '';
|
|
18388
|
+
banner.style.background = 'rgba(240,180,0,0.12)';
|
|
18389
|
+
banner.style.color = '#a37100';
|
|
18390
|
+
banner.textContent = warnings.length + ' warning' + (warnings.length === 1 ? '' : 's') + ' — Validate for details';
|
|
18391
|
+
} else {
|
|
18392
|
+
banner.style.display = 'none';
|
|
18393
|
+
}
|
|
18394
|
+
}
|
|
18395
|
+
|
|
18396
|
+
function _decorateBuilderNodes(host, wf) {
|
|
18397
|
+
if (!wf) return;
|
|
18398
|
+
var byStepId = {};
|
|
18399
|
+
for (var i = 0; i < wf.steps.length; i++) byStepId[wf.steps[i].id] = wf.steps[i];
|
|
18400
|
+
// Drawflow renders blank node bodies by default — overlay our own content.
|
|
18401
|
+
var nodes = host.querySelectorAll('.drawflow-node');
|
|
18402
|
+
nodes.forEach(function(nodeEl) {
|
|
18403
|
+
var contentEl = nodeEl.querySelector('.drawflow_content_node');
|
|
18404
|
+
if (!contentEl || contentEl.dataset._decorated) return;
|
|
18405
|
+
contentEl.dataset._decorated = '1';
|
|
18406
|
+
var dataAttr = nodeEl.querySelector('input[df-stepId]');
|
|
18407
|
+
var stepId = dataAttr ? dataAttr.value : null;
|
|
18408
|
+
// Drawflow doesn't auto-bind without templates — derive from class instead
|
|
18409
|
+
var classNames = (nodeEl.className || '').split(/\s+/).filter(function(c) { return c.indexOf('cl-node-') === 0; });
|
|
18410
|
+
var kind = classNames.length ? classNames[0].replace('cl-node-', '') : 'prompt';
|
|
18411
|
+
var title = '';
|
|
18412
|
+
var body = '';
|
|
18413
|
+
var matchedStep = null;
|
|
18414
|
+
for (var j = 0; j < wf.steps.length; j++) {
|
|
18415
|
+
var s = wf.steps[j];
|
|
18416
|
+
if ((s.kind || 'prompt') === kind) { matchedStep = s; break; }
|
|
18417
|
+
}
|
|
18418
|
+
if (!matchedStep) {
|
|
18419
|
+
// Best-effort: use first step
|
|
18420
|
+
matchedStep = wf.steps[0];
|
|
18421
|
+
}
|
|
18422
|
+
title = (matchedStep ? matchedStep.id : '?');
|
|
18423
|
+
body = _summarizeStep(matchedStep, kind);
|
|
18424
|
+
contentEl.innerHTML =
|
|
18425
|
+
'<div style="font-weight:600;font-size:12px;margin-bottom:4px;color:#fff;text-transform:lowercase">' + esc(kind) + ' · ' + esc(title) + '</div>' +
|
|
18426
|
+
'<div style="font-size:11px;color:rgba(255,255,255,0.85);line-height:1.35;max-height:80px;overflow:hidden">' + esc(body) + '</div>';
|
|
18427
|
+
});
|
|
18428
|
+
}
|
|
18429
|
+
|
|
18430
|
+
function _summarizeStep(step, kind) {
|
|
18431
|
+
if (!step) return '';
|
|
18432
|
+
if (kind === 'mcp' && step.mcp) return step.mcp.server + '.' + step.mcp.tool;
|
|
18433
|
+
if (kind === 'channel' && step.channel) return step.channel.channel + ' → ' + step.channel.target;
|
|
18434
|
+
if (kind === 'transform' && step.transform) return step.transform.expression;
|
|
18435
|
+
if (kind === 'conditional' && step.conditional) return 'if ' + step.conditional.condition;
|
|
18436
|
+
if (kind === 'loop' && step.loop) return 'for each ' + step.loop.items;
|
|
18437
|
+
return (step.prompt || '').slice(0, 200);
|
|
18438
|
+
}
|
|
18439
|
+
|
|
18440
|
+
function closeBuilderCanvas() {
|
|
18441
|
+
_builderCanvasOpenId = null;
|
|
18442
|
+
_builderCanvasLastWorkflow = null;
|
|
18443
|
+
if (_builderCanvasEditor) {
|
|
18444
|
+
try { _builderCanvasEditor.clear(); } catch (e) { /* ignore */ }
|
|
18445
|
+
_builderCanvasEditor = null;
|
|
18446
|
+
}
|
|
18447
|
+
var host = document.getElementById('builder-canvas');
|
|
18448
|
+
if (host) host.innerHTML = '';
|
|
18449
|
+
var idEl = document.getElementById('builder-canvas-id');
|
|
18450
|
+
if (idEl) idEl.textContent = '';
|
|
18451
|
+
var banner = document.getElementById('builder-canvas-banner');
|
|
18452
|
+
if (banner) banner.style.display = 'none';
|
|
18453
|
+
}
|
|
18454
|
+
|
|
18455
|
+
async function validateBuilderCanvas() {
|
|
18456
|
+
if (!_builderCanvasOpenId) { toast('Open a workflow first', 'info'); return; }
|
|
18457
|
+
try {
|
|
18458
|
+
var r = await apiJson('POST', '/api/builder/workflows/' + encodeURIComponent(_builderCanvasOpenId) + '/validate', {});
|
|
18459
|
+
if (!r.issues || r.issues.length === 0) { toast('OK — no issues', 'success'); return; }
|
|
18460
|
+
var lines = r.issues.map(function(i) { return '[' + i.severity + '] ' + (i.stepId ? '(' + i.stepId + ') ' : '') + i.message; });
|
|
18461
|
+
alert('Validation:\\n\\n' + lines.join('\\n'));
|
|
18462
|
+
} catch (err) { toast('Validate failed: ' + err, 'error'); }
|
|
18463
|
+
}
|
|
18464
|
+
|
|
18465
|
+
async function dryRunBuilderCanvas() {
|
|
18466
|
+
if (!_builderCanvasOpenId) { toast('Open a workflow first', 'info'); return; }
|
|
18467
|
+
try {
|
|
18468
|
+
var r = await apiJson('POST', '/api/builder/workflows/' + encodeURIComponent(_builderCanvasOpenId) + '/dry-run', {});
|
|
18469
|
+
var lines = [];
|
|
18470
|
+
lines.push(r.ok ? 'DRY RUN — would execute:' : 'DRY RUN (validation issues found):');
|
|
18471
|
+
lines.push('');
|
|
18472
|
+
for (var i = 0; i < r.steps.length; i++) {
|
|
18473
|
+
var s = r.steps[i];
|
|
18474
|
+
lines.push('[wave ' + s.wave + '] ' + s.description);
|
|
18475
|
+
for (var k = 0; k < s.warnings.length; k++) lines.push(' ⚠ ' + s.warnings[k]);
|
|
18476
|
+
}
|
|
18477
|
+
if (r.estimatedTokens) lines.push('', '~' + r.estimatedTokens.total.toLocaleString() + ' tokens estimate (' + r.estimatedTokens.promptSteps + ' prompt step' + (r.estimatedTokens.promptSteps === 1 ? '' : 's') + ')');
|
|
18478
|
+
if (r.notes && r.notes.length) { lines.push(''); for (var n = 0; n < r.notes.length; n++) lines.push(r.notes[n]); }
|
|
18479
|
+
alert(lines.join('\\n'));
|
|
18480
|
+
} catch (err) { toast('Dry-run failed: ' + err, 'error'); }
|
|
18481
|
+
}
|
|
18482
|
+
|
|
18483
|
+
function _handleBuilderEvent(evt) {
|
|
18484
|
+
if (!evt || !evt.workflowId) return;
|
|
18485
|
+
// Run events are routed to the test-run handler.
|
|
18486
|
+
if (evt.type && evt.type.indexOf && evt.type.indexOf('run:') === 0) {
|
|
18487
|
+
_onRunEvent(evt);
|
|
18488
|
+
return;
|
|
18489
|
+
}
|
|
18490
|
+
// Suppress echoes of our own saves: server reflects the saveToken back.
|
|
18491
|
+
var token = evt.payload && evt.payload.saveToken;
|
|
18492
|
+
if (token && _builderRecentSaveTokens.has(token)) {
|
|
18493
|
+
_builderRecentSaveTokens.delete(token);
|
|
18494
|
+
return;
|
|
18495
|
+
}
|
|
18496
|
+
// Re-render if event is for the open workflow.
|
|
18497
|
+
if (evt.workflowId === _builderCanvasOpenId) {
|
|
18498
|
+
if (evt.type === 'workflow:deleted') { closeBuilderCanvas(); refreshBuilderCanvasPicker(document.getElementById('builder-type').value); return; }
|
|
18499
|
+
openBuilderWorkflow(_builderCanvasOpenId);
|
|
18500
|
+
}
|
|
18501
|
+
// Refresh picker when list changes
|
|
18502
|
+
if (evt.type === 'workflow:created' || evt.type === 'workflow:deleted' || evt.type === 'workflow:renamed') {
|
|
18503
|
+
refreshBuilderCanvasPicker(document.getElementById('builder-type').value);
|
|
18504
|
+
}
|
|
18505
|
+
}
|
|
18506
|
+
|
|
18507
|
+
// ── Node palette (Phase 2b) ─────────────────────────────────────
|
|
18508
|
+
|
|
18509
|
+
var _builderPaletteOpen = false;
|
|
18510
|
+
|
|
18511
|
+
function toggleBuilderPalette() {
|
|
18512
|
+
var pop = document.getElementById('builder-palette-pop');
|
|
18513
|
+
if (!pop) return;
|
|
18514
|
+
_builderPaletteOpen = !_builderPaletteOpen;
|
|
18515
|
+
pop.style.display = _builderPaletteOpen ? '' : 'none';
|
|
18516
|
+
}
|
|
18517
|
+
|
|
18518
|
+
function _builderAddNodeOfKind(kind) {
|
|
18519
|
+
if (!_builderCanvasEditor) return;
|
|
18520
|
+
toggleBuilderPalette();
|
|
18521
|
+
var canvas = document.getElementById('builder-canvas');
|
|
18522
|
+
if (!canvas) return;
|
|
18523
|
+
var rect = canvas.getBoundingClientRect();
|
|
18524
|
+
var posX = (rect.width / 2) - 100;
|
|
18525
|
+
var posY = (rect.height / 2) - 40;
|
|
18526
|
+
var stepId = _generateUniqueStepId(kind);
|
|
18527
|
+
var data = _defaultDataForKind(kind, stepId);
|
|
18528
|
+
var className = 'cl-node cl-node-' + kind;
|
|
18529
|
+
var nodeId = _builderCanvasEditor.addNode(_nodeNameForKind(kind), 1, 1, posX, posY, className, data, '');
|
|
18530
|
+
// Drawflow needs a tick before we can decorate
|
|
18531
|
+
setTimeout(function() {
|
|
18532
|
+
_decorateBuilderNodeById(nodeId, kind, data);
|
|
18533
|
+
_scheduleBuilderSave(200);
|
|
18534
|
+
}, 50);
|
|
18535
|
+
}
|
|
18536
|
+
|
|
18537
|
+
function _nodeNameForKind(kind) {
|
|
18538
|
+
switch (kind) {
|
|
18539
|
+
case 'mcp': return 'MCP Tool';
|
|
18540
|
+
case 'channel': return 'Channel';
|
|
18541
|
+
case 'transform': return 'Transform';
|
|
18542
|
+
case 'conditional': return 'Conditional';
|
|
18543
|
+
case 'loop': return 'Loop';
|
|
18544
|
+
default: return 'Prompt';
|
|
18545
|
+
}
|
|
18546
|
+
}
|
|
18547
|
+
|
|
18548
|
+
function _defaultDataForKind(kind, stepId) {
|
|
18549
|
+
var base = { stepId: stepId, prompt: '', tier: 1, maxTurns: 15, kind: kind };
|
|
18550
|
+
if (kind === 'prompt') return Object.assign({}, base, { prompt: 'Describe what this step should do.' });
|
|
18551
|
+
if (kind === 'mcp') return Object.assign({}, base, { mcp: { server: '', tool: '', inputs: {} } });
|
|
18552
|
+
if (kind === 'channel') return Object.assign({}, base, { channel: { channel: 'slack', target: '#me', content: '' } });
|
|
18553
|
+
if (kind === 'transform') return Object.assign({}, base, { transform: { expression: 'input' } });
|
|
18554
|
+
if (kind === 'conditional') return Object.assign({}, base, { conditional: { condition: 'true', trueNext: [], falseNext: [] } });
|
|
18555
|
+
if (kind === 'loop') return Object.assign({}, base, { loop: { items: 'input', bodyStepIds: [] } });
|
|
18556
|
+
return base;
|
|
18557
|
+
}
|
|
18558
|
+
|
|
18559
|
+
function _generateUniqueStepId(kind) {
|
|
18560
|
+
var used = new Set();
|
|
18561
|
+
if (_builderCanvasEditor) {
|
|
18562
|
+
var data = _builderCanvasEditor.export();
|
|
18563
|
+
var nodes = data && data.drawflow && data.drawflow.Home && data.drawflow.Home.data ? data.drawflow.Home.data : {};
|
|
18564
|
+
for (var k in nodes) {
|
|
18565
|
+
var d = nodes[k].data || {};
|
|
18566
|
+
if (d.stepId) used.add(d.stepId);
|
|
18567
|
+
}
|
|
18568
|
+
}
|
|
18569
|
+
var prefix = kind.slice(0, 4);
|
|
18570
|
+
var i = 1;
|
|
18571
|
+
while (used.has(prefix + i)) i++;
|
|
18572
|
+
return prefix + i;
|
|
18573
|
+
}
|
|
18574
|
+
|
|
18575
|
+
function _decorateBuilderNodeById(nodeId, kind, data) {
|
|
18576
|
+
var nodeEl = document.querySelector('.drawflow-node[id="node-' + nodeId + '"]');
|
|
18577
|
+
if (!nodeEl) return;
|
|
18578
|
+
var content = nodeEl.querySelector('.drawflow_content_node');
|
|
18579
|
+
if (!content) return;
|
|
18580
|
+
content.dataset._decorated = '1';
|
|
18581
|
+
content.innerHTML =
|
|
18582
|
+
'<div style="font-weight:600;font-size:12px;margin-bottom:4px;color:#fff;text-transform:lowercase">' + esc(kind) + ' · ' + esc(data.stepId) + '</div>' +
|
|
18583
|
+
'<div style="font-size:11px;color:rgba(255,255,255,0.85);line-height:1.35;max-height:80px;overflow:hidden">' + esc(_summarizeStepData(data, kind)) + '</div>';
|
|
18584
|
+
}
|
|
18585
|
+
|
|
18586
|
+
function _summarizeStepData(d, kind) {
|
|
18587
|
+
if (!d) return '';
|
|
18588
|
+
if (kind === 'mcp' && d.mcp) return (d.mcp.server || '?') + '.' + (d.mcp.tool || '?');
|
|
18589
|
+
if (kind === 'channel' && d.channel) return d.channel.channel + ' → ' + d.channel.target;
|
|
18590
|
+
if (kind === 'transform' && d.transform) return d.transform.expression || '';
|
|
18591
|
+
if (kind === 'conditional' && d.conditional) return 'if ' + (d.conditional.condition || '');
|
|
18592
|
+
if (kind === 'loop' && d.loop) return 'for each ' + (d.loop.items || '');
|
|
18593
|
+
return (d.prompt || '').slice(0, 200);
|
|
18594
|
+
}
|
|
18595
|
+
|
|
18596
|
+
// ── Per-node config panel (Phase 2c) ───────────────────────────
|
|
18597
|
+
|
|
18598
|
+
var _builderConfigOpenNodeId = null;
|
|
18599
|
+
var _builderMcpToolsCache = null;
|
|
18600
|
+
|
|
18601
|
+
function _openNodeConfigPanel(nodeId) {
|
|
18602
|
+
if (!_builderCanvasEditor) return;
|
|
18603
|
+
_builderConfigOpenNodeId = nodeId;
|
|
18604
|
+
var node = _builderCanvasEditor.getNodeFromId(nodeId);
|
|
18605
|
+
if (!node) return;
|
|
18606
|
+
var data = node.data || {};
|
|
18607
|
+
var kind = data.kind || 'prompt';
|
|
18608
|
+
var panel = document.getElementById('builder-config-panel');
|
|
18609
|
+
if (!panel) return;
|
|
18610
|
+
panel.style.display = '';
|
|
18611
|
+
panel.innerHTML = _renderConfigPanel(nodeId, kind, data);
|
|
18612
|
+
_bindConfigPanelInputs(nodeId, kind);
|
|
18613
|
+
if (kind === 'mcp') _populateMcpDropdowns();
|
|
18614
|
+
}
|
|
18615
|
+
|
|
18616
|
+
function _closeNodeConfigPanel() {
|
|
18617
|
+
_builderConfigOpenNodeId = null;
|
|
18618
|
+
var panel = document.getElementById('builder-config-panel');
|
|
18619
|
+
if (panel) panel.style.display = 'none';
|
|
18620
|
+
}
|
|
18621
|
+
|
|
18622
|
+
function _renderConfigPanel(nodeId, kind, d) {
|
|
18623
|
+
var common = (
|
|
18624
|
+
'<div class="cfg-row"><label>Step id</label><input type="text" data-cfg="stepId" value="' + esc(d.stepId || '') + '"></div>' +
|
|
18625
|
+
'<div class="cfg-row"><label>Tier</label><input type="number" min="1" max="5" data-cfg="tier" value="' + (d.tier || 1) + '"></div>' +
|
|
18626
|
+
'<div class="cfg-row"><label>Max turns</label><input type="number" min="1" data-cfg="maxTurns" value="' + (d.maxTurns || 15) + '"></div>' +
|
|
18627
|
+
'<div class="cfg-row"><label>Model</label><input type="text" data-cfg="model" placeholder="default" value="' + esc(d.model || '') + '"></div>'
|
|
18628
|
+
);
|
|
18629
|
+
var kindHtml = '';
|
|
18630
|
+
if (kind === 'prompt') {
|
|
18631
|
+
kindHtml = '<div class="cfg-row"><label>Prompt</label><textarea data-cfg="prompt" rows="6">' + esc(d.prompt || '') + '</textarea></div>';
|
|
18632
|
+
} else if (kind === 'mcp') {
|
|
18633
|
+
var m = d.mcp || {};
|
|
18634
|
+
kindHtml = (
|
|
18635
|
+
'<div class="cfg-row"><label>MCP server</label><select data-cfg="mcp.server" id="cfg-mcp-server"><option value="' + esc(m.server || '') + '">' + esc(m.server || '— pick —') + '</option></select></div>' +
|
|
18636
|
+
'<div class="cfg-row"><label>MCP tool</label><select data-cfg="mcp.tool" id="cfg-mcp-tool"><option value="' + esc(m.tool || '') + '">' + esc(m.tool || '— pick —') + '</option></select></div>' +
|
|
18637
|
+
'<div class="cfg-row"><label>Inputs (JSON)</label><textarea data-cfg="mcp.inputs" rows="4">' + esc(JSON.stringify(m.inputs || {}, null, 2)) + '</textarea></div>' +
|
|
18638
|
+
'<div class="cfg-row"><label>Description</label><textarea data-cfg="prompt" rows="3">' + esc(d.prompt || '') + '</textarea></div>'
|
|
18639
|
+
);
|
|
18640
|
+
} else if (kind === 'channel') {
|
|
18641
|
+
var c = d.channel || {};
|
|
18642
|
+
kindHtml = (
|
|
18643
|
+
'<div class="cfg-row"><label>Channel</label><select data-cfg="channel.channel">' +
|
|
18644
|
+
['discord','slack','telegram','whatsapp','email','webhook'].map(function(k) { return '<option' + (c.channel === k ? ' selected' : '') + '>' + k + '</option>'; }).join('') +
|
|
18645
|
+
'</select></div>' +
|
|
18646
|
+
'<div class="cfg-row"><label>Target</label><input type="text" data-cfg="channel.target" placeholder="#channel, user id, email…" value="' + esc(c.target || '') + '"></div>' +
|
|
18647
|
+
'<div class="cfg-row"><label>Content</label><textarea data-cfg="channel.content" rows="6">' + esc(c.content || '') + '</textarea></div>'
|
|
18648
|
+
);
|
|
18649
|
+
} else if (kind === 'transform') {
|
|
18650
|
+
var t = d.transform || {};
|
|
18651
|
+
kindHtml = '<div class="cfg-row"><label>Expression</label><textarea data-cfg="transform.expression" rows="6" placeholder="JS expression that returns shaped data">' + esc(t.expression || '') + '</textarea></div>';
|
|
18652
|
+
} else if (kind === 'conditional') {
|
|
18653
|
+
var co = d.conditional || {};
|
|
18654
|
+
kindHtml = (
|
|
18655
|
+
'<div class="cfg-row"><label>Condition</label><textarea data-cfg="conditional.condition" rows="3" placeholder="JS expression — truthy/falsy">' + esc(co.condition || '') + '</textarea></div>' +
|
|
18656
|
+
'<div class="cfg-row"><label>True → step ids</label><input type="text" data-cfg="conditional.trueNext" placeholder="comma-separated" value="' + esc((co.trueNext || []).join(',')) + '"></div>' +
|
|
18657
|
+
'<div class="cfg-row"><label>False → step ids</label><input type="text" data-cfg="conditional.falseNext" placeholder="comma-separated" value="' + esc((co.falseNext || []).join(',')) + '"></div>'
|
|
18658
|
+
);
|
|
18659
|
+
} else if (kind === 'loop') {
|
|
18660
|
+
var l = d.loop || {};
|
|
18661
|
+
kindHtml = (
|
|
18662
|
+
'<div class="cfg-row"><label>Items</label><input type="text" data-cfg="loop.items" placeholder="JS expression returning iterable" value="' + esc(l.items || '') + '"></div>' +
|
|
18663
|
+
'<div class="cfg-row"><label>Body step ids</label><input type="text" data-cfg="loop.bodyStepIds" placeholder="comma-separated" value="' + esc((l.bodyStepIds || []).join(',')) + '"></div>'
|
|
18664
|
+
);
|
|
18665
|
+
}
|
|
18666
|
+
var lastRun = _builderRunStepResults && _builderRunStepResults[d.stepId || ''];
|
|
18667
|
+
var lastRunBtn = lastRun
|
|
18668
|
+
? '<button onclick="showStepOutput(\\x27' + esc(d.stepId || '') + '\\x27)" style="margin-top:4px;background:var(--bg-tertiary);border:1px solid var(--border);color:var(--text-secondary);padding:4px 10px;border-radius:4px;cursor:pointer;font-size:11px;width:100%">Show last run output (' + esc(lastRun.status || '') + ')</button>'
|
|
18669
|
+
: '';
|
|
18670
|
+
return (
|
|
18671
|
+
'<div style="display:flex;align-items:center;gap:8px;padding:10px 14px;border-bottom:1px solid var(--border);font-weight:600;font-size:12px">' +
|
|
18672
|
+
'<span>' + esc(kind) + ' step</span>' +
|
|
18673
|
+
'<span style="flex:1"></span>' +
|
|
18674
|
+
'<button onclick="_deleteSelectedNode()" title="Delete this node" style="background:none;border:none;color:var(--red);cursor:pointer;font-size:16px">×</button>' +
|
|
18675
|
+
'</div>' +
|
|
18676
|
+
'<div style="padding:10px 14px;flex:1;overflow-y:auto" id="builder-config-fields">' +
|
|
18677
|
+
common +
|
|
18678
|
+
'<div style="border-top:1px dashed var(--border);margin:10px 0"></div>' +
|
|
18679
|
+
kindHtml +
|
|
18680
|
+
(lastRunBtn ? '<div style="border-top:1px dashed var(--border);margin:14px 0 8px"></div>' + lastRunBtn : '') +
|
|
18681
|
+
'</div>'
|
|
18682
|
+
);
|
|
18683
|
+
}
|
|
18684
|
+
|
|
18685
|
+
function _bindConfigPanelInputs(nodeId) {
|
|
18686
|
+
var panel = document.getElementById('builder-config-panel');
|
|
18687
|
+
if (!panel) return;
|
|
18688
|
+
panel.querySelectorAll('[data-cfg]').forEach(function(el) {
|
|
18689
|
+
el.addEventListener('change', function() { _applyConfigField(nodeId, el); });
|
|
18690
|
+
if (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT') {
|
|
18691
|
+
el.addEventListener('blur', function() { _applyConfigField(nodeId, el); });
|
|
18692
|
+
}
|
|
18693
|
+
});
|
|
18694
|
+
}
|
|
18695
|
+
|
|
18696
|
+
function _applyConfigField(nodeId, el) {
|
|
18697
|
+
if (!_builderCanvasEditor) return;
|
|
18698
|
+
var node = _builderCanvasEditor.getNodeFromId(nodeId);
|
|
18699
|
+
if (!node) return;
|
|
18700
|
+
var pathStr = el.getAttribute('data-cfg') || '';
|
|
18701
|
+
var pathParts = pathStr.split('.');
|
|
18702
|
+
var raw = el.value;
|
|
18703
|
+
var parsed;
|
|
18704
|
+
if (pathStr.endsWith('.inputs')) {
|
|
18705
|
+
try { parsed = raw ? JSON.parse(raw) : {}; } catch (e) { toast('Invalid JSON in inputs', 'error'); return; }
|
|
18706
|
+
} else if (pathStr === 'tier' || pathStr === 'maxTurns') {
|
|
18707
|
+
parsed = Number(raw) || 0;
|
|
18708
|
+
} else if (pathStr === 'conditional.trueNext' || pathStr === 'conditional.falseNext' || pathStr === 'loop.bodyStepIds') {
|
|
18709
|
+
parsed = raw ? raw.split(',').map(function(s) { return s.trim(); }).filter(Boolean) : [];
|
|
18710
|
+
} else {
|
|
18711
|
+
parsed = raw;
|
|
18712
|
+
}
|
|
18713
|
+
var newData = JSON.parse(JSON.stringify(node.data || {}));
|
|
18714
|
+
var cur = newData;
|
|
18715
|
+
for (var i = 0; i < pathParts.length - 1; i++) {
|
|
18716
|
+
if (!cur[pathParts[i]] || typeof cur[pathParts[i]] !== 'object') cur[pathParts[i]] = {};
|
|
18717
|
+
cur = cur[pathParts[i]];
|
|
18718
|
+
}
|
|
18719
|
+
cur[pathParts[pathParts.length - 1]] = parsed;
|
|
18720
|
+
_builderCanvasEditor.updateNodeDataFromId(nodeId, newData);
|
|
18721
|
+
// Re-decorate the node visually
|
|
18722
|
+
_decorateBuilderNodeById(nodeId, newData.kind || 'prompt', newData);
|
|
18723
|
+
_scheduleBuilderSave(300);
|
|
18724
|
+
}
|
|
18725
|
+
|
|
18726
|
+
function _deleteSelectedNode() {
|
|
18727
|
+
if (!_builderCanvasEditor || !_builderConfigOpenNodeId) return;
|
|
18728
|
+
if (!confirm('Delete this node?')) return;
|
|
18729
|
+
_builderCanvasEditor.removeNodeId('node-' + _builderConfigOpenNodeId);
|
|
18730
|
+
_closeNodeConfigPanel();
|
|
18731
|
+
_scheduleBuilderSave(150);
|
|
18732
|
+
}
|
|
18733
|
+
|
|
18734
|
+
// ── Test runs (Phase 2d/2e) ─────────────────────────────────────
|
|
18735
|
+
|
|
18736
|
+
var _builderActiveRunId = null;
|
|
18737
|
+
var _builderRunStepResults = {};
|
|
18738
|
+
|
|
18739
|
+
async function testBuilderCanvas() {
|
|
18740
|
+
if (!_builderCanvasOpenId) { toast('Open a workflow first', 'info'); return; }
|
|
18741
|
+
if (_builderActiveRunId) { toast('A test is already running', 'info'); return; }
|
|
18742
|
+
// Always flush any pending save so the test sees the latest graph
|
|
18743
|
+
if (_builderSaveTimer) { clearTimeout(_builderSaveTimer); _builderSaveTimer = null; await _flushBuilderSave(); }
|
|
18744
|
+
_clearRunVisualState();
|
|
18745
|
+
try {
|
|
18746
|
+
var r = await apiJson('POST', '/api/builder/workflows/' + encodeURIComponent(_builderCanvasOpenId) + '/test', { mode: 'mock' });
|
|
18747
|
+
if (r.error) { toast(r.error, 'error'); return; }
|
|
18748
|
+
_builderActiveRunId = r.runId;
|
|
18749
|
+
_builderRunStepResults = {};
|
|
18750
|
+
var cancel = document.getElementById('builder-canvas-cancel-btn');
|
|
18751
|
+
if (cancel) cancel.style.display = '';
|
|
18752
|
+
_setBuilderSaveStatus('saved'); // override status with a more useful one below
|
|
18753
|
+
_setRunFooter('Running test… (' + r.runId.slice(0, 8) + ')');
|
|
18754
|
+
} catch (err) {
|
|
18755
|
+
toast('Test failed to start: ' + err, 'error');
|
|
18756
|
+
}
|
|
18757
|
+
}
|
|
18758
|
+
|
|
18759
|
+
async function cancelBuilderTest() {
|
|
18760
|
+
if (!_builderActiveRunId) return;
|
|
18761
|
+
try {
|
|
18762
|
+
await apiJson('POST', '/api/builder/runs/' + encodeURIComponent(_builderActiveRunId) + '/cancel', {});
|
|
18763
|
+
} catch (err) { /* SSE will still report the cancellation */ }
|
|
18764
|
+
}
|
|
18765
|
+
|
|
18766
|
+
function _clearRunVisualState() {
|
|
18767
|
+
document.querySelectorAll('#builder-canvas .drawflow-node').forEach(function(n) {
|
|
18768
|
+
n.classList.remove('cl-step-running', 'cl-step-done', 'cl-step-failed', 'cl-step-skipped', 'cl-step-cancelled', 'cl-step-timeout');
|
|
18769
|
+
});
|
|
18770
|
+
}
|
|
18771
|
+
|
|
18772
|
+
function _setRunFooter(text) {
|
|
18773
|
+
var el = document.getElementById('builder-canvas-status');
|
|
18774
|
+
if (el) { el.textContent = text || ''; el.style.color = 'var(--text-muted)'; }
|
|
18775
|
+
}
|
|
18776
|
+
|
|
18777
|
+
function _findNodeElForStepId(stepId) {
|
|
18778
|
+
if (!_builderCanvasEditor) return null;
|
|
18779
|
+
var data = _builderCanvasEditor.export();
|
|
18780
|
+
var nodes = data && data.drawflow && data.drawflow.Home && data.drawflow.Home.data ? data.drawflow.Home.data : {};
|
|
18781
|
+
for (var k in nodes) {
|
|
18782
|
+
var d = nodes[k].data || {};
|
|
18783
|
+
if (d.stepId === stepId) return document.querySelector('.drawflow-node[id="node-' + k + '"]');
|
|
18784
|
+
}
|
|
18785
|
+
return null;
|
|
18786
|
+
}
|
|
18787
|
+
|
|
18788
|
+
function _onRunEvent(evt) {
|
|
18789
|
+
if (!evt || !evt.runId || evt.workflowId !== _builderCanvasOpenId) return;
|
|
18790
|
+
if (evt.type === 'run:started') {
|
|
18791
|
+
_setRunFooter('Running test… (' + evt.runId.slice(0, 8) + ')');
|
|
18792
|
+
return;
|
|
18793
|
+
}
|
|
18794
|
+
if (evt.type === 'run:step-status') {
|
|
18795
|
+
var p = evt.payload || {};
|
|
18796
|
+
var nodeEl = _findNodeElForStepId(p.stepId);
|
|
18797
|
+
if (nodeEl) {
|
|
18798
|
+
nodeEl.classList.remove('cl-step-running', 'cl-step-done', 'cl-step-failed', 'cl-step-skipped', 'cl-step-cancelled', 'cl-step-timeout');
|
|
18799
|
+
nodeEl.classList.add('cl-step-' + p.status);
|
|
18800
|
+
}
|
|
18801
|
+
return;
|
|
18802
|
+
}
|
|
18803
|
+
if (evt.type === 'run:step-output') {
|
|
18804
|
+
var po = evt.payload || {};
|
|
18805
|
+
_builderRunStepResults[po.stepId] = po;
|
|
18806
|
+
return;
|
|
18807
|
+
}
|
|
18808
|
+
if (evt.type === 'run:completed' || evt.type === 'run:cancelled') {
|
|
18809
|
+
var ec = (evt.payload && evt.payload.status) || (evt.type === 'run:cancelled' ? 'cancelled' : 'ok');
|
|
18810
|
+
var dur = (evt.payload && evt.payload.durationMs) || 0;
|
|
18811
|
+
_builderActiveRunId = null;
|
|
18812
|
+
var cancel = document.getElementById('builder-canvas-cancel-btn');
|
|
18813
|
+
if (cancel) cancel.style.display = 'none';
|
|
18814
|
+
_setRunFooter('Test ' + ec + ' in ' + dur + 'ms — click a node for output');
|
|
18815
|
+
return;
|
|
18816
|
+
}
|
|
18817
|
+
}
|
|
18818
|
+
|
|
18819
|
+
function showStepOutput(stepId) {
|
|
18820
|
+
var po = _builderRunStepResults[stepId];
|
|
18821
|
+
if (!po) { toast('No output for ' + stepId + ' yet', 'info'); return; }
|
|
18822
|
+
var lines = ['Step: ' + stepId, 'Status: ' + po.status, ''];
|
|
18823
|
+
if (po.error) lines.push('Error: ' + po.error, '');
|
|
18824
|
+
if (po.output != null) lines.push(typeof po.output === 'string' ? po.output : JSON.stringify(po.output, null, 2));
|
|
18825
|
+
alert(lines.join('\\n'));
|
|
18826
|
+
}
|
|
18827
|
+
|
|
18828
|
+
async function _populateMcpDropdowns() {
|
|
18829
|
+
try {
|
|
18830
|
+
if (!_builderMcpToolsCache) {
|
|
18831
|
+
// Fetch from the agent-side discovery — proxied through a simple endpoint
|
|
18832
|
+
// (we build a small client-side bridge by listing available servers via /api/builder/mcp-discovery)
|
|
18833
|
+
var r = await apiFetch('/api/builder/mcp-discovery');
|
|
18834
|
+
_builderMcpToolsCache = await r.json();
|
|
18835
|
+
}
|
|
18836
|
+
var serverSel = document.getElementById('cfg-mcp-server');
|
|
18837
|
+
var toolSel = document.getElementById('cfg-mcp-tool');
|
|
18838
|
+
if (!serverSel || !toolSel) return;
|
|
18839
|
+
var current = serverSel.value;
|
|
18840
|
+
var opts = '<option value="">— pick —</option>';
|
|
18841
|
+
var servers = (_builderMcpToolsCache && _builderMcpToolsCache.servers) || [];
|
|
18842
|
+
for (var i = 0; i < servers.length; i++) {
|
|
18843
|
+
var s = servers[i];
|
|
18844
|
+
opts += '<option value="' + esc(s.name) + '"' + (s.name === current ? ' selected' : '') + '>' + esc(s.name) + (s.enabled ? '' : ' (off)') + '</option>';
|
|
18845
|
+
}
|
|
18846
|
+
serverSel.innerHTML = opts;
|
|
18847
|
+
var rebuildTools = function() {
|
|
18848
|
+
var s = servers.filter(function(x) { return x.name === serverSel.value; })[0];
|
|
18849
|
+
var tools = (s && s.tools) || [];
|
|
18850
|
+
var curT = toolSel.value;
|
|
18851
|
+
var t = '<option value="">— pick —</option>';
|
|
18852
|
+
for (var j = 0; j < tools.length; j++) {
|
|
18853
|
+
t += '<option value="' + esc(tools[j]) + '"' + (tools[j] === curT ? ' selected' : '') + '>' + esc(tools[j]) + '</option>';
|
|
18854
|
+
}
|
|
18855
|
+
toolSel.innerHTML = t;
|
|
18856
|
+
};
|
|
18857
|
+
rebuildTools();
|
|
18858
|
+
serverSel.addEventListener('change', rebuildTools);
|
|
18859
|
+
} catch (err) {
|
|
18860
|
+
/* non-fatal — dropdowns just stay sparse */
|
|
18861
|
+
}
|
|
17242
18862
|
}
|
|
17243
18863
|
|
|
17244
18864
|
async function refreshBuilderSkills() {
|
|
@@ -17840,6 +19460,76 @@ function formatBytes(n) {
|
|
|
17840
19460
|
return (n / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
17841
19461
|
}
|
|
17842
19462
|
|
|
19463
|
+
async function memoryHealthAction(action) {
|
|
19464
|
+
var labels = { 'janitor': 'cleanup', 'rebuild-fts': 'FTS rebuild', 'fix-orphans': 'orphan fix' };
|
|
19465
|
+
if (!confirm('Run ' + (labels[action] || action) + ' now?')) return;
|
|
19466
|
+
try {
|
|
19467
|
+
var r = await apiJson('POST', '/api/memory/health/action', { action: action });
|
|
19468
|
+
if (r.error) { toast('Action failed: ' + r.error, 'error'); return; }
|
|
19469
|
+
var detail = '';
|
|
19470
|
+
if (r.result) detail = ' — ' + Object.entries(r.result).slice(0, 4).map(function(p) { return p[0] + ':' + p[1]; }).join(', ');
|
|
19471
|
+
if (r.report) detail = ' — orphans nulled: ' + (r.report.orphanRefsNulled || 0) + ', FTS rebuilds: ' + (r.report.ftsRebuilds || 0);
|
|
19472
|
+
toast(action + ' complete' + detail, 'success');
|
|
19473
|
+
refreshMemoryHealth();
|
|
19474
|
+
} catch (err) {
|
|
19475
|
+
toast('Action error: ' + err, 'error');
|
|
19476
|
+
}
|
|
19477
|
+
}
|
|
19478
|
+
|
|
19479
|
+
// ── Goals: inline create form ────────────────────────────────────
|
|
19480
|
+
function openNewGoalForm() {
|
|
19481
|
+
var el = document.getElementById('new-goal-form');
|
|
19482
|
+
if (!el) return;
|
|
19483
|
+
el.style.display = '';
|
|
19484
|
+
setTimeout(function() {
|
|
19485
|
+
var t = document.getElementById('new-goal-title');
|
|
19486
|
+
if (t) t.focus();
|
|
19487
|
+
}, 50);
|
|
19488
|
+
}
|
|
19489
|
+
|
|
19490
|
+
async function submitNewGoal() {
|
|
19491
|
+
var titleEl = document.getElementById('new-goal-title');
|
|
19492
|
+
var descEl = document.getElementById('new-goal-desc');
|
|
19493
|
+
var title = (titleEl?.value || '').trim();
|
|
19494
|
+
if (!title) { toast('Goal needs a title', 'error'); return; }
|
|
19495
|
+
try {
|
|
19496
|
+
var r = await apiJson('POST', '/api/goals', { title: title, description: (descEl?.value || '').trim() });
|
|
19497
|
+
if (r && r.error) { toast('Create failed: ' + r.error, 'error'); return; }
|
|
19498
|
+
if (titleEl) titleEl.value = '';
|
|
19499
|
+
if (descEl) descEl.value = '';
|
|
19500
|
+
document.getElementById('new-goal-form').style.display = 'none';
|
|
19501
|
+
toast('Goal created', 'success');
|
|
19502
|
+
if (typeof refreshGoals === 'function') refreshGoals();
|
|
19503
|
+
} catch (err) { toast('Create error: ' + err, 'error'); }
|
|
19504
|
+
}
|
|
19505
|
+
|
|
19506
|
+
// ── Workflows: open Builder for a brand-new workflow ─────────────
|
|
19507
|
+
function openBuilderForNewWorkflow() {
|
|
19508
|
+
navigateTo('builder');
|
|
19509
|
+
setTimeout(function() {
|
|
19510
|
+
var typeSel = document.getElementById('builder-type');
|
|
19511
|
+
if (typeSel) { typeSel.value = 'workflow'; updateBuilderMode(); }
|
|
19512
|
+
var name = prompt('Name your new workflow:');
|
|
19513
|
+
if (!name) return;
|
|
19514
|
+
apiJson('POST', '/api/builder/workflows', { name: name }).then(function(r) {
|
|
19515
|
+
if (r && r.error) { toast('Create failed: ' + r.error, 'error'); return; }
|
|
19516
|
+
if (r && r.id) {
|
|
19517
|
+
// Refresh picker, then open the new workflow
|
|
19518
|
+
refreshBuilderCanvasPicker('workflow');
|
|
19519
|
+
setTimeout(function() { openBuilderWorkflow(r.id); }, 200);
|
|
19520
|
+
}
|
|
19521
|
+
});
|
|
19522
|
+
}, 100);
|
|
19523
|
+
}
|
|
19524
|
+
|
|
19525
|
+
// ── Unleashed: open the start-task picker ────────────────────────
|
|
19526
|
+
function openStartUnleashedTask() {
|
|
19527
|
+
// Reuse the existing cron list — pick a cron job, run it in unleashed mode.
|
|
19528
|
+
// For v1 just route to Automations where the existing controls live.
|
|
19529
|
+
navigateTo('automations');
|
|
19530
|
+
toast('Pick a cron job and click "Run unleashed" — kicks off long-running mode.', 'info');
|
|
19531
|
+
}
|
|
19532
|
+
|
|
17843
19533
|
async function refreshMemoryHealth() {
|
|
17844
19534
|
var el = document.getElementById('memory-health-content');
|
|
17845
19535
|
if (!el) return;
|
|
@@ -18555,6 +20245,131 @@ async function saveDiscordSetup() {
|
|
|
18555
20245
|
} catch(e) { toast(String(e), 'error'); }
|
|
18556
20246
|
}
|
|
18557
20247
|
|
|
20248
|
+
function toggleHomeRail() {
|
|
20249
|
+
var rail = document.getElementById('home-rail');
|
|
20250
|
+
if (!rail) return;
|
|
20251
|
+
// Mobile: open/close. Desktop: collapse/show.
|
|
20252
|
+
if (window.matchMedia('(max-width: 1024px)').matches) {
|
|
20253
|
+
rail.classList.toggle('open');
|
|
20254
|
+
} else {
|
|
20255
|
+
rail.classList.toggle('collapsed');
|
|
20256
|
+
}
|
|
20257
|
+
}
|
|
20258
|
+
|
|
20259
|
+
async function refreshHomeRail() {
|
|
20260
|
+
// Daemon status
|
|
20261
|
+
try {
|
|
20262
|
+
var rs = await apiFetch('/api/status');
|
|
20263
|
+
var ds = await rs.json();
|
|
20264
|
+
var pip = document.querySelector('#rail-daemon-body .agent-activity-dot');
|
|
20265
|
+
var label = document.querySelector('#rail-daemon-body .agent-activity span:last-child');
|
|
20266
|
+
if (label) label.textContent = ds.running ? 'Daemon running' : 'Daemon stopped';
|
|
20267
|
+
if (pip) pip.style.background = ds.running ? '#22c55e' : '#ef4444';
|
|
20268
|
+
var up = document.getElementById('rail-daemon-uptime');
|
|
20269
|
+
if (up && ds.uptimeMs) up.textContent = Math.round(ds.uptimeMs / 60000) + 'm';
|
|
20270
|
+
} catch { /* */ }
|
|
20271
|
+
|
|
20272
|
+
// Today's plan (compact)
|
|
20273
|
+
try {
|
|
20274
|
+
var rp = await apiFetch('/api/daily-plan');
|
|
20275
|
+
var dp = await rp.json();
|
|
20276
|
+
var planEl = document.getElementById('home-plan-content');
|
|
20277
|
+
if (planEl) {
|
|
20278
|
+
if (!dp || !dp.plan) {
|
|
20279
|
+
planEl.innerHTML = '<div style="font-size:12px;color:var(--text-muted)">No plan yet today.</div>';
|
|
20280
|
+
} else {
|
|
20281
|
+
var items = (dp.plan.items || []).slice(0, 4);
|
|
20282
|
+
if (items.length === 0) {
|
|
20283
|
+
planEl.innerHTML = '<div style="font-size:12px;color:var(--text-muted)">No items in today\\x27s plan.</div>';
|
|
20284
|
+
} else {
|
|
20285
|
+
planEl.innerHTML = items.map(function(it) {
|
|
20286
|
+
return '<div class="rail-row"><span class="label">' + esc(it.title || it.text || '') + '</span><span class="meta">' + esc(it.time || '') + '</span></div>';
|
|
20287
|
+
}).join('');
|
|
20288
|
+
}
|
|
20289
|
+
}
|
|
20290
|
+
}
|
|
20291
|
+
} catch {
|
|
20292
|
+
var pe = document.getElementById('home-plan-content');
|
|
20293
|
+
if (pe) pe.innerHTML = '<div style="font-size:11px;color:var(--text-muted)">No plan available.</div>';
|
|
20294
|
+
}
|
|
20295
|
+
|
|
20296
|
+
// Upcoming cron fires (next 3)
|
|
20297
|
+
try {
|
|
20298
|
+
var rc = await apiFetch('/api/cron');
|
|
20299
|
+
var dc = await rc.json();
|
|
20300
|
+
var jobs = (dc.jobs || []).filter(function(j) { return j.enabled && j.nextRun; });
|
|
20301
|
+
jobs.sort(function(a, b) { return new Date(a.nextRun).getTime() - new Date(b.nextRun).getTime(); });
|
|
20302
|
+
var top = jobs.slice(0, 3);
|
|
20303
|
+
var ue = document.getElementById('rail-upcoming');
|
|
20304
|
+
var uc = document.getElementById('rail-upcoming-count');
|
|
20305
|
+
if (uc) uc.textContent = String(jobs.length);
|
|
20306
|
+
if (ue) {
|
|
20307
|
+
ue.innerHTML = top.length ? top.map(function(j) {
|
|
20308
|
+
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>';
|
|
20309
|
+
}).join('') : '<div style="font-size:11px;color:var(--text-muted)">Nothing scheduled soon.</div>';
|
|
20310
|
+
}
|
|
20311
|
+
} catch { /* */ }
|
|
20312
|
+
|
|
20313
|
+
// Active unleashed runs
|
|
20314
|
+
try {
|
|
20315
|
+
var ru = await apiFetch('/api/unleashed');
|
|
20316
|
+
var du = await ru.json();
|
|
20317
|
+
var active = (du.tasks || []).filter(function(t) { return t.status === 'running'; });
|
|
20318
|
+
var ae = document.getElementById('rail-active');
|
|
20319
|
+
var ac = document.getElementById('rail-active-count');
|
|
20320
|
+
if (ac) {
|
|
20321
|
+
if (active.length > 0) { ac.style.display = ''; ac.textContent = String(active.length); }
|
|
20322
|
+
else ac.style.display = 'none';
|
|
20323
|
+
}
|
|
20324
|
+
if (ae) {
|
|
20325
|
+
ae.innerHTML = active.length ? active.map(function(t) {
|
|
20326
|
+
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>';
|
|
20327
|
+
}).join('') : '<div style="font-size:11px;color:var(--text-muted)">Nothing running.</div>';
|
|
20328
|
+
}
|
|
20329
|
+
} catch { /* */ }
|
|
20330
|
+
|
|
20331
|
+
// Time saved (rough: cron runs * 5min + activity exchanges * 2min, this week)
|
|
20332
|
+
try {
|
|
20333
|
+
var rm = await apiFetch('/api/metrics?period=week');
|
|
20334
|
+
var dm = await rm.json();
|
|
20335
|
+
var minutes = ((dm.cronRuns || 0) * 5) + ((dm.exchanges || 0) * 2);
|
|
20336
|
+
var ts = document.getElementById('rail-time-saved');
|
|
20337
|
+
if (ts) {
|
|
20338
|
+
if (minutes >= 60) ts.innerHTML = '<div style="font-size:18px;font-weight:600">' + (minutes / 60).toFixed(1) + 'h</div><div style="font-size:11px;color:var(--text-muted)">across ' + (dm.cronRuns || 0) + ' cron runs + ' + (dm.exchanges || 0) + ' chats</div>';
|
|
20339
|
+
else ts.innerHTML = '<div style="font-size:18px;font-weight:600">' + minutes + 'm</div><div style="font-size:11px;color:var(--text-muted)">across ' + (dm.cronRuns || 0) + ' cron runs</div>';
|
|
20340
|
+
}
|
|
20341
|
+
} catch { /* */ }
|
|
20342
|
+
|
|
20343
|
+
// Approvals (self-improve proposals + pending skills)
|
|
20344
|
+
try {
|
|
20345
|
+
var rsi = await apiFetch('/api/self-improve');
|
|
20346
|
+
var dsi = await rsi.json();
|
|
20347
|
+
var pending = (dsi.proposals || []).filter(function(p) { return p.status === 'pending'; });
|
|
20348
|
+
var ae2 = document.getElementById('rail-approvals');
|
|
20349
|
+
var ac2 = document.getElementById('rail-approvals-count');
|
|
20350
|
+
if (ac2) {
|
|
20351
|
+
if (pending.length > 0) { ac2.style.display = ''; ac2.textContent = String(pending.length); }
|
|
20352
|
+
else ac2.style.display = 'none';
|
|
20353
|
+
}
|
|
20354
|
+
if (ae2) {
|
|
20355
|
+
if (pending.length === 0) ae2.innerHTML = '<div style="font-size:11px;color:var(--text-muted)">Nothing pending.</div>';
|
|
20356
|
+
else ae2.innerHTML = pending.slice(0, 3).map(function(p) {
|
|
20357
|
+
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>';
|
|
20358
|
+
}).join('');
|
|
20359
|
+
}
|
|
20360
|
+
} catch { /* */ }
|
|
20361
|
+
}
|
|
20362
|
+
|
|
20363
|
+
function timeUntil(iso) {
|
|
20364
|
+
if (!iso) return '';
|
|
20365
|
+
var ms = new Date(iso).getTime() - Date.now();
|
|
20366
|
+
if (ms < 0) return 'past';
|
|
20367
|
+
var min = Math.round(ms / 60000);
|
|
20368
|
+
if (min < 60) return 'in ' + min + 'm';
|
|
20369
|
+
if (min < 24 * 60) return 'in ' + Math.round(min / 60) + 'h';
|
|
20370
|
+
return 'in ' + Math.round(min / (24 * 60)) + 'd';
|
|
20371
|
+
}
|
|
20372
|
+
|
|
18558
20373
|
async function refreshAll() {
|
|
18559
20374
|
// Use batch init for core data — avoids concurrent requests that freeze the event loop
|
|
18560
20375
|
try {
|
|
@@ -18563,6 +20378,8 @@ async function refreshAll() {
|
|
|
18563
20378
|
if (d.status) refreshStatus(d.status);
|
|
18564
20379
|
if (d.activity) refreshActivity(false, d.activity);
|
|
18565
20380
|
if (d.office) refreshTeamNav(d.office);
|
|
20381
|
+
// Home rail data — fire and forget, doesn't block init render.
|
|
20382
|
+
if (currentPage === 'home') refreshHomeRail();
|
|
18566
20383
|
if (d.version) {
|
|
18567
20384
|
if (d.version.needsRestart && !_restartBannerShown) {
|
|
18568
20385
|
_restartBannerShown = true;
|
|
@@ -21932,6 +23749,9 @@ try {
|
|
|
21932
23749
|
toast('Daemon restarted \u2014 refreshing data...', 'info');
|
|
21933
23750
|
setTimeout(function() { refreshAll(); }, 1500);
|
|
21934
23751
|
}
|
|
23752
|
+
if (evt.type === 'builder') {
|
|
23753
|
+
try { _handleBuilderEvent(evt.data); } catch(e) { /* non-fatal */ }
|
|
23754
|
+
}
|
|
21935
23755
|
if (evt.type === 'deep_result') {
|
|
21936
23756
|
try {
|
|
21937
23757
|
var container = document.getElementById('chat-messages');
|