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.
@@ -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
- /* ── Cards ──────────────────────────────── */
8094
- .card {
8095
- background: var(--bg-card);
8096
- backdrop-filter: blur(8px);
8097
- border: 1px solid var(--border);
8098
- border-radius: var(--radius);
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: space-between;
8109
- font-size: 13px;
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
- .card-body {
8114
- padding: 18px;
8415
+ .page-head .title-block .desc {
8115
8416
  font-size: 13px;
8116
- line-height: 1.7;
8117
- }
8118
- .grid-2 {
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
- .gs-header {
8421
+ .page-head .actions {
8134
8422
  display: flex;
8423
+ gap: 8px;
8135
8424
  align-items: center;
8136
- gap: 12px;
8137
- margin-bottom: 20px;
8425
+ flex-wrap: wrap;
8138
8426
  }
8139
- .gs-title {
8140
- font-size: 18px;
8141
- font-weight: 700;
8142
- color: var(--text-primary);
8427
+ .page-section {
8428
+ padding: 0 22px 22px;
8143
8429
  }
8144
- .gs-subtitle {
8145
- font-size: 13px;
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
- .gs-dismiss {
8150
- position: absolute;
8151
- top: 12px;
8152
- right: 12px;
8153
- font-size: 18px;
8436
+ .empty-cta .icon {
8437
+ font-size: 36px;
8438
+ margin-bottom: 12px;
8439
+ opacity: 0.5;
8154
8440
  }
8155
- .gs-grid {
8156
- display: grid;
8157
- grid-template-columns: repeat(5, 1fr);
8158
- gap: 16px;
8441
+ .empty-cta .label {
8442
+ font-size: 14px;
8443
+ margin-bottom: 4px;
8444
+ color: var(--text-secondary);
8445
+ font-weight: 500;
8159
8446
  }
8160
- .gs-card {
8161
- background: var(--bg-card);
8162
- border: 1px solid var(--border);
8163
- border-radius: var(--radius);
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
- .gs-card:hover {
8174
- transform: translateY(-2px);
8175
- box-shadow: var(--shadow-sm);
8452
+ /* ── Skeleton shimmer for loading lists ── */
8453
+ .skel-block {
8454
+ padding: 12px 18px;
8176
8455
  }
8177
- .gs-card.gs-done {
8178
- opacity: 0.5;
8179
- border-color: var(--green);
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
- .gs-card.gs-done::after {
8182
- content: '\\2713';
8183
- position: absolute;
8184
- top: 8px;
8185
- right: 10px;
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
- .gs-step-num {
8191
- width: 24px;
8192
- height: 24px;
8193
- border-radius: 50%;
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
- .gs-card-icon {
8204
- font-size: 28px;
8205
- line-height: 1;
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
- .gs-card-title {
8208
- font-size: 14px;
8209
- font-weight: 600;
8486
+ .row-actions button:hover {
8487
+ background: var(--bg-hover);
8488
+ border-color: var(--border);
8210
8489
  color: var(--text-primary);
8211
8490
  }
8212
- .gs-card-desc {
8213
- font-size: 12px;
8214
- color: var(--text-muted);
8215
- line-height: 1.4;
8216
- margin-bottom: 4px;
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
- @media (max-width: 768px) {
8222
- .gs-grid { grid-template-columns: 1fr 1fr; }
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
- #page-chat.active, #page-builder.active {
10013
- display: flex !important;
10014
- flex-direction: column;
10015
- height: calc(100vh - var(--header-h));
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
- /* Chat: full height + mobile-friendly input */
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-section-title">Today</div>
10460
- <div class="nav-item active" data-page="home">
10461
- <span class="nav-icon">&#9679;</span> Home
10462
- </div>
10463
- <div class="nav-item" data-page="chat">
10464
- <span class="nav-icon">&#128172;</span> Chat
10465
- </div>
10466
- <div class="nav-item" data-page="daily-plan">
10467
- <span class="nav-icon">&#128197;</span> Daily Plan
10468
- </div>
10469
- <div class="nav-item" data-page="goals">
10470
- <span class="nav-icon">&#127919;</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">&#129504;</span> Brain
11139
+ <div class="nav-item active" data-page="home" title="Chat, today, activity">
11140
+ <span class="nav-icon">&#127819;</span> Home
10477
11141
  </div>
10478
- </div>
10479
- <div class="nav-section">
10480
- <div class="nav-section-title">Automate</div>
10481
- <div class="nav-item" data-page="automations">
10482
- <span class="nav-icon">&#9200;</span> Scheduled Tasks
11142
+ <div class="nav-item" data-page="build" title="Workflows, crons, skills">
11143
+ <span class="nav-icon">&#128736;</span> Build
10483
11144
  <span class="nav-badge" id="nav-cron-count">0</span>
10484
11145
  </div>
10485
- <div class="nav-item" onclick="openAutomationsTab('skills')">
10486
- <span class="nav-icon">&#128737;</span> Skills
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">&#9201;</span> Timers
11146
+ <div class="nav-item" data-page="team" title="Agents, activity, goals">
11147
+ <span class="nav-icon">&#128101;</span> Team
10491
11148
  </div>
10492
- <div class="nav-item" data-page="workflows">
10493
- <span class="nav-icon">&#128260;</span> Workflows
11149
+ <div class="nav-item" data-page="brain" title="Memory, knowledge, ingestion, health">
11150
+ <span class="nav-icon">&#129504;</span> Brain
10494
11151
  </div>
10495
- <div class="nav-item" data-page="unleashed">
10496
- <span class="nav-icon">&#128640;</span> Unleashed
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">&#9881;</span> Settings
10498
11154
  </div>
10499
11155
  </div>
10500
- <div class="nav-section">
10501
- <div class="nav-section-title">Team</div>
10502
- <div class="nav-item" data-page="team">
10503
- <span class="nav-icon">&#128101;</span> The Office
10504
- </div>
10505
- <div class="nav-item" data-page="team-status">
10506
- <span class="nav-icon">&#128202;</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 Agent
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">&#128736;</span> Builder
10517
- </div>
10518
- <div class="nav-item" onclick="openSkillStudio()">
10519
- <span class="nav-icon">&#128161;</span> Skill Studio
10520
- </div>
10521
- <div class="nav-item" data-page="projects">
10522
- <span class="nav-icon">&#128194;</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">&#128274;</span> Trust &amp; 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">&#128200;</span> Metrics
10533
- </div>
10534
- <div class="nav-item" data-page="memory-health">
10535
- <span class="nav-icon">&#129504;</span> Memory Health
10536
- </div>
10537
- <div class="nav-item" data-page="logs">
10538
- <span class="nav-icon">&#128220;</span> Logs
10539
- </div>
10540
- <div class="nav-item" data-page="sessions">
10541
- <span class="nav-icon">&#128221;</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-section-title">System</div>
10546
- <div class="nav-item" data-page="settings">
10547
- <span class="nav-icon">&#9881;</span> Settings
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">&#128269;</span> Search</span>
11168
+ <kbd style="font-size:10px;padding:1px 5px;border:1px solid var(--border);border-radius:3px">&#8984;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 Page ═══ -->
11177
+ <!-- ═══ Home chat-first daily driver ═══ -->
10557
11178
  <div class="page active" id="page-home">
10558
- <div class="agent-hero" id="agent-hero">
10559
- <div class="agent-hero-top">
10560
- <div class="agent-avatar" id="agent-avatar">${name.charAt(0).toUpperCase()}</div>
10561
- <div class="agent-info">
10562
- <div class="hero-wordmark" id="hero-wordmark"></div>
10563
- <div class="agent-activity" id="agent-activity">
10564
- <span class="agent-activity-dot"></span>
10565
- <span>Loading...</span>
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">&times;</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">&#128274;</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">&#128101;</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">&#128172;</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">&#9200;</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">&#128194;</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
- <!-- Getting Started (shown for new users, hidden when setup is complete) -->
10575
- <div id="getting-started" class="getting-started" style="display:none">
10576
- <div class="gs-header">
10577
- <div class="gs-title">Get Started</div>
10578
- <div class="gs-subtitle">Set up your AI assistant in 5 steps</div>
10579
- <button class="btn-ghost btn-sm gs-dismiss" onclick="dismissGettingStarted()" title="Dismiss">&times;</button>
10580
- </div>
10581
- <div class="gs-grid">
10582
- <div class="gs-card" id="gs-step-auth">
10583
- <div class="gs-step-num">1</div>
10584
- <div class="gs-card-icon">&#128274;</div>
10585
- <div class="gs-card-title">Login with Anthropic</div>
10586
- <div class="gs-card-desc" id="gs-auth-desc">Connect your Anthropic account to power your agents.</div>
10587
- <button class="btn btn-sm btn-primary" id="gs-auth-btn" onclick="startAnthropicOAuth()">Login with Claude</button>
10588
- </div>
10589
- <div class="gs-card" id="gs-step-agent">
10590
- <div class="gs-step-num">2</div>
10591
- <div class="gs-card-icon">&#128101;</div>
10592
- <div class="gs-card-title">Create an Agent</div>
10593
- <div class="gs-card-desc">Hire your first AI team member with a role, tools, and a channel.</div>
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">&#128172;</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">&#9200;</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(&quot;What&apos;s on my schedule?&quot;)">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'&amp;&amp;!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
- <div class="gs-card" id="gs-step-project">
10611
- <div class="gs-step-num">5</div>
10612
- <div class="gs-card-icon">&#128194;</div>
10613
- <div class="gs-card-title">Link a Project</div>
10614
- <div class="gs-card-desc">Give agents context about your codebases and tools.</div>
10615
- <button class="btn btn-sm" onclick="navigateTo('projects')">Browse Projects</button>
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
- </div>
10618
- </div>
11269
+ </main>
10619
11270
 
10620
- <div class="summary-grid" id="summary-cards"></div>
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">&times;</button>
10621
11274
 
10622
- <!-- Today's Plan + Team Pulse -->
10623
- <div style="display:grid;grid-template-columns:3fr 2fr;gap:16px;margin-bottom:16px">
10624
- <div class="card">
10625
- <div class="card-header" style="display:flex;justify-content:space-between;align-items:center">
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
- </div>
10632
- <div class="card-body" id="home-plan-content" style="max-height:320px;overflow-y:auto"><div class="empty-state">Loading plan...</div></div>
10633
- </div>
10634
- <div class="card">
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
- <!-- Live Activity feed — primary home content. Metrics, Sessions, and per-agent stats moved to dedicated sidebar pages. -->
10644
- <div class="card">
10645
- <div class="card-header" style="display:flex;justify-content:space-between;align-items:center">
10646
- <span>Live Activity</span>
10647
- <div style="display:flex;gap:6px;align-items:center">
10648
- <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)">
10649
- <option value="">All Sources</option>
10650
- <option value="cron">Cron</option>
10651
- <option value="activity">Activities</option>
10652
- <option value="send">Emails</option>
10653
- <option value="approval">Approvals</option>
10654
- <option value="memory">Memory</option>
10655
- </select>
10656
- <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)">
10657
- <option value="">All Agents</option>
10658
- </select>
10659
- </div>
10660
- </div>
10661
- <div class="card-body" id="panel-activity"><div class="empty-state">Loading...</div></div>
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">&#9776;</button>
10662
11314
  </div>
10663
11315
  </div>
10664
11316
 
10665
11317
  <!-- ═══ Builder Page — Conversational Artifact Creation ═══ -->
10666
- <div class="page" id="page-builder">
10667
- <div style="display:flex;align-items:center;gap:12px;padding:12px 16px;border-bottom:1px solid var(--border)">
10668
- <span class="page-title" id="builder-page-title" style="margin:0;font-size:16px">Builder</span>
10669
- <select id="builder-type" onchange="resetBuilder();updateBuilderMode()" style="padding:6px 10px;border:1px solid var(--border);border-radius:6px;background:var(--bg-secondary);color:var(--text-primary);font-size:13px">
10670
- <option value="skill">Skill</option>
10671
- <option value="cron">Cron Job</option>
10672
- <option value="agent">Agent</option>
10673
- <option value="workflow">Workflow</option>
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')">&#128279; Workflows</button>
11321
+ <button data-build-tab="crons" onclick="switchBuildTab('crons')">&#9200; 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')">&#128737; 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')">&#128221; 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:6px 12px;font-size:13px;color:var(--text-secondary);font-weight:500"></span>
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
- <div style="display:flex;flex:1;min-height:0;overflow:hidden">
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:400px;display:flex;flex-direction:column;background:var(--bg-secondary)">
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);margin-left:8px"></span>
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
- <!-- ═══ Scheduled Tasks Page (Cron + Timers + Self-Improve + Skills + Analytics) ═══ -->
10742
- <div class="page" id="page-automations">
10743
- <div class="page-title">Scheduled Tasks</div>
10744
- <div class="tab-bar" id="automations-tabs">
10745
- <button class="active" onclick="switchTab('automations','scheduled')">Scheduled Tasks</button>
10746
- <button onclick="switchTab('automations','broken')">Broken Jobs <span class="tab-badge" id="tab-broken-count" title="repeatedly failing" style="display:none;background:#ef4444;color:#fff">0</span></button>
10747
- <button onclick="switchTab('automations','timers')">Timers <span class="tab-badge" id="tab-timer-count" style="display:none">0</span></button>
10748
- <button onclick="switchTab('automations','self-improve')">Self-Improve <span class="tab-badge" id="tab-si-pending" style="display:none">0</span></button>
10749
- <button onclick="switchTab('automations','skills')">Skills <span class="tab-badge" id="tab-skill-count" style="display:none">0</span><span class="tab-badge" id="tab-pending-skill-count" title="pending approval" style="display:none;background:#f59e0b;color:#000">0</span></button>
10750
- <button onclick="switchTab('automations','analytics')">Execution Analytics</button>
10751
- </div>
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">&#128240;</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-body" id="panel-broken-jobs"><div class="empty-state">Loading...</div></div>
10763
- </div>
10764
- </div>
10765
- <div class="tab-pane" id="tab-automations-timers">
10766
- <div class="card">
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">&#128202;</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-body" id="teach-skill-form" style="display:none;padding:16px">
10792
- <div style="display:grid;gap:12px">
10793
- <div>
10794
- <label style="font-size:12px;font-weight:600;color:var(--text-secondary)">Title</label>
10795
- <input type="text" id="skill-title" placeholder="e.g., Deploy to production" style="width:100%;padding:8px;border:1px solid var(--border);border-radius:6px;background:var(--bg-input);color:var(--text-primary);font-size:13px">
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">&#128221;</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
- </div>
10816
- <div class="card" id="pending-skills-card" style="display:none">
10817
- <div class="card-header" style="display:flex;align-items:center;justify-content:space-between">
10818
- <span>Pending Approval</span>
10819
- <span class="badge badge-orange" id="pending-skills-count-badge" style="font-size:10px">0 pending</span>
11447
+ <div class="card clickable-row" onclick="forkBuildTemplate('email-triage')" style="padding:18px">
11448
+ <div style="font-size:24px;margin-bottom:8px">&#128231;</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-body" id="panel-pending-skills"></div>
10822
- </div>
10823
- <div class="card">
10824
- <div class="card-header" style="display:flex;align-items:center;justify-content:space-between">
10825
- <span>Learned Skills</span>
10826
- <span class="badge badge-gray" id="skill-count-badge" style="font-size:10px">0 skills</span>
11453
+ <div class="card clickable-row" onclick="forkBuildTemplate('weekly-review')" style="padding:18px">
11454
+ <div style="font-size:24px;margin-bottom:8px">&#128197;</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">&#10133;</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
- <!-- ═══ Team Status Page ═══ -->
10841
- <div class="page" id="page-team-status">
10842
- <div class="page-title">Team Status</div>
10843
- <div id="team-status-grid" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:16px;margin-top:16px"></div>
10844
- <script>
10845
- (function() {
10846
- function renderTeamStatus() {
10847
- fetch('/api/team/status').then(r => r.json()).then(data => {
10848
- const grid = document.getElementById('team-status-grid');
10849
- if (!grid) return;
10850
- if (!data.agents || data.agents.length === 0) {
10851
- grid.innerHTML = '<div class="empty-state">No agents found. Create an agent to get started.</div>';
10852
- return;
10853
- }
10854
- grid.innerHTML = data.agents.map(a => {
10855
- const avatarLetter = (a.name || a.slug || '?').charAt(0).toUpperCase();
10856
- const tasksHtml = a.tasks
10857
- ? '<div style="margin:4px 0"><span style="font-size:12px;color:var(--text-secondary)">Tasks: </span>' +
10858
- '<strong>' + (a.tasks.pending || 0) + ' pending</strong>' +
10859
- (a.tasks.overdue > 0 ? ' <span style="color:#ef4444;font-weight:600">' + a.tasks.overdue + ' overdue</span>' : '') +
10860
- '</div>'
10861
- : '';
10862
- const goalsHtml = a.activeGoals > 0
10863
- ? '<div style="margin:4px 0;font-size:12px"><span style="color:var(--text-secondary)">Goals: </span>' +
10864
- '<strong>' + a.activeGoals + ' active</strong>' +
10865
- (a.firstGoalTitle ? ' — ' + a.firstGoalTitle.slice(0, 60) : '') +
10866
- '</div>'
10867
- : '';
10868
- const noteHtml = a.lastNoteDate
10869
- ? '<div style="margin:4px 0;font-size:12px;color:var(--text-secondary)">Last log: ' + a.lastNoteDate +
10870
- (a.lastNoteSnippet ? ' ' + a.lastNoteSnippet.slice(0, 100) : '') + '</div>'
10871
- : '';
10872
- const wmHtml = a.workingMemorySnippet
10873
- ? '<div style="margin-top:8px;padding:8px;background:var(--bg-input);border-radius:6px;font-size:11px;color:var(--text-secondary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' + a.workingMemorySnippet + '</div>'
10874
- : '';
10875
- return '<div style="background:var(--bg-card);border:1px solid var(--border);border-radius:12px;padding:16px;cursor:pointer" onclick="showPage(\\'team\\');setTimeout(()=>selectAgent(\\''+a.slug+'\\'),100)">' +
10876
- '<div style="display:flex;align-items:center;gap:10px;margin-bottom:10px">' +
10877
- '<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>' +
10878
- '<div><div style="font-weight:600">' + (a.name || a.slug) + '</div>' +
10879
- '<div style="font-size:11px;color:var(--text-secondary)">' + (a.description || a.slug) + '</div></div>' +
10880
- '</div>' +
10881
- tasksHtml + goalsHtml + noteHtml + wmHtml +
10882
- '</div>';
10883
- }).join('');
10884
- }).catch(() => {
10885
- const grid = document.getElementById('team-status-grid');
10886
- if (grid) grid.innerHTML = '<div class="empty-state">Failed to load team status.</div>';
10887
- });
10888
- }
10889
- // Render when page becomes visible
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
- </script>
10900
- </div>
11521
+ };
11522
+ })();
11523
+ </script>
10901
11524
 
10902
11525
  <!-- ═══ Brain Page (unified: Search + Graph + Stats + Sources + Seed + Runs) ═══ -->
10903
- <div class="page" id="page-intelligence">
10904
- <div class="page-title">Brain</div>
10905
- <div style="color:var(--muted,#888);margin-bottom:16px;font-size:13px">
10906
- Query what you know, and feed new knowledge in. Everything on this page writes to or reads from the same memory + knowledge graph.
10907
- </div>
10908
- <div style="display:flex;gap:10px;margin-bottom:16px">
10909
- <input type="text" id="memory-search-input" placeholder="Search vault, notes, memory..." style="flex:1" onkeydown="if(event.key==='Enter')runMemorySearch()">
10910
- <button class="btn-primary" onclick="runMemorySearch()">Search</button>
11526
+ <div class="page" id="page-brain">
11527
+ <div class="page-head">
11528
+ <div class="icon">&#129504;</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')">Search</button>
10914
- <button onclick="switchTab('intelligence','graph')">Knowledge Graph</button>
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','memory')">Memory Stats</button>
10917
- <button onclick="switchTab('intelligence','seed')">Seed Upload</button>
10918
- <button onclick="switchTab('intelligence','sources')">Sources</button>
10919
- <button onclick="switchTab('intelligence','runs')">Ingestion Runs</button>
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 &amp; 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
- <!-- ═══ Sessions Page ═══ -->
12076
- <div class="page" id="page-sessions">
12077
- <div class="page-title">Sessions</div>
12078
- <p style="color:var(--text-muted);margin-bottom:16px">Active conversation sessions across channels. Each session is a continuous thread with one user.</p>
12079
- <div id="panel-sessions"><div class="empty-state">Loading...</div></div>
12080
- </div>
12081
-
12082
- <!-- ═══ Trust & Claims Page ═══ -->
12083
- <div class="page" id="page-claims">
12084
- <div class="page-title">Trust &amp; Claims</div>
12085
- <div class="card" style="margin-bottom:16px">
12086
- <div class="card-body" style="display:flex;align-items:center;gap:16px;padding:16px">
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 &mdash; 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(&quot;What&apos;s on my schedule?&quot;)">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 &mdash; 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
- <!-- ═══ Daily Plan Page ═══ -->
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
- <!-- ═══ Goals Page ═══ -->
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 style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px">
12204
- <div class="page-title" style="margin-bottom:0">The Office</div>
12205
- <div style="display:flex;gap:8px;align-items:center">
12206
- <button class="btn" onclick="startHiringInterview()" style="background:var(--green);color:#000;font-weight:600">Hire a New Employee</button>
12207
- <button class="btn btn-sm" onclick="applyRoleTemplate('sdr')" style="color:var(--accent)" title="Pre-filled SDR role template">+ SDR</button>
12208
- <button class="btn btn-sm" onclick="applyRoleTemplate('researcher')" style="color:var(--text-muted)" title="Pre-filled researcher role template">+ Researcher</button>
12209
- <button class="btn btn-sm" onclick="showAgentCreateModal()" style="color:var(--text-muted)">Manual Setup</button>
12785
+ <div class="page-head">
12786
+ <div class="icon">&#128101;</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">&#128227;</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')">General</button>
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 &amp; Env</button>
12425
13058
  <button onclick="switchTab('settings','integrations')">Integrations</button>
12426
- <button onclick="switchTab('settings','notifications')">Notifications</button>
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-notifications">
12541
- <div id="digest-settings-content"><div class="empty-state">Loading...</div></div>
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
- </div>
12544
- </div>
12545
-
12546
- <!-- ═══ Projects Page (promoted from Settings) ═══ -->
12547
- <div class="page" id="page-projects">
12548
- <div class="page-title">Projects</div>
12549
- <p style="color:var(--text-muted);margin-bottom:16px">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>
12550
- <div class="card" style="margin-bottom:20px">
12551
- <div class="card-header" style="display:flex;align-items:center;justify-content:space-between">
12552
- <span>Workspace Directories</span>
12553
- <button class="btn btn-sm btn-primary" onclick="promptAddWorkspaceDir()" style="font-size:11px">+ Add Path</button>
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="card-body" id="workspace-dirs-list-projects" style="font-size:13px">
12556
- <div class="empty-state">Loading...</div>
13211
+ <div class="tab-pane" id="tab-settings-advanced">
13212
+ <div class="card" style="margin-bottom:16px">
13213
+ <div class="card-header">Diagnostics &amp; 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 &amp; 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
- <!-- ═══ Unleashed Page ═══ -->
12570
- <div class="page" id="page-unleashed">
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
- function navigateTo(page, opts) {
13202
- opts = opts || {};
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
- // Clear all active states
13205
- document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
13206
- document.querySelectorAll('.team-nav-item').forEach(n => n.classList.remove('active'));
13207
- document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
13208
- // Activate the right nav item
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
- // Show the page
13910
+
13216
13911
  var el = document.getElementById('page-' + page);
13217
13912
  if (el) { el.style.display = ''; el.classList.add('active'); }
13218
- // Page-specific refresh
13219
- if (page === 'home') { refreshAll(); }
13220
- if (page === 'chat') { loadProfiles(); document.getElementById('chat-input').focus(); }
13221
- if (page === 'builder') {
13222
- var _builderPreselect = currentAgentSlug || '';
13223
- refreshBuilderAgents(_builderPreselect);
13224
- updateBuilderMode();
13225
- document.getElementById('builder-input').focus();
13226
- }
13227
- if (page === 'automations') { refreshCron(); refreshTimers(); refreshSelfImprove(); refreshSkills(); refreshBrokenJobs(); }
13228
- if (page === 'claims') { refreshClaims(); refreshRoutingAudit(); }
13229
- if (page === 'intelligence') { refreshMemory(); }
13230
- if (page === 'settings') { refreshSettings(); refreshRemoteAccess(); refreshSalesforce(); refreshClaudeIntegrations(); refreshMcpServers(); }
13231
- if (page === 'logs') refreshLogs();
13232
- if (page === 'team') { refreshTeam(); }
13233
- if (page === 'projects') { refreshProjects(); }
13234
- if (page === 'workflows') { refreshWorkflows(); }
13235
- if (page === 'unleashed') { refreshUnleashed(); }
13236
- if (page === 'goals') { refreshGoalsProgress(); }
13237
- if (page === 'metrics') { refreshMetrics(); }
13238
- if (page === 'memory-health') { refreshMemoryHealth(); }
13239
- if (page === 'sessions') { refreshSessions(); }
13240
- if (page === 'daily-plan') { refreshDailyPlan(); }
13241
- if (page === 'agent-detail' && opts.agentSlug != null) {
13242
- currentAgentSlug = opts.agentSlug;
13243
- renderAgentDetail(opts.agentSlug);
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
- // Shortcut nav items that drop into a sub-tab on the Automations page
13249
- function openAutomationsTab(tab) {
13250
- navigateTo('automations');
13251
- setTimeout(function() { switchTab('automations', tab); }, 0);
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 === 'notifications') refreshDigestSettings();
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" style="cursor:pointer" onclick="viewSession(\\x27' + encodeURIComponent(key) + '\\x27)">'
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');