clementine-agent 1.2.3 → 1.3.1

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>
11146
+ <div class="nav-item" data-page="team" title="Agents, activity, goals">
11147
+ <span class="nav-icon">&#128101;</span> Team
10488
11148
  </div>
10489
- <div class="nav-item" onclick="openAutomationsTab('timers')">
10490
- <span class="nav-icon">&#9201;</span> Timers
10491
- </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
- <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>
11336
+ <button class="btn-sm btn-primary" onclick="newFromBuildHeader()" title="Create a new artifact for this tab" style="padding:4px 14px;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,52 @@ 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;flex-direction:column"></div>
11406
+ <!-- Empty-state CTA — visible when no workflow is open on the canvas -->
11407
+ <div id="builder-canvas-empty" style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:14px;color:var(--text-muted);text-align:center;padding:32px;pointer-events:none">
11408
+ <div style="font-size:38px;opacity:0.4">&#128279;</div>
11409
+ <div style="font-size:14px;font-weight:500;color:var(--text-secondary)">No workflow open</div>
11410
+ <div style="font-size:12px;line-height:1.5;max-width:280px">
11411
+ Pick one from the dropdown above &mdash; or click <strong>New</strong> in the header to create one from scratch, or open the <strong>Templates</strong> tab for starter patterns.
11412
+ </div>
11413
+ </div>
11414
+ <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">
11415
+ <span id="builder-canvas-status"></span>
11416
+ <span style="flex:1"></span>
11417
+ <span id="builder-canvas-id" style="font-family:monospace;opacity:0.6"></span>
11418
+ </div>
11419
+ </div>
10724
11420
  <!-- Existing skills drawer (visible in skill mode) -->
10725
11421
  <div id="builder-skills-drawer" style="display:none;border-top:2px solid var(--border);max-height:260px;overflow-y:auto">
10726
11422
  <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 +11427,132 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
10731
11427
  </div>
10732
11428
  </div>
10733
11429
  </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
11430
 
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>
11431
+ <!-- Templates tab starter patterns -->
11432
+ <div id="build-tab-templates" data-build-tabpane="templates" style="display:none;padding:24px;overflow-y:auto">
11433
+ <div style="max-width:920px;margin:0 auto">
11434
+ <h2 style="font-size:18px;font-weight:600;margin:0 0 6px;color:var(--text-primary)">Start from a template</h2>
11435
+ <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>
11436
+ <div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:14px">
11437
+ <div class="card clickable-row" onclick="forkBuildTemplate('daily-news-digest')" style="padding:18px">
11438
+ <div style="font-size:24px;margin-bottom:8px">&#128240;</div>
11439
+ <div style="font-weight:600;font-size:14px;margin-bottom:4px">Daily news digest</div>
11440
+ <div style="font-size:12px;color:var(--text-muted);line-height:1.4">Cron 7am: pull RSS sources, summarize, send to Slack/email.</div>
11441
+ <div style="margin-top:10px;font-size:11px;color:var(--clementine);font-weight:500">cron · 4 steps</div>
10761
11442
  </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>
11443
+ <div class="card clickable-row" onclick="forkBuildTemplate('lead-picker')" style="padding:18px">
11444
+ <div style="font-size:24px;margin-bottom:8px">&#128202;</div>
11445
+ <div style="font-weight:600;font-size:14px;margin-bottom:4px">Lead picker → Salesforce</div>
11446
+ <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>
11447
+ <div style="margin-top:10px;font-size:11px;color:var(--clementine);font-weight:500">manual · 3 steps</div>
10790
11448
  </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>
11449
+ <div class="card clickable-row" onclick="forkBuildTemplate('pr-review-queue')" style="padding:18px">
11450
+ <div style="font-size:24px;margin-bottom:8px">&#128221;</div>
11451
+ <div style="font-weight:600;font-size:14px;margin-bottom:4px">PR review queue</div>
11452
+ <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>
11453
+ <div style="margin-top:10px;font-size:11px;color:var(--clementine);font-weight:500">cron · 3 steps</div>
10814
11454
  </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>
11455
+ <div class="card clickable-row" onclick="forkBuildTemplate('email-triage')" style="padding:18px">
11456
+ <div style="font-size:24px;margin-bottom:8px">&#128231;</div>
11457
+ <div style="font-weight:600;font-size:14px;margin-bottom:4px">Email triage</div>
11458
+ <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>
11459
+ <div style="margin-top:10px;font-size:11px;color:var(--clementine);font-weight:500">cron · 4 steps</div>
10820
11460
  </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>
11461
+ <div class="card clickable-row" onclick="forkBuildTemplate('weekly-review')" style="padding:18px">
11462
+ <div style="font-size:24px;margin-bottom:8px">&#128197;</div>
11463
+ <div style="font-weight:600;font-size:14px;margin-bottom:4px">Weekly review</div>
11464
+ <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>
11465
+ <div style="margin-top:10px;font-size:11px;color:var(--clementine);font-weight:500">cron · 3 steps</div>
11466
+ </div>
11467
+ <div class="card clickable-row" onclick="forkBuildTemplate('blank-workflow')" style="padding:18px;border-style:dashed">
11468
+ <div style="font-size:24px;margin-bottom:8px">&#10133;</div>
11469
+ <div style="font-weight:600;font-size:14px;margin-bottom:4px">Blank workflow</div>
11470
+ <div style="font-size:12px;color:var(--text-muted);line-height:1.4">Start from scratch with a single prompt step.</div>
11471
+ <div style="margin-top:10px;font-size:11px;color:var(--clementine);font-weight:500">manual · 1 step</div>
10827
11472
  </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
11473
  </div>
10830
11474
  </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
11475
  </div>
10838
11476
  </div>
10839
11477
 
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'] });
11478
+ <!-- page-agent-detail merged into Team page; click an agent in Roster to drill down. -->
11479
+
11480
+
11481
+ <!-- DELETED in Session 5 — automations content moved to Build / Brain → Learning.
11482
+ (Block formerly housed: panel-cron, panel-broken-jobs, panel-timers,
11483
+ si-* status cards/proposals/history, teach-skill-form, panel-skills,
11484
+ pending-skills-card, panel-workflows, advisor-analytics-content.) -->
11485
+ <!-- (Session 5) page-automations parking removed. Self-Improve now lives in
11486
+ Brain Learning; Build (Workflows/Crons/Skills/Templates) is the home for
11487
+ everything else that was here. -->
11488
+
11489
+ <!-- page-team-status merged into Team Activity tab.
11490
+ Render is now triggered by switchTab('team','activity'). -->
11491
+ <script>
11492
+ (function() {
11493
+ window.renderTeamStatus = function renderTeamStatus() {
11494
+ fetch('/api/team/status').then(function(r) { return r.json(); }).then(function(data) {
11495
+ var grid = document.getElementById('team-status-grid');
11496
+ if (!grid) return;
11497
+ if (!data.agents || data.agents.length === 0) {
11498
+ 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>';
11499
+ return;
11500
+ }
11501
+ grid.innerHTML = data.agents.map(function(a) {
11502
+ var avatarLetter = (a.name || a.slug || '?').charAt(0).toUpperCase();
11503
+ var tasksHtml = a.tasks
11504
+ ? '<div style="margin:4px 0"><span style="font-size:12px;color:var(--text-secondary)">Tasks: </span><strong>' + (a.tasks.pending || 0) + ' pending</strong>' +
11505
+ (a.tasks.overdue > 0 ? ' <span style="color:#ef4444;font-weight:600">' + a.tasks.overdue + ' overdue</span>' : '') + '</div>'
11506
+ : '';
11507
+ var goalsHtml = a.activeGoals > 0
11508
+ ? '<div style="margin:4px 0;font-size:12px"><span style="color:var(--text-secondary)">Goals: </span><strong>' + a.activeGoals + ' active</strong>' +
11509
+ (a.firstGoalTitle ? ' — ' + a.firstGoalTitle.slice(0, 60) : '') + '</div>'
11510
+ : '';
11511
+ var noteHtml = a.lastNoteDate
11512
+ ? '<div style="margin:4px 0;font-size:12px;color:var(--text-secondary)">Last log: ' + a.lastNoteDate +
11513
+ (a.lastNoteSnippet ? ' ' + a.lastNoteSnippet.slice(0, 100) : '') + '</div>'
11514
+ : '';
11515
+ var wmHtml = a.workingMemorySnippet
11516
+ ? '<div style="margin-top:8px;padding:8px;background:var(--bg-input);border-radius:6px;font-size:11px;color:var(--text-secondary)">' + a.workingMemorySnippet + '</div>'
11517
+ : '';
11518
+ 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})">' +
11519
+ '<div style="display:flex;align-items:center;gap:10px;margin-bottom:10px">' +
11520
+ '<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>' +
11521
+ '<div><div style="font-weight:600">' + (a.name || a.slug) + '</div>' +
11522
+ '<div style="font-size:11px;color:var(--text-secondary)">' + (a.description || a.slug) + '</div></div></div>' +
11523
+ tasksHtml + goalsHtml + noteHtml + wmHtml + '</div>';
11524
+ }).join('');
11525
+ }).catch(function() {
11526
+ var grid = document.getElementById('team-status-grid');
11527
+ if (grid) grid.innerHTML = '<div class="empty-state">Failed to load team status.</div>';
10897
11528
  });
10898
- })();
10899
- </script>
10900
- </div>
11529
+ };
11530
+ })();
11531
+ </script>
10901
11532
 
10902
11533
  <!-- ═══ 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>
11534
+ <div class="page" id="page-brain">
11535
+ <div class="page-head">
11536
+ <div class="icon">&#129504;</div>
11537
+ <div class="title-block">
11538
+ <h1>Brain</h1>
11539
+ <p class="desc">Query what you know, feed new knowledge in, and watch the system learn.</p>
11540
+ </div>
11541
+ <div class="actions" style="flex:1;max-width:480px;display:flex;gap:8px">
11542
+ <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()">
11543
+ <button class="btn-primary btn-sm" onclick="runMemorySearch()">Search</button>
11544
+ </div>
10911
11545
  </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>
11546
+ <div class="tab-bar" id="intelligence-tabs" style="margin:0 0 0 18px">
11547
+ <button class="active" onclick="switchTab('intelligence','search')">Memory</button>
11548
+ <button onclick="switchTab('intelligence','graph')">Knowledge</button>
11549
+ <button onclick="switchTab('intelligence','sources')">Ingestion</button>
11550
+ <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
11551
  <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>
11552
+ <button onclick="switchTab('intelligence','learning')">Learning <span class="tab-badge" id="brain-learning-badge" style="display:none;background:#f59e0b;color:#000">0</span></button>
11553
+ <button onclick="switchTab('intelligence','memory')">Stats</button>
11554
+ <button onclick="switchTab('intelligence','seed')">Seed</button>
11555
+ <button onclick="switchTab('intelligence','runs')">Runs</button>
10920
11556
  </div>
10921
11557
  <div id="intelligence-tab-content">
10922
11558
  <div class="tab-pane active" id="tab-intelligence-search">
@@ -11118,6 +11754,68 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
11118
11754
  <div class="tab-pane" id="tab-intelligence-runs">
11119
11755
  <div id="brain-runs-list"></div>
11120
11756
  </div>
11757
+ <div class="tab-pane" id="tab-intelligence-health">
11758
+ <div style="display:flex;align-items:center;gap:8px;margin-bottom:14px;flex-wrap:wrap">
11759
+ <button class="btn-sm" onclick="memoryHealthAction('janitor')" title="Run the janitor cleanup pass now">Run cleanup</button>
11760
+ <button class="btn-sm" onclick="memoryHealthAction('rebuild-fts')" title="Rebuild the FTS5 index">Rebuild FTS</button>
11761
+ <button class="btn-sm" onclick="memoryHealthAction('fix-orphans')" title="Null out missing derived_from refs">Fix orphans</button>
11762
+ <button class="btn-sm" onclick="refreshMemoryHealth()">Refresh</button>
11763
+ </div>
11764
+ <div id="memory-health-content">
11765
+ <div class="skel-block"><div class="skel-row med"></div><div class="skel-row"></div><div class="skel-row short"></div></div>
11766
+ </div>
11767
+ <div class="card" style="margin-top:18px">
11768
+ <div class="card-header" style="display:flex;align-items:center;justify-content:space-between">
11769
+ <span>Trust &amp; claim verification</span>
11770
+ <span id="trust-score-detail" style="font-size:11px;color:var(--text-muted)">Rolling 30-claim score</span>
11771
+ </div>
11772
+ <div class="card-body" style="padding:14px;display:flex;align-items:center;gap:14px">
11773
+ <span style="font-size:28px;font-weight:700" id="trust-score-big">--</span>
11774
+ <div style="flex:1;display:flex;gap:6px;flex-wrap:wrap">
11775
+ <button class="btn-sm" onclick="refreshClaims('all')" id="claims-filter-all">All</button>
11776
+ <button class="btn-sm" onclick="refreshClaims('pending')" id="claims-filter-pending">Pending</button>
11777
+ <button class="btn-sm" onclick="refreshClaims('verified')" id="claims-filter-verified">Verified</button>
11778
+ <button class="btn-sm" onclick="refreshClaims('failed')" id="claims-filter-failed">Failed</button>
11779
+ </div>
11780
+ </div>
11781
+ </div>
11782
+ <div class="card" style="margin-top:14px">
11783
+ <div class="card-header">Recent claims</div>
11784
+ <div class="card-body" id="panel-claims">
11785
+ <div class="skel-block"><div class="skel-row med"></div><div class="skel-row short"></div></div>
11786
+ </div>
11787
+ </div>
11788
+ <div class="card" style="margin-top:14px">
11789
+ <div class="card-header" style="display:flex;align-items:center;justify-content:space-between">
11790
+ <span>Team routing decisions</span>
11791
+ <span style="font-size:11px;color:var(--text-muted)">Owner-facing sessions only</span>
11792
+ </div>
11793
+ <div class="card-body" id="panel-routing-audit">
11794
+ <div class="skel-block"><div class="skel-row med"></div></div>
11795
+ </div>
11796
+ </div>
11797
+ </div>
11798
+ <div class="tab-pane" id="tab-intelligence-learning">
11799
+ <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px">
11800
+ <div style="font-size:13px;color:var(--text-secondary)">Self-improvement runs nightly at 1 AM. You can also trigger it manually.</div>
11801
+ <button class="btn-sm btn-primary" onclick="siRunCycle()" id="si-run-btn">Run Now</button>
11802
+ </div>
11803
+ <div class="grid-2" id="si-status-cards">
11804
+ <div class="skel-block"><div class="skel-row med"></div><div class="skel-row short"></div></div>
11805
+ <div class="skel-block"><div class="skel-row med"></div><div class="skel-row short"></div></div>
11806
+ </div>
11807
+ <div class="card" style="margin-top:16px">
11808
+ <div class="card-header" style="display:flex;align-items:center;justify-content:space-between">
11809
+ <span>Pending Proposals</span>
11810
+ <span class="tab-badge" id="tab-si-pending" style="display:none;background:#f59e0b;color:#000">0</span>
11811
+ </div>
11812
+ <div class="card-body" id="si-pending-list"><div class="empty-state">No pending proposals</div></div>
11813
+ </div>
11814
+ <div class="card" style="margin-top:16px">
11815
+ <div class="card-header">Experiment History</div>
11816
+ <div class="card-body" id="si-history-list"><div class="empty-state">No experiments yet</div></div>
11817
+ </div>
11818
+ </div>
11121
11819
  </div>
11122
11820
 
11123
11821
  <script>
@@ -12072,143 +12770,47 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
12072
12770
  </script>
12073
12771
  </div>
12074
12772
 
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>
12773
+ <!-- Sessions, Trust & Claims, Logs, Chat, Metrics, Daily Plan pages
12774
+ removed in Session 2 — content lives on Home or migrates to other
12775
+ destinations in Sessions 3-4. Page references for these IDs are
12776
+ resolved via ROUTE_REDIRECTS in navigateTo. -->
12777
+
12778
+ <!-- Hidden mounting points for daily-plan content used by the Home rail
12779
+ (refreshDailyPlan looks for these by ID). Keeping the IDs avoids
12780
+ touching every refreshDailyPlan callsite right now. -->
12781
+ <div style="display:none">
12782
+ <div id="plan-diff-content"></div>
12783
+ <div id="plan-history-list"></div>
12784
+ <input type="date" id="plan-date-picker-secondary" disabled>
12175
12785
  </div>
12176
12786
 
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>
12787
+ <!-- (Session 5) page-memory-health parking stub removed. Brain → Health is the live home. -->
12193
12788
 
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>
12789
+ <!-- page-goals merged into Team → Goals tab. -->
12200
12790
 
12201
12791
  <!-- ═══ Team Page — The Office ═══ -->
12202
12792
  <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>
12793
+ <div class="page-head">
12794
+ <div class="icon">&#128101;</div>
12795
+ <div class="title-block">
12796
+ <h1>The Office</h1>
12797
+ <p class="desc">Your team of agents — what they're doing, what they're contributing to.</p>
12210
12798
  </div>
12799
+ <div class="actions">
12800
+ <button class="btn-primary btn-sm" onclick="startHiringInterview()" style="background:var(--green);color:#000">Hire</button>
12801
+ <button class="btn-sm" onclick="applyRoleTemplate('sdr')" title="Pre-filled SDR role template">+ SDR</button>
12802
+ <button class="btn-sm" onclick="applyRoleTemplate('researcher')" title="Pre-filled researcher role template">+ Researcher</button>
12803
+ <button class="btn-sm" onclick="showAgentCreateModal()">Manual</button>
12804
+ </div>
12805
+ </div>
12806
+ <div class="tab-bar" id="team-tabs" style="margin:0 0 0 18px">
12807
+ <button class="active" onclick="switchTab('team','roster')">Roster</button>
12808
+ <button onclick="switchTab('team','activity')">Activity</button>
12809
+ <button onclick="switchTab('team','goals')">Goals</button>
12810
+ <button onclick="switchTab('team','comms')">Comms</button>
12211
12811
  </div>
12812
+ <div id="team-tab-content">
12813
+ <div class="tab-pane active" id="tab-team-roster">
12212
12814
  <div id="office-hero-section"></div>
12213
12815
  <div class="office-floor" id="team-agent-grid">
12214
12816
  <div class="empty-state">No agents configured</div>
@@ -12413,17 +13015,60 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
12413
13015
  </form>
12414
13016
  </div>
12415
13017
  </div>
13018
+ </div><!-- /tab-team-roster -->
13019
+
13020
+ <!-- Team → Activity tab (migrated from page-team-status) -->
13021
+ <div class="tab-pane" id="tab-team-activity" style="padding:18px">
13022
+ <div id="team-status-grid" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:16px">
13023
+ <div class="skel-block"><div class="skel-row med"></div><div class="skel-row"></div><div class="skel-row short"></div></div>
13024
+ </div>
13025
+ </div>
13026
+
13027
+ <!-- Team → Goals tab (migrated from page-goals) -->
13028
+ <div class="tab-pane" id="tab-team-goals" style="padding:18px">
13029
+ <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:14px">
13030
+ <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>
13031
+ <button class="btn-primary btn-sm" onclick="openNewGoalForm()">+ New Goal</button>
13032
+ </div>
13033
+ <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">
13034
+ <div style="font-weight:600;font-size:13px;margin-bottom:10px">New goal</div>
13035
+ <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">
13036
+ <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>
13037
+ <div style="display:flex;gap:8px;justify-content:flex-end">
13038
+ <button class="btn-sm" onclick="document.getElementById('new-goal-form').style.display='none'">Cancel</button>
13039
+ <button class="btn-sm btn-primary" onclick="submitNewGoal()">Create</button>
13040
+ </div>
13041
+ </div>
13042
+ <div id="goals-progress-content">
13043
+ <div class="skel-block"><div class="skel-row med"></div><div class="skel-row"></div><div class="skel-row short"></div></div>
13044
+ </div>
13045
+ </div>
13046
+
13047
+ <!-- Team → Comms tab — placeholder (Session 5 will fill). -->
13048
+ <div class="tab-pane" id="tab-team-comms" style="padding:18px">
13049
+ <div class="empty-cta">
13050
+ <div class="icon">&#128227;</div>
13051
+ <div class="label">Inter-agent communications</div>
13052
+ <div class="hint">Coming soon — message log + topology visualization across agents.</div>
13053
+ </div>
13054
+ </div>
13055
+ </div><!-- /team-tab-content -->
12416
13056
  </div>
12417
13057
 
13058
+ <!-- (Session 5) team-status / agent-detail / goals parking divs removed.
13059
+ Team's Roster / Activity / Goals tabs are the live homes. -->
13060
+
12418
13061
  <!-- ═══ Settings Page (merged: General + Remote + Integrations + Projects) ═══ -->
12419
13062
  <div class="page" id="page-settings">
12420
13063
  <div class="page-title">Settings</div>
12421
13064
  <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>
13065
+ <button class="active" onclick="switchTab('settings','general')">Channels &amp; Env</button>
12425
13066
  <button onclick="switchTab('settings','integrations')">Integrations</button>
12426
- <button onclick="switchTab('settings','notifications')">Notifications</button>
13067
+ <button onclick="switchTab('settings','projects')">Projects</button>
13068
+ <button onclick="switchTab('settings','security')">Security</button>
13069
+ <button onclick="switchTab('settings','logs')">Logs</button>
13070
+ <button onclick="switchTab('settings','remote')">Remote Access</button>
13071
+ <button onclick="switchTab('settings','advanced')">Advanced</button>
12427
13072
  </div>
12428
13073
  <div id="settings-tab-content">
12429
13074
  <div class="tab-pane active" id="tab-settings-general">
@@ -12537,41 +13182,62 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
12537
13182
  </div>
12538
13183
  </div>
12539
13184
  </div>
12540
- <div class="tab-pane" id="tab-settings-notifications">
12541
- <div id="digest-settings-content"><div class="empty-state">Loading...</div></div>
13185
+ <div class="tab-pane" id="tab-settings-projects">
13186
+ <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>
13187
+ <div class="card" style="margin-bottom:20px">
13188
+ <div class="card-header" style="display:flex;align-items:center;justify-content:space-between">
13189
+ <span>Workspace Directories</span>
13190
+ <button class="btn btn-sm btn-primary" onclick="promptAddWorkspaceDir()" style="font-size:11px">+ Add Path</button>
13191
+ </div>
13192
+ <div class="card-body" id="workspace-dirs-list-projects" style="font-size:13px">
13193
+ <div class="skel-block"><div class="skel-row med"></div><div class="skel-row short"></div></div>
13194
+ </div>
13195
+ </div>
13196
+ <div id="panel-projects-page">
13197
+ <div class="skel-block"><div class="skel-row med"></div><div class="skel-row"></div></div>
13198
+ </div>
12542
13199
  </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>
13200
+ <div class="tab-pane" id="tab-settings-logs">
13201
+ <div style="display:flex;gap:8px;align-items:center;margin-bottom:14px;flex-wrap:wrap">
13202
+ <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">
13203
+ <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()">
13204
+ <option value="">All Levels</option>
13205
+ <option value="error">Error+</option>
13206
+ <option value="warn">Warn+</option>
13207
+ <option value="info">Info+</option>
13208
+ <option value="debug">Debug+</option>
13209
+ </select>
13210
+ <label style="display:flex;align-items:center;gap:6px;font-size:12px;color:var(--text-secondary);cursor:pointer">
13211
+ <input type="checkbox" id="log-autoscroll" checked> Auto-scroll
13212
+ </label>
13213
+ <button class="btn-sm" onclick="refreshLogs()">Refresh</button>
13214
+ </div>
13215
+ <div class="log-viewer" id="panel-logs">
13216
+ <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>
13217
+ </div>
12554
13218
  </div>
12555
- <div class="card-body" id="workspace-dirs-list-projects" style="font-size:13px">
12556
- <div class="empty-state">Loading...</div>
13219
+ <div class="tab-pane" id="tab-settings-advanced">
13220
+ <div class="card" style="margin-bottom:16px">
13221
+ <div class="card-header">Diagnostics &amp; maintenance</div>
13222
+ <div class="card-body" style="padding:16px;display:flex;gap:8px;flex-wrap:wrap">
13223
+ <button class="btn-sm" onclick="restartDashboard()">Restart Dashboard</button>
13224
+ <button class="btn-sm" onclick="if(confirm('Restart the daemon? Active sessions drain first.')) apiPost('/api/restart')">Restart Daemon</button>
13225
+ <button class="btn-sm" onclick="apiFetch('/api/doctor').then(function(r){return r.text()}).then(function(t){alert(t)})">Run Doctor</button>
13226
+ <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>
13227
+ </div>
13228
+ </div>
13229
+ <div class="card">
13230
+ <div class="card-header">Notifications &amp; digest</div>
13231
+ <div class="card-body" style="padding:16px" id="digest-settings-content">
13232
+ <div class="skel-block"><div class="skel-row med"></div><div class="skel-row short"></div></div>
13233
+ </div>
13234
+ </div>
12557
13235
  </div>
12558
13236
  </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
13237
  </div>
12568
13238
 
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>
13239
+ <!-- (Session 5) page-workflows / page-unleashed parking divs removed.
13240
+ Build Workflows + Home rail Active Runs are the live homes. -->
12575
13241
 
12576
13242
  </div><!-- /content -->
12577
13243
  </div><!-- /layout -->
@@ -13198,59 +13864,363 @@ let currentPage = 'home';
13198
13864
  var currentAgentSlug = null;
13199
13865
  var prevAgentSlugs = null;
13200
13866
 
13867
+ // ── Routing ────────────────────────────────────────────────────
13868
+ //
13869
+ // Five top-level destinations: home, build, team, brain, settings.
13870
+ // Sub-pages live as tabs within each destination.
13871
+ // Old routes redirect once for back-compat.
13872
+
13873
+ var DESTINATIONS = ['home', 'build', 'team', 'brain', 'settings'];
13874
+
13875
+ var ROUTE_REDIRECTS = {
13876
+ // old hash → new {page, tab}
13877
+ 'chat': { page: 'home', tab: 'chat' },
13878
+ 'sessions': { page: 'home', tab: 'activity' },
13879
+ 'daily-plan': { page: 'home', tab: 'today' },
13880
+ 'goals': { page: 'team', tab: 'goals' },
13881
+ 'workflows': { page: 'build', tab: 'workflows' },
13882
+ 'automations': { page: 'build', tab: 'crons' },
13883
+ 'unleashed': { page: 'build', tab: 'workflows' },
13884
+ 'builder': { page: 'build', tab: 'workflows' },
13885
+ 'skill-studio': { page: 'build', tab: 'skills' },
13886
+ 'team-status': { page: 'team', tab: 'activity' },
13887
+ 'agent-detail': { page: 'team', tab: 'roster' },
13888
+ 'intelligence': { page: 'brain', tab: 'memory' },
13889
+ 'memory-health': { page: 'brain', tab: 'health' },
13890
+ 'claims': { page: 'brain', tab: 'health' },
13891
+ 'metrics': { page: 'team', tab: 'activity' },
13892
+ 'logs': { page: 'settings', tab: 'logs' },
13893
+ 'projects': { page: 'settings', tab: 'projects' },
13894
+ };
13895
+
13201
13896
  function navigateTo(page, opts) {
13202
13897
  opts = opts || {};
13898
+
13899
+ // Redirect old route names to the new IA so existing callers + bookmarks work.
13900
+ if (ROUTE_REDIRECTS[page]) {
13901
+ var r = ROUTE_REDIRECTS[page];
13902
+ return navigateTo(r.page, Object.assign({ tab: r.tab }, opts));
13903
+ }
13904
+
13905
+ if (DESTINATIONS.indexOf(page) === -1) page = 'home';
13203
13906
  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
13907
+
13908
+ document.querySelectorAll('.nav-item').forEach(function(n) { n.classList.remove('active'); });
13909
+ document.querySelectorAll('.team-nav-item').forEach(function(n) { n.classList.remove('active'); });
13910
+ document.querySelectorAll('.page').forEach(function(p) { p.classList.remove('active'); });
13911
+
13209
13912
  var navEl = document.querySelector('.nav-item[data-page="' + page + '"]');
13210
13913
  if (navEl) navEl.classList.add('active');
13211
13914
  if (opts.agentSlug != null) {
13212
13915
  var teamEl = document.querySelector('.team-nav-item[data-slug="' + opts.agentSlug + '"]');
13213
13916
  if (teamEl) teamEl.classList.add('active');
13214
13917
  }
13215
- // Show the page
13918
+
13216
13919
  var el = document.getElementById('page-' + page);
13217
13920
  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);
13921
+
13922
+ // Per-destination init + tab routing
13923
+ switch (page) {
13924
+ case 'home':
13925
+ refreshAll();
13926
+ // tab is a soft hint on Home (one cohesive layout): focus the relevant area.
13927
+ var t = opts.tab || 'chat';
13928
+ setTimeout(function() {
13929
+ if (t === 'chat') {
13930
+ var ci = document.getElementById('chat-input');
13931
+ if (ci) ci.focus();
13932
+ } else if (t === 'today') {
13933
+ var rail = document.getElementById('home-rail');
13934
+ if (rail && window.matchMedia('(max-width: 1024px)').matches) rail.classList.add('open');
13935
+ var p = document.getElementById('home-plan-content');
13936
+ if (p) p.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
13937
+ } else if (t === 'activity') {
13938
+ var act = document.getElementById('panel-activity');
13939
+ if (act) act.scrollIntoView({ behavior: 'smooth', block: 'start' });
13940
+ }
13941
+ }, 80);
13942
+ break;
13943
+ case 'build':
13944
+ switchBuildTab(opts.tab || 'workflows');
13945
+ var bp = currentAgentSlug || '';
13946
+ refreshBuilderAgents(bp);
13947
+ break;
13948
+ case 'team':
13949
+ refreshTeam();
13950
+ switchDestTab('team', opts.tab || 'roster');
13951
+ if (opts.agentSlug) {
13952
+ currentAgentSlug = opts.agentSlug;
13953
+ if (typeof openAgentDrawer === 'function') openAgentDrawer(opts.agentSlug);
13954
+ }
13955
+ break;
13956
+ case 'brain':
13957
+ if (typeof refreshMemory === 'function') refreshMemory();
13958
+ var bt = opts.tab || 'memory';
13959
+ // Spec tab names → internal intelligence-tab ids
13960
+ var intelTab = bt === 'memory' ? 'search'
13961
+ : bt === 'knowledge' ? 'graph'
13962
+ : bt === 'ingestion' ? 'sources'
13963
+ : bt === 'health' ? 'health'
13964
+ : bt === 'user-model' ? 'user-model'
13965
+ : bt === 'learning' ? 'learning'
13966
+ : bt;
13967
+ try { switchTab('intelligence', intelTab); } catch (e) { /* */ }
13968
+ if (bt === 'health') {
13969
+ if (typeof refreshMemoryHealth === 'function') refreshMemoryHealth();
13970
+ if (typeof refreshClaims === 'function') refreshClaims();
13971
+ if (typeof refreshRoutingAudit === 'function') refreshRoutingAudit();
13972
+ }
13973
+ if (bt === 'learning' && typeof refreshSelfImprove === 'function') refreshSelfImprove();
13974
+ break;
13975
+ case 'settings':
13976
+ switchDestTab('settings', opts.tab || 'channels');
13977
+ break;
13244
13978
  }
13979
+
13245
13980
  closeSidebar();
13246
13981
  }
13247
13982
 
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);
13983
+ /** Switch the active tab inside a destination. Tabs use [data-tab] markup. */
13984
+ function switchDestTab(page, tab) {
13985
+ if (!tab) return;
13986
+ var pageEl = document.getElementById('page-' + page);
13987
+ if (!pageEl) return;
13988
+ pageEl.querySelectorAll('[data-tab]').forEach(function(el) {
13989
+ var match = el.getAttribute('data-tab') === tab;
13990
+ if (el.tagName === 'BUTTON' || el.classList.contains('tab-btn')) {
13991
+ el.classList.toggle('active', match);
13992
+ } else {
13993
+ el.style.display = match ? '' : 'none';
13994
+ }
13995
+ });
13996
+ // Per-tab init hook (called after DOM update)
13997
+ var initFn = window['_init_' + page + '_' + tab];
13998
+ if (typeof initFn === 'function') {
13999
+ try { initFn(); } catch (err) { console.warn('Tab init failed:', err); }
14000
+ }
14001
+ }
14002
+
14003
+ // (Session 5) openAutomationsTab compat shim removed — all callers route via navigateTo + ROUTE_REDIRECTS.
14004
+
14005
+ // ── Build (Workflows / Crons / Skills / Templates) tabs ─────────────
14006
+ function switchBuildTab(tab) {
14007
+ if (!tab) tab = 'workflows';
14008
+ // Update tab-bar active state
14009
+ document.querySelectorAll('#build-tabs button').forEach(function(b) {
14010
+ b.classList.toggle('active', b.getAttribute('data-build-tab') === tab);
14011
+ });
14012
+ // Show/hide tab panes
14013
+ var workPane = document.getElementById('build-tab-workflows');
14014
+ var tplPane = document.getElementById('build-tab-templates');
14015
+ var headerStrip = document.getElementById('build-header-strip');
14016
+ // Always close any open workflow when changing tabs — switching context
14017
+ // is a clean slate, not a stale node hanging on the canvas.
14018
+ if (typeof closeBuilderCanvas === 'function') closeBuilderCanvas();
14019
+ if (tab === 'templates') {
14020
+ if (workPane) workPane.style.display = 'none';
14021
+ if (tplPane) tplPane.style.display = '';
14022
+ if (headerStrip) headerStrip.style.display = 'none';
14023
+ } else {
14024
+ if (workPane) workPane.style.display = 'flex';
14025
+ if (tplPane) tplPane.style.display = 'none';
14026
+ if (headerStrip) headerStrip.style.display = 'flex';
14027
+ // Map build-tab → builder-type so the canvas + chat reflect the tab.
14028
+ var typeSel = document.getElementById('builder-type');
14029
+ if (typeSel) {
14030
+ var nextType = tab === 'crons' ? 'cron' : tab === 'skills' ? 'skill' : 'workflow';
14031
+ if (typeSel.value !== nextType) {
14032
+ typeSel.value = nextType;
14033
+ if (typeof resetBuilder === 'function') resetBuilder();
14034
+ if (typeof updateBuilderMode === 'function') updateBuilderMode();
14035
+ } else if (typeof updateBuilderMode === 'function') {
14036
+ updateBuilderMode();
14037
+ }
14038
+ }
14039
+ // Preload Drawflow eagerly so the canvas is responsive when the
14040
+ // user picks a workflow (rather than waiting for the lazy load).
14041
+ if (typeof _ensureDrawflowLoaded === 'function') {
14042
+ _ensureDrawflowLoaded().catch(function() { /* */ });
14043
+ }
14044
+ // Focus chat input
14045
+ setTimeout(function() {
14046
+ var bi = document.getElementById('builder-input');
14047
+ if (bi) bi.focus();
14048
+ }, 60);
14049
+ }
14050
+ }
14051
+
14052
+ // "New" button in the Build header strip — context-aware: prompts for a
14053
+ // name and creates the right artifact for the active tab. Workflows + Crons
14054
+ // route through the workflow_create surface; Skills falls back to the
14055
+ // existing chat-based Skill Studio reset.
14056
+ async function newFromBuildHeader() {
14057
+ var activeTab = document.querySelector('#build-tabs button.active')?.getAttribute('data-build-tab') || 'workflows';
14058
+ if (activeTab === 'skills') {
14059
+ if (typeof resetBuilder === 'function') resetBuilder();
14060
+ var bi = document.getElementById('builder-input');
14061
+ if (bi) bi.focus();
14062
+ return;
14063
+ }
14064
+ if (activeTab === 'templates') {
14065
+ toast('Pick a template to fork from the cards.', 'info');
14066
+ return;
14067
+ }
14068
+ var noun = activeTab === 'crons' ? 'cron' : 'workflow';
14069
+ var name = prompt('Name your new ' + noun + ':');
14070
+ if (!name || !name.trim()) return;
14071
+ try {
14072
+ var body = { name: name.trim() };
14073
+ if (activeTab === 'crons') body.schedule = '0 9 * * *'; // sensible default; user edits in canvas
14074
+ var r = await apiJson('POST', '/api/builder/workflows', body);
14075
+ if (r && r.error) { toast('Create failed: ' + r.error, 'error'); return; }
14076
+ if (r && r.id) {
14077
+ await refreshBuilderCanvasPicker(activeTab === 'crons' ? 'cron' : 'workflow');
14078
+ await openBuilderWorkflow(r.id);
14079
+ toast('Created ' + noun + ': ' + name, 'success');
14080
+ }
14081
+ } catch (err) { toast('Create error: ' + err, 'error'); }
14082
+ }
14083
+
14084
+ // ── Build templates: fork a starter pattern into a new workflow ─────
14085
+ async function forkBuildTemplate(templateId) {
14086
+ var templates = {
14087
+ 'daily-news-digest': {
14088
+ name: 'Daily news digest',
14089
+ description: 'Morning news roundup pulled from configured RSS feeds.',
14090
+ schedule: '0 7 * * *',
14091
+ initialPrompt: 'Pull today headlines from configured RSS sources, summarize the top 5 stories, format as a brief digest, then send to my preferred channel.',
14092
+ },
14093
+ 'lead-picker': {
14094
+ name: 'Lead picker (manual)',
14095
+ description: 'Search leads matching ICP, pick from canvas, push selected to Salesforce.',
14096
+ schedule: undefined,
14097
+ initialPrompt: 'Use Salesforce search to find prospects matching the ICP I describe. Show results so I can pick which to push as leads.',
14098
+ },
14099
+ 'pr-review-queue': {
14100
+ name: 'PR review queue',
14101
+ description: 'Weekday morning summary of open PRs that need review.',
14102
+ schedule: '0 9 * * 1-5',
14103
+ initialPrompt: 'List open GitHub PRs that need my review, summarize each PRs risk level and key changes, send a digest to Slack.',
14104
+ },
14105
+ 'email-triage': {
14106
+ name: 'Email triage',
14107
+ description: 'Morning unread-email triage with classified suggested actions.',
14108
+ schedule: '0 8 * * *',
14109
+ initialPrompt: 'List unread emails, classify each by intent (reply / archive / snooze / delegate), draft replies for the ones needing one, surface for my approval.',
14110
+ },
14111
+ 'weekly-review': {
14112
+ name: 'Weekly review',
14113
+ description: 'Friday-evening review of the week and next-week priorities.',
14114
+ schedule: '0 18 * * 5',
14115
+ 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.',
14116
+ },
14117
+ 'blank-workflow': {
14118
+ name: 'New workflow',
14119
+ description: '',
14120
+ schedule: undefined,
14121
+ initialPrompt: 'Describe what this workflow should do.',
14122
+ },
14123
+ };
14124
+ var tpl = templates[templateId];
14125
+ if (!tpl) { toast('Unknown template', 'error'); return; }
14126
+ var name = prompt('Name for the new workflow:', tpl.name);
14127
+ if (!name) return;
14128
+ try {
14129
+ var r = await apiJson('POST', '/api/builder/workflows', {
14130
+ name: name,
14131
+ description: tpl.description,
14132
+ schedule: tpl.schedule,
14133
+ initialPrompt: tpl.initialPrompt,
14134
+ });
14135
+ if (r && r.error) { toast('Create failed: ' + r.error, 'error'); return; }
14136
+ if (r && r.id) {
14137
+ switchBuildTab(tpl.schedule ? 'crons' : 'workflows');
14138
+ refreshBuilderCanvasPicker(tpl.schedule ? 'cron' : 'workflow');
14139
+ setTimeout(function() { openBuilderWorkflow(r.id); }, 200);
14140
+ toast('Forked template: ' + name, 'success');
14141
+ }
14142
+ } catch (err) {
14143
+ toast('Fork failed: ' + err, 'error');
14144
+ }
14145
+ }
14146
+
14147
+ // Cmd+K palette — keyboard-only quick navigation.
14148
+ function openCommandK() {
14149
+ var existing = document.getElementById('cmdk-overlay');
14150
+ if (existing) { existing.remove(); return; }
14151
+ var overlay = document.createElement('div');
14152
+ overlay.id = 'cmdk-overlay';
14153
+ 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';
14154
+ overlay.onclick = function(e) { if (e.target === overlay) overlay.remove(); };
14155
+ var box = document.createElement('div');
14156
+ 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';
14157
+ box.innerHTML =
14158
+ '<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)">' +
14159
+ '<div id="cmdk-results" style="max-height:320px;overflow-y:auto"></div>';
14160
+ overlay.appendChild(box);
14161
+ document.body.appendChild(overlay);
14162
+ var input = document.getElementById('cmdk-input');
14163
+ var results = document.getElementById('cmdk-results');
14164
+ var entries = [
14165
+ { kw: 'home chat', page: 'home', tab: 'chat', label: 'Home · Chat' },
14166
+ { kw: 'home today plan', page: 'home', tab: 'today', label: 'Home · Today' },
14167
+ { kw: 'home activity', page: 'home', tab: 'activity', label: 'Home · Activity' },
14168
+ { kw: 'build workflows', page: 'build', tab: 'workflows', label: 'Build · Workflows' },
14169
+ { kw: 'build crons', page: 'build', tab: 'crons', label: 'Build · Crons' },
14170
+ { kw: 'build skills', page: 'build', tab: 'skills', label: 'Build · Skills' },
14171
+ { kw: 'build templates', page: 'build', tab: 'templates', label: 'Build · Templates' },
14172
+ { kw: 'team roster', page: 'team', tab: 'roster', label: 'Team · Roster' },
14173
+ { kw: 'team activity', page: 'team', tab: 'activity', label: 'Team · Activity' },
14174
+ { kw: 'team comms', page: 'team', tab: 'comms', label: 'Team · Comms' },
14175
+ { kw: 'team goals', page: 'team', tab: 'goals', label: 'Team · Goals' },
14176
+ { kw: 'brain memory', page: 'brain', tab: 'memory', label: 'Brain · Memory' },
14177
+ { kw: 'brain knowledge', page: 'brain', tab: 'knowledge', label: 'Brain · Knowledge' },
14178
+ { kw: 'brain ingestion', page: 'brain', tab: 'ingestion', label: 'Brain · Ingestion' },
14179
+ { kw: 'brain health', page: 'brain', tab: 'health', label: 'Brain · Health' },
14180
+ { kw: 'brain user model', page: 'brain', tab: 'user-model', label: 'Brain · User Model' },
14181
+ { kw: 'settings channels', page: 'settings', tab: 'channels', label: 'Settings · Channels' },
14182
+ { kw: 'settings integrations mcp', page: 'settings', tab: 'integrations', label: 'Settings · Integrations' },
14183
+ { kw: 'settings projects', page: 'settings', tab: 'projects', label: 'Settings · Projects' },
14184
+ { kw: 'settings security', page: 'settings', tab: 'security', label: 'Settings · Security' },
14185
+ { kw: 'settings logs', page: 'settings', tab: 'logs', label: 'Settings · Logs' },
14186
+ { kw: 'settings advanced', page: 'settings', tab: 'advanced', label: 'Settings · Advanced' },
14187
+ ];
14188
+ function render() {
14189
+ var q = (input.value || '').toLowerCase().trim();
14190
+ var hits = q ? entries.filter(function(e) { return e.kw.indexOf(q) !== -1 || e.label.toLowerCase().indexOf(q) !== -1; }) : entries;
14191
+ results.innerHTML = hits.slice(0, 12).map(function(e, i) {
14192
+ 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)' : '') + '">' +
14193
+ '<span>' + e.label + '</span>' +
14194
+ '<span style="font-size:11px;color:var(--text-muted)">' + e.page + '/' + e.tab + '</span>' +
14195
+ '</div>';
14196
+ }).join('') || '<div style="padding:14px 18px;color:var(--text-muted);font-size:12px">No matches</div>';
14197
+ results.querySelectorAll('.cmdk-row').forEach(function(row) {
14198
+ row.onclick = function() {
14199
+ navigateTo(row.getAttribute('data-page'), { tab: row.getAttribute('data-tab') });
14200
+ overlay.remove();
14201
+ };
14202
+ });
14203
+ }
14204
+ input.oninput = render;
14205
+ input.onkeydown = function(e) {
14206
+ if (e.key === 'Escape') overlay.remove();
14207
+ if (e.key === 'Enter') {
14208
+ var first = results.querySelector('.cmdk-row');
14209
+ if (first) first.click();
14210
+ }
14211
+ };
14212
+ render();
14213
+ setTimeout(function() { input.focus(); }, 30);
13252
14214
  }
13253
14215
 
14216
+ // Global keyboard shortcut for Cmd+K / Ctrl+K
14217
+ document.addEventListener('keydown', function(e) {
14218
+ if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
14219
+ e.preventDefault();
14220
+ openCommandK();
14221
+ }
14222
+ });
14223
+
13254
14224
  async function refreshUnleashed() {
13255
14225
  var el = document.getElementById('panel-unleashed');
13256
14226
  if (!el) return;
@@ -13333,23 +14303,27 @@ function switchTab(group, tab) {
13333
14303
  if (pane) pane.classList.add('active');
13334
14304
  }
13335
14305
  // 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
14306
  if (group === 'intelligence') {
13345
14307
  if (tab === 'graph') refreshGraph();
13346
14308
  if (tab === 'memory') refreshMemory();
14309
+ if (tab === 'health') {
14310
+ if (typeof refreshMemoryHealth === 'function') refreshMemoryHealth();
14311
+ if (typeof refreshClaims === 'function') refreshClaims();
14312
+ if (typeof refreshRoutingAudit === 'function') refreshRoutingAudit();
14313
+ }
14314
+ if (tab === 'learning' && typeof refreshSelfImprove === 'function') refreshSelfImprove();
13347
14315
  }
13348
14316
  if (group === 'settings') {
13349
14317
  if (tab === 'integrations') refreshSalesforce();
13350
14318
  if (tab === 'remote') refreshRemoteAccess();
13351
14319
  if (tab === 'security') refreshAuthSessions();
13352
- if (tab === 'notifications') refreshDigestSettings();
14320
+ if (tab === 'projects' && typeof refreshProjects === 'function') refreshProjects();
14321
+ if (tab === 'logs' && typeof refreshLogs === 'function') refreshLogs();
14322
+ if (tab === 'advanced' && typeof refreshDigestSettings === 'function') refreshDigestSettings();
14323
+ }
14324
+ if (group === 'team') {
14325
+ if (tab === 'activity' && typeof renderTeamStatus === 'function') renderTeamStatus();
14326
+ if (tab === 'goals' && typeof refreshGoalsProgress === 'function') refreshGoalsProgress();
13353
14327
  }
13354
14328
  }
13355
14329
 
@@ -14416,7 +15390,7 @@ async function refreshSessions() {
14416
15390
  var s = d[key];
14417
15391
  var friendly = friendlySession(key);
14418
15392
  var safeKey = esc(key).replace(/'/g, '');
14419
- html += '<div class="session-card" style="cursor:pointer" onclick="viewSession(\\x27' + encodeURIComponent(key) + '\\x27)">'
15393
+ html += '<div class="session-card clickable-row" onclick="viewSession(\\x27' + encodeURIComponent(key) + '\\x27)">'
14420
15394
  + '<div class="session-card-header">'
14421
15395
  + '<span class="session-card-icon">' + friendly.icon + '</span>'
14422
15396
  + '<span class="session-card-name">' + esc(friendly.label) + '</span>'
@@ -14425,6 +15399,7 @@ async function refreshSessions() {
14425
15399
  + '<div class="session-card-meta">Last active: ' + timeAgo(s.timestamp) + '</div>'
14426
15400
  + '<div class="session-card-meta" style="font-family:monospace;font-size:10px">' + esc(key) + '</div>'
14427
15401
  + '<div class="session-card-actions">'
15402
+ + '<button class="btn-sm btn-primary" onclick="event.stopPropagation();resumeSession(\\x27' + encodeURIComponent(key) + '\\x27)" title="Open in chat">Resume</button>'
14428
15403
  + '<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
15404
  + '</div></div>';
14430
15405
  }
@@ -14433,6 +15408,17 @@ async function refreshSessions() {
14433
15408
  } catch(e) { }
14434
15409
  }
14435
15410
 
15411
+ function resumeSession(encodedKey) {
15412
+ var key = decodeURIComponent(encodedKey);
15413
+ // The dashboard chat session is fixed at "dashboard:web" today, so we
15414
+ // navigate to chat and surface a hint. If/when chat gains multi-session
15415
+ // support, this is the place to bind the active session id.
15416
+ navigateTo('chat');
15417
+ var indicator = document.getElementById('chat-session-indicator');
15418
+ if (indicator) indicator.textContent = key;
15419
+ toast('Switched to ' + (friendlySession(key).label || key), 'info');
15420
+ }
15421
+
14436
15422
  async function viewSession(encodedKey) {
14437
15423
  var key = decodeURIComponent(encodedKey);
14438
15424
  var panel = document.getElementById('panel-sessions');
@@ -17229,6 +18215,12 @@ function updateBuilderMode() {
17229
18215
  var type = (document.getElementById('builder-type') || {}).value || 'skill';
17230
18216
  var title = document.getElementById('builder-page-title');
17231
18217
  var drawer = document.getElementById('builder-skills-drawer');
18218
+ var preview = document.getElementById('builder-preview');
18219
+ var canvasHost = document.getElementById('builder-canvas-host');
18220
+ var picker = document.getElementById('builder-canvas-picker');
18221
+ var validateBtn = document.getElementById('builder-canvas-validate-btn');
18222
+ var dryrunBtn = document.getElementById('builder-canvas-dryrun-btn');
18223
+ var paneTitle = document.getElementById('builder-right-pane-title');
17232
18224
 
17233
18225
  if (type === 'skill') {
17234
18226
  if (title) title.textContent = 'Skill Studio';
@@ -17239,6 +18231,688 @@ function updateBuilderMode() {
17239
18231
  if (drawer) drawer.style.display = 'none';
17240
18232
  document.getElementById('builder-input').placeholder = 'Describe what you want to build...';
17241
18233
  }
18234
+
18235
+ // Canvas mode for cron + workflow types
18236
+ var canvasMode = (type === 'cron' || type === 'workflow');
18237
+ var testBtn = document.getElementById('builder-canvas-test-btn');
18238
+ if (canvasMode) {
18239
+ if (preview) preview.style.display = 'none';
18240
+ if (canvasHost) canvasHost.style.display = 'flex';
18241
+ if (picker) picker.style.display = '';
18242
+ if (validateBtn) validateBtn.style.display = '';
18243
+ if (dryrunBtn) dryrunBtn.style.display = '';
18244
+ if (testBtn) testBtn.style.display = '';
18245
+ if (paneTitle) paneTitle.textContent = (type === 'cron' ? 'Crons' : 'Workflows');
18246
+ refreshBuilderCanvasPicker(type);
18247
+ } else {
18248
+ if (preview) preview.style.display = '';
18249
+ if (canvasHost) canvasHost.style.display = 'none';
18250
+ if (picker) picker.style.display = 'none';
18251
+ if (validateBtn) validateBtn.style.display = 'none';
18252
+ if (dryrunBtn) dryrunBtn.style.display = 'none';
18253
+ if (testBtn) testBtn.style.display = 'none';
18254
+ if (paneTitle) paneTitle.textContent = 'Live Preview';
18255
+ closeBuilderCanvas();
18256
+ }
18257
+ }
18258
+
18259
+ // ── Builder visual canvas (Phase 1: read-only, agent edits via MCP tools) ──
18260
+
18261
+ var _builderCanvasEditor = null;
18262
+ var _builderCanvasOpenId = null;
18263
+ var _builderCanvasLastWorkflow = null;
18264
+ var _builderDrawflowLoading = null;
18265
+
18266
+ function _ensureDrawflowLoaded() {
18267
+ if (window.Drawflow) return Promise.resolve();
18268
+ if (_builderDrawflowLoading) return _builderDrawflowLoading;
18269
+ _builderDrawflowLoading = new Promise(function(resolve, reject) {
18270
+ var s = document.createElement('script');
18271
+ s.src = '/static/drawflow.min.js';
18272
+ s.onload = function() { resolve(); };
18273
+ s.onerror = function() { reject(new Error('Failed to load Drawflow')); };
18274
+ document.head.appendChild(s);
18275
+ });
18276
+ return _builderDrawflowLoading;
18277
+ }
18278
+
18279
+ async function refreshBuilderCanvasPicker(type) {
18280
+ var picker = document.getElementById('builder-canvas-picker');
18281
+ if (!picker) return;
18282
+ try {
18283
+ var r = await apiFetch('/api/builder/workflows');
18284
+ var d = await r.json();
18285
+ var items = (d.workflows || []).filter(function(w) { return w.origin === type; });
18286
+ var opts = '<option value="">' + (items.length ? '— pick a ' + type + ' —' : '(none yet)') + '</option>';
18287
+ for (var i = 0; i < items.length; i++) {
18288
+ var w = items[i];
18289
+ var lbl = w.name + (w.schedule ? ' · ' + w.schedule : '') + (w.enabled ? '' : ' · off');
18290
+ opts += '<option value="' + esc(w.id) + '">' + esc(lbl) + '</option>';
18291
+ }
18292
+ picker.innerHTML = opts;
18293
+ if (_builderCanvasOpenId) picker.value = _builderCanvasOpenId;
18294
+ } catch (err) {
18295
+ picker.innerHTML = '<option value="">(failed to load)</option>';
18296
+ }
18297
+ }
18298
+
18299
+ async function openBuilderWorkflow(id) {
18300
+ if (!id) { closeBuilderCanvas(); return; }
18301
+ try {
18302
+ await _ensureDrawflowLoaded();
18303
+ var r = await apiFetch('/api/builder/workflows/' + encodeURIComponent(id));
18304
+ if (!r.ok) {
18305
+ var msg = await r.json().catch(function() { return {}; });
18306
+ toast('Failed to open: ' + (msg.error || r.status), 'error');
18307
+ return;
18308
+ }
18309
+ var d = await r.json();
18310
+ _builderCanvasOpenId = id;
18311
+ _builderCanvasLastWorkflow = d.workflow;
18312
+ _renderBuilderCanvas(d.drawflow);
18313
+
18314
+ var idEl = document.getElementById('builder-canvas-id');
18315
+ if (idEl) idEl.textContent = id;
18316
+
18317
+ var banner = document.getElementById('builder-canvas-banner');
18318
+ if (banner) {
18319
+ var issues = (d.validation && d.validation.issues) || [];
18320
+ var errors = issues.filter(function(i) { return i.severity === 'error'; });
18321
+ if (errors.length) {
18322
+ banner.style.display = '';
18323
+ banner.style.background = 'rgba(255,80,80,0.12)';
18324
+ banner.style.color = 'var(--red)';
18325
+ banner.textContent = errors.length + ' validation error' + (errors.length === 1 ? '' : 's') + ' — open Validate for details';
18326
+ } else {
18327
+ banner.style.display = 'none';
18328
+ }
18329
+ }
18330
+ } catch (err) {
18331
+ toast('Canvas error: ' + err, 'error');
18332
+ }
18333
+ }
18334
+
18335
+ function _renderBuilderCanvas(drawflowData) {
18336
+ var host = document.getElementById('builder-canvas');
18337
+ if (!host) return;
18338
+ // Hide empty-state CTA — there's a workflow open now.
18339
+ var empty = document.getElementById('builder-canvas-empty');
18340
+ if (empty) empty.style.display = 'none';
18341
+ // Tear down previous editor
18342
+ if (_builderCanvasEditor) {
18343
+ try { _builderCanvasEditor.clear(); } catch (e) { /* ignore */ }
18344
+ host.innerHTML = '';
18345
+ }
18346
+ var editor = new window.Drawflow(host);
18347
+ editor.reroute = true;
18348
+ editor.editor_mode = 'edit';
18349
+ editor.start();
18350
+ try {
18351
+ editor.import(drawflowData || { drawflow: { Home: { data: {} } } });
18352
+ _decorateBuilderNodes(host, _builderCanvasLastWorkflow);
18353
+ } catch (err) {
18354
+ host.innerHTML = '<div style="padding:24px;color:var(--red)">Failed to render canvas: ' + esc(String(err)) + '</div>';
18355
+ }
18356
+ _builderCanvasEditor = editor;
18357
+ _bindBuilderCanvasEvents(editor);
18358
+ }
18359
+
18360
+ var _builderSaveTimer = null;
18361
+ var _builderSavePending = false;
18362
+ var _builderRecentSaveTokens = new Set();
18363
+
18364
+ function _bindBuilderCanvasEvents(editor) {
18365
+ // Drawflow event names: nodeMoved, connectionCreated, connectionRemoved,
18366
+ // nodeDataChanged, nodeRemoved.
18367
+ ['nodeMoved', 'connectionCreated', 'connectionRemoved', 'nodeDataChanged', 'nodeRemoved'].forEach(function(evt) {
18368
+ try {
18369
+ editor.on(evt, function() {
18370
+ if (evt === 'nodeMoved') _scheduleBuilderSave(800);
18371
+ else _scheduleBuilderSave(300);
18372
+ });
18373
+ } catch (e) { /* drawflow always exposes .on, but be safe */ }
18374
+ });
18375
+ try {
18376
+ editor.on('nodeSelected', function(nodeId) { _openNodeConfigPanel(nodeId); });
18377
+ editor.on('nodeUnselected', function() { _closeNodeConfigPanel(); });
18378
+ } catch (e) { /* */ }
18379
+ }
18380
+
18381
+ function _scheduleBuilderSave(delay) {
18382
+ if (_builderSaveTimer) clearTimeout(_builderSaveTimer);
18383
+ _builderSaveTimer = setTimeout(function() { _flushBuilderSave(); }, delay || 500);
18384
+ }
18385
+
18386
+ async function _flushBuilderSave() {
18387
+ if (!_builderCanvasEditor || !_builderCanvasOpenId) return;
18388
+ if (_builderSavePending) {
18389
+ _scheduleBuilderSave(400);
18390
+ return;
18391
+ }
18392
+ _builderSavePending = true;
18393
+ _setBuilderSaveStatus('saving');
18394
+ var saveToken = 'sv_' + Date.now() + '_' + Math.random().toString(36).slice(2, 8);
18395
+ _builderRecentSaveTokens.add(saveToken);
18396
+ setTimeout(function() { _builderRecentSaveTokens.delete(saveToken); }, 5000);
18397
+ try {
18398
+ var data = _builderCanvasEditor.export();
18399
+ var r = await apiJson('POST', '/api/builder/workflows/' + encodeURIComponent(_builderCanvasOpenId) + '/save-from-drawflow', { drawflow: data, saveToken: saveToken });
18400
+ if (r.error) {
18401
+ if (r.validation && r.validation.issues) {
18402
+ _setBuilderSaveStatus('error', r.validation.issues.length + ' validation error' + (r.validation.issues.length === 1 ? '' : 's'));
18403
+ } else {
18404
+ _setBuilderSaveStatus('error', r.error);
18405
+ }
18406
+ } else {
18407
+ _setBuilderSaveStatus('saved');
18408
+ // Refresh banner from validation if warnings
18409
+ _updateBuilderBannerFromValidation(r.validation);
18410
+ }
18411
+ } catch (err) {
18412
+ _setBuilderSaveStatus('error', String(err));
18413
+ } finally {
18414
+ _builderSavePending = false;
18415
+ }
18416
+ }
18417
+
18418
+ function _setBuilderSaveStatus(state, detail) {
18419
+ var el = document.getElementById('builder-canvas-status');
18420
+ if (!el) return;
18421
+ if (state === 'saving') { el.textContent = 'Saving…'; el.style.color = 'var(--text-muted)'; }
18422
+ else if (state === 'saved') { el.textContent = 'Saved'; el.style.color = 'var(--green, #4caf50)'; setTimeout(function() { if (el.textContent === 'Saved') el.textContent = ''; }, 1500); }
18423
+ else if (state === 'error') { el.textContent = 'Save error: ' + (detail || ''); el.style.color = 'var(--red)'; }
18424
+ }
18425
+
18426
+ function _updateBuilderBannerFromValidation(v) {
18427
+ var banner = document.getElementById('builder-canvas-banner');
18428
+ if (!banner || !v) return;
18429
+ var issues = v.issues || [];
18430
+ var errors = issues.filter(function(i) { return i.severity === 'error'; });
18431
+ var warnings = issues.filter(function(i) { return i.severity === 'warning'; });
18432
+ if (errors.length) {
18433
+ banner.style.display = '';
18434
+ banner.style.background = 'rgba(255,80,80,0.12)';
18435
+ banner.style.color = 'var(--red)';
18436
+ banner.textContent = errors.length + ' validation error' + (errors.length === 1 ? '' : 's') + ' — open Validate for details';
18437
+ } else if (warnings.length) {
18438
+ banner.style.display = '';
18439
+ banner.style.background = 'rgba(240,180,0,0.12)';
18440
+ banner.style.color = '#a37100';
18441
+ banner.textContent = warnings.length + ' warning' + (warnings.length === 1 ? '' : 's') + ' — Validate for details';
18442
+ } else {
18443
+ banner.style.display = 'none';
18444
+ }
18445
+ }
18446
+
18447
+ function _decorateBuilderNodes(host, wf) {
18448
+ if (!wf || !_builderCanvasEditor) return;
18449
+ // Pull each Drawflow node's data via the editor (which preserves the
18450
+ // stepId we set in stepToNodeData on the server side). Earlier code tried
18451
+ // to recover stepId from a df-stepId input, which Drawflow doesn't emit
18452
+ // unless you use templates — so every node decorated with the same step.
18453
+ // Now we look the step up by stepId directly.
18454
+ var byStepId = {};
18455
+ for (var i = 0; i < wf.steps.length; i++) byStepId[wf.steps[i].id] = wf.steps[i];
18456
+ var allData = _builderCanvasEditor.export();
18457
+ var nodeData = (allData && allData.drawflow && allData.drawflow.Home && allData.drawflow.Home.data) || {};
18458
+ var nodes = host.querySelectorAll('.drawflow-node');
18459
+ nodes.forEach(function(nodeEl) {
18460
+ var contentEl = nodeEl.querySelector('.drawflow_content_node');
18461
+ if (!contentEl || contentEl.dataset._decorated) return;
18462
+ contentEl.dataset._decorated = '1';
18463
+ var numericId = (nodeEl.id || '').replace(/^node-/, '');
18464
+ var data = (nodeData[numericId] && nodeData[numericId].data) || {};
18465
+ var stepId = data.stepId || null;
18466
+ var step = stepId ? byStepId[stepId] : null;
18467
+ var kind = (data.kind || (step && step.kind) || 'prompt');
18468
+ var title = step ? step.id : (stepId || '?');
18469
+ var body = _summarizeStep(step || data, kind);
18470
+ contentEl.innerHTML =
18471
+ '<div style="font-weight:600;font-size:12px;margin-bottom:4px;color:#fff;text-transform:lowercase">' + esc(kind) + ' &middot; ' + esc(title) + '</div>' +
18472
+ '<div style="font-size:11px;color:rgba(255,255,255,0.85);line-height:1.35;max-height:80px;overflow:hidden">' + esc(body) + '</div>';
18473
+ });
18474
+ }
18475
+
18476
+ function _summarizeStep(step, kind) {
18477
+ if (!step) return '';
18478
+ if (kind === 'mcp' && step.mcp) return step.mcp.server + '.' + step.mcp.tool;
18479
+ if (kind === 'channel' && step.channel) return step.channel.channel + ' → ' + step.channel.target;
18480
+ if (kind === 'transform' && step.transform) return step.transform.expression;
18481
+ if (kind === 'conditional' && step.conditional) return 'if ' + step.conditional.condition;
18482
+ if (kind === 'loop' && step.loop) return 'for each ' + step.loop.items;
18483
+ return (step.prompt || '').slice(0, 200);
18484
+ }
18485
+
18486
+ function closeBuilderCanvas() {
18487
+ _builderCanvasOpenId = null;
18488
+ _builderCanvasLastWorkflow = null;
18489
+ if (_builderCanvasEditor) {
18490
+ try { _builderCanvasEditor.clear(); } catch (e) { /* ignore */ }
18491
+ _builderCanvasEditor = null;
18492
+ }
18493
+ var host = document.getElementById('builder-canvas');
18494
+ if (host) host.innerHTML = '';
18495
+ var idEl = document.getElementById('builder-canvas-id');
18496
+ if (idEl) idEl.textContent = '';
18497
+ var banner = document.getElementById('builder-canvas-banner');
18498
+ if (banner) banner.style.display = 'none';
18499
+ // Reset picker if it has a stale selection
18500
+ var picker = document.getElementById('builder-canvas-picker');
18501
+ if (picker && picker.value) picker.value = '';
18502
+ // Close config panel if it was open
18503
+ if (typeof _closeNodeConfigPanel === 'function') _closeNodeConfigPanel();
18504
+ // Show empty-state CTA
18505
+ var empty = document.getElementById('builder-canvas-empty');
18506
+ if (empty) empty.style.display = 'flex';
18507
+ }
18508
+
18509
+ async function validateBuilderCanvas() {
18510
+ if (!_builderCanvasOpenId) { toast('Open a workflow first', 'info'); return; }
18511
+ try {
18512
+ var r = await apiJson('POST', '/api/builder/workflows/' + encodeURIComponent(_builderCanvasOpenId) + '/validate', {});
18513
+ if (!r.issues || r.issues.length === 0) { toast('OK — no issues', 'success'); return; }
18514
+ var lines = r.issues.map(function(i) { return '[' + i.severity + '] ' + (i.stepId ? '(' + i.stepId + ') ' : '') + i.message; });
18515
+ alert('Validation:\\n\\n' + lines.join('\\n'));
18516
+ } catch (err) { toast('Validate failed: ' + err, 'error'); }
18517
+ }
18518
+
18519
+ async function dryRunBuilderCanvas() {
18520
+ if (!_builderCanvasOpenId) { toast('Open a workflow first', 'info'); return; }
18521
+ try {
18522
+ var r = await apiJson('POST', '/api/builder/workflows/' + encodeURIComponent(_builderCanvasOpenId) + '/dry-run', {});
18523
+ var lines = [];
18524
+ lines.push(r.ok ? 'DRY RUN — would execute:' : 'DRY RUN (validation issues found):');
18525
+ lines.push('');
18526
+ for (var i = 0; i < r.steps.length; i++) {
18527
+ var s = r.steps[i];
18528
+ lines.push('[wave ' + s.wave + '] ' + s.description);
18529
+ for (var k = 0; k < s.warnings.length; k++) lines.push(' ⚠ ' + s.warnings[k]);
18530
+ }
18531
+ if (r.estimatedTokens) lines.push('', '~' + r.estimatedTokens.total.toLocaleString() + ' tokens estimate (' + r.estimatedTokens.promptSteps + ' prompt step' + (r.estimatedTokens.promptSteps === 1 ? '' : 's') + ')');
18532
+ if (r.notes && r.notes.length) { lines.push(''); for (var n = 0; n < r.notes.length; n++) lines.push(r.notes[n]); }
18533
+ alert(lines.join('\\n'));
18534
+ } catch (err) { toast('Dry-run failed: ' + err, 'error'); }
18535
+ }
18536
+
18537
+ function _handleBuilderEvent(evt) {
18538
+ if (!evt || !evt.workflowId) return;
18539
+ // Run events are routed to the test-run handler.
18540
+ if (evt.type && evt.type.indexOf && evt.type.indexOf('run:') === 0) {
18541
+ _onRunEvent(evt);
18542
+ return;
18543
+ }
18544
+ // Suppress echoes of our own saves: server reflects the saveToken back.
18545
+ var token = evt.payload && evt.payload.saveToken;
18546
+ if (token && _builderRecentSaveTokens.has(token)) {
18547
+ _builderRecentSaveTokens.delete(token);
18548
+ return;
18549
+ }
18550
+ // Re-render if event is for the open workflow.
18551
+ if (evt.workflowId === _builderCanvasOpenId) {
18552
+ if (evt.type === 'workflow:deleted') { closeBuilderCanvas(); refreshBuilderCanvasPicker(document.getElementById('builder-type').value); return; }
18553
+ openBuilderWorkflow(_builderCanvasOpenId);
18554
+ }
18555
+ // Refresh picker when list changes
18556
+ if (evt.type === 'workflow:created' || evt.type === 'workflow:deleted' || evt.type === 'workflow:renamed') {
18557
+ refreshBuilderCanvasPicker(document.getElementById('builder-type').value);
18558
+ }
18559
+ }
18560
+
18561
+ // ── Node palette (Phase 2b) ─────────────────────────────────────
18562
+
18563
+ var _builderPaletteOpen = false;
18564
+
18565
+ function toggleBuilderPalette() {
18566
+ var pop = document.getElementById('builder-palette-pop');
18567
+ if (!pop) return;
18568
+ _builderPaletteOpen = !_builderPaletteOpen;
18569
+ pop.style.display = _builderPaletteOpen ? '' : 'none';
18570
+ }
18571
+
18572
+ function _builderAddNodeOfKind(kind) {
18573
+ if (!_builderCanvasEditor) return;
18574
+ toggleBuilderPalette();
18575
+ var canvas = document.getElementById('builder-canvas');
18576
+ if (!canvas) return;
18577
+ var rect = canvas.getBoundingClientRect();
18578
+ var posX = (rect.width / 2) - 100;
18579
+ var posY = (rect.height / 2) - 40;
18580
+ var stepId = _generateUniqueStepId(kind);
18581
+ var data = _defaultDataForKind(kind, stepId);
18582
+ var className = 'cl-node cl-node-' + kind;
18583
+ var nodeId = _builderCanvasEditor.addNode(_nodeNameForKind(kind), 1, 1, posX, posY, className, data, '');
18584
+ // Drawflow needs a tick before we can decorate
18585
+ setTimeout(function() {
18586
+ _decorateBuilderNodeById(nodeId, kind, data);
18587
+ _scheduleBuilderSave(200);
18588
+ }, 50);
18589
+ }
18590
+
18591
+ function _nodeNameForKind(kind) {
18592
+ switch (kind) {
18593
+ case 'mcp': return 'MCP Tool';
18594
+ case 'channel': return 'Channel';
18595
+ case 'transform': return 'Transform';
18596
+ case 'conditional': return 'Conditional';
18597
+ case 'loop': return 'Loop';
18598
+ default: return 'Prompt';
18599
+ }
18600
+ }
18601
+
18602
+ function _defaultDataForKind(kind, stepId) {
18603
+ var base = { stepId: stepId, prompt: '', tier: 1, maxTurns: 15, kind: kind };
18604
+ if (kind === 'prompt') return Object.assign({}, base, { prompt: 'Describe what this step should do.' });
18605
+ if (kind === 'mcp') return Object.assign({}, base, { mcp: { server: '', tool: '', inputs: {} } });
18606
+ if (kind === 'channel') return Object.assign({}, base, { channel: { channel: 'slack', target: '#me', content: '' } });
18607
+ if (kind === 'transform') return Object.assign({}, base, { transform: { expression: 'input' } });
18608
+ if (kind === 'conditional') return Object.assign({}, base, { conditional: { condition: 'true', trueNext: [], falseNext: [] } });
18609
+ if (kind === 'loop') return Object.assign({}, base, { loop: { items: 'input', bodyStepIds: [] } });
18610
+ return base;
18611
+ }
18612
+
18613
+ function _generateUniqueStepId(kind) {
18614
+ var used = new Set();
18615
+ if (_builderCanvasEditor) {
18616
+ var data = _builderCanvasEditor.export();
18617
+ var nodes = data && data.drawflow && data.drawflow.Home && data.drawflow.Home.data ? data.drawflow.Home.data : {};
18618
+ for (var k in nodes) {
18619
+ var d = nodes[k].data || {};
18620
+ if (d.stepId) used.add(d.stepId);
18621
+ }
18622
+ }
18623
+ var prefix = kind.slice(0, 4);
18624
+ var i = 1;
18625
+ while (used.has(prefix + i)) i++;
18626
+ return prefix + i;
18627
+ }
18628
+
18629
+ function _decorateBuilderNodeById(nodeId, kind, data) {
18630
+ var nodeEl = document.querySelector('.drawflow-node[id="node-' + nodeId + '"]');
18631
+ if (!nodeEl) return;
18632
+ var content = nodeEl.querySelector('.drawflow_content_node');
18633
+ if (!content) return;
18634
+ content.dataset._decorated = '1';
18635
+ content.innerHTML =
18636
+ '<div style="font-weight:600;font-size:12px;margin-bottom:4px;color:#fff;text-transform:lowercase">' + esc(kind) + ' · ' + esc(data.stepId) + '</div>' +
18637
+ '<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>';
18638
+ }
18639
+
18640
+ function _summarizeStepData(d, kind) {
18641
+ if (!d) return '';
18642
+ if (kind === 'mcp' && d.mcp) return (d.mcp.server || '?') + '.' + (d.mcp.tool || '?');
18643
+ if (kind === 'channel' && d.channel) return d.channel.channel + ' → ' + d.channel.target;
18644
+ if (kind === 'transform' && d.transform) return d.transform.expression || '';
18645
+ if (kind === 'conditional' && d.conditional) return 'if ' + (d.conditional.condition || '');
18646
+ if (kind === 'loop' && d.loop) return 'for each ' + (d.loop.items || '');
18647
+ return (d.prompt || '').slice(0, 200);
18648
+ }
18649
+
18650
+ // ── Per-node config panel (Phase 2c) ───────────────────────────
18651
+
18652
+ var _builderConfigOpenNodeId = null;
18653
+ var _builderMcpToolsCache = null;
18654
+
18655
+ function _openNodeConfigPanel(nodeId) {
18656
+ if (!_builderCanvasEditor) return;
18657
+ _builderConfigOpenNodeId = nodeId;
18658
+ var node = _builderCanvasEditor.getNodeFromId(nodeId);
18659
+ if (!node) return;
18660
+ var data = node.data || {};
18661
+ var kind = data.kind || 'prompt';
18662
+ var panel = document.getElementById('builder-config-panel');
18663
+ if (!panel) return;
18664
+ panel.style.display = '';
18665
+ panel.innerHTML = _renderConfigPanel(nodeId, kind, data);
18666
+ _bindConfigPanelInputs(nodeId, kind);
18667
+ if (kind === 'mcp') _populateMcpDropdowns();
18668
+ }
18669
+
18670
+ function _closeNodeConfigPanel() {
18671
+ _builderConfigOpenNodeId = null;
18672
+ var panel = document.getElementById('builder-config-panel');
18673
+ if (panel) panel.style.display = 'none';
18674
+ }
18675
+
18676
+ function _renderConfigPanel(nodeId, kind, d) {
18677
+ var common = (
18678
+ '<div class="cfg-row"><label>Step id</label><input type="text" data-cfg="stepId" value="' + esc(d.stepId || '') + '"></div>' +
18679
+ '<div class="cfg-row"><label>Tier</label><input type="number" min="1" max="5" data-cfg="tier" value="' + (d.tier || 1) + '"></div>' +
18680
+ '<div class="cfg-row"><label>Max turns</label><input type="number" min="1" data-cfg="maxTurns" value="' + (d.maxTurns || 15) + '"></div>' +
18681
+ '<div class="cfg-row"><label>Model</label><input type="text" data-cfg="model" placeholder="default" value="' + esc(d.model || '') + '"></div>'
18682
+ );
18683
+ var kindHtml = '';
18684
+ if (kind === 'prompt') {
18685
+ kindHtml = '<div class="cfg-row"><label>Prompt</label><textarea data-cfg="prompt" rows="6">' + esc(d.prompt || '') + '</textarea></div>';
18686
+ } else if (kind === 'mcp') {
18687
+ var m = d.mcp || {};
18688
+ kindHtml = (
18689
+ '<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>' +
18690
+ '<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>' +
18691
+ '<div class="cfg-row"><label>Inputs (JSON)</label><textarea data-cfg="mcp.inputs" rows="4">' + esc(JSON.stringify(m.inputs || {}, null, 2)) + '</textarea></div>' +
18692
+ '<div class="cfg-row"><label>Description</label><textarea data-cfg="prompt" rows="3">' + esc(d.prompt || '') + '</textarea></div>'
18693
+ );
18694
+ } else if (kind === 'channel') {
18695
+ var c = d.channel || {};
18696
+ kindHtml = (
18697
+ '<div class="cfg-row"><label>Channel</label><select data-cfg="channel.channel">' +
18698
+ ['discord','slack','telegram','whatsapp','email','webhook'].map(function(k) { return '<option' + (c.channel === k ? ' selected' : '') + '>' + k + '</option>'; }).join('') +
18699
+ '</select></div>' +
18700
+ '<div class="cfg-row"><label>Target</label><input type="text" data-cfg="channel.target" placeholder="#channel, user id, email…" value="' + esc(c.target || '') + '"></div>' +
18701
+ '<div class="cfg-row"><label>Content</label><textarea data-cfg="channel.content" rows="6">' + esc(c.content || '') + '</textarea></div>'
18702
+ );
18703
+ } else if (kind === 'transform') {
18704
+ var t = d.transform || {};
18705
+ 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>';
18706
+ } else if (kind === 'conditional') {
18707
+ var co = d.conditional || {};
18708
+ kindHtml = (
18709
+ '<div class="cfg-row"><label>Condition</label><textarea data-cfg="conditional.condition" rows="3" placeholder="JS expression — truthy/falsy">' + esc(co.condition || '') + '</textarea></div>' +
18710
+ '<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>' +
18711
+ '<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>'
18712
+ );
18713
+ } else if (kind === 'loop') {
18714
+ var l = d.loop || {};
18715
+ kindHtml = (
18716
+ '<div class="cfg-row"><label>Items</label><input type="text" data-cfg="loop.items" placeholder="JS expression returning iterable" value="' + esc(l.items || '') + '"></div>' +
18717
+ '<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>'
18718
+ );
18719
+ }
18720
+ var lastRun = _builderRunStepResults && _builderRunStepResults[d.stepId || ''];
18721
+ var lastRunBtn = lastRun
18722
+ ? '<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>'
18723
+ : '';
18724
+ return (
18725
+ '<div style="display:flex;align-items:center;gap:8px;padding:10px 14px;border-bottom:1px solid var(--border);font-weight:600;font-size:12px">' +
18726
+ '<span>' + esc(kind) + ' step</span>' +
18727
+ '<span style="flex:1"></span>' +
18728
+ '<button onclick="_deleteSelectedNode()" title="Delete this node" style="background:none;border:none;color:var(--red);cursor:pointer;font-size:16px">×</button>' +
18729
+ '</div>' +
18730
+ '<div style="padding:10px 14px;flex:1;overflow-y:auto" id="builder-config-fields">' +
18731
+ common +
18732
+ '<div style="border-top:1px dashed var(--border);margin:10px 0"></div>' +
18733
+ kindHtml +
18734
+ (lastRunBtn ? '<div style="border-top:1px dashed var(--border);margin:14px 0 8px"></div>' + lastRunBtn : '') +
18735
+ '</div>'
18736
+ );
18737
+ }
18738
+
18739
+ function _bindConfigPanelInputs(nodeId) {
18740
+ var panel = document.getElementById('builder-config-panel');
18741
+ if (!panel) return;
18742
+ panel.querySelectorAll('[data-cfg]').forEach(function(el) {
18743
+ el.addEventListener('change', function() { _applyConfigField(nodeId, el); });
18744
+ if (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT') {
18745
+ el.addEventListener('blur', function() { _applyConfigField(nodeId, el); });
18746
+ }
18747
+ });
18748
+ }
18749
+
18750
+ function _applyConfigField(nodeId, el) {
18751
+ if (!_builderCanvasEditor) return;
18752
+ var node = _builderCanvasEditor.getNodeFromId(nodeId);
18753
+ if (!node) return;
18754
+ var pathStr = el.getAttribute('data-cfg') || '';
18755
+ var pathParts = pathStr.split('.');
18756
+ var raw = el.value;
18757
+ var parsed;
18758
+ if (pathStr.endsWith('.inputs')) {
18759
+ try { parsed = raw ? JSON.parse(raw) : {}; } catch (e) { toast('Invalid JSON in inputs', 'error'); return; }
18760
+ } else if (pathStr === 'tier' || pathStr === 'maxTurns') {
18761
+ parsed = Number(raw) || 0;
18762
+ } else if (pathStr === 'conditional.trueNext' || pathStr === 'conditional.falseNext' || pathStr === 'loop.bodyStepIds') {
18763
+ parsed = raw ? raw.split(',').map(function(s) { return s.trim(); }).filter(Boolean) : [];
18764
+ } else {
18765
+ parsed = raw;
18766
+ }
18767
+ var newData = JSON.parse(JSON.stringify(node.data || {}));
18768
+ var cur = newData;
18769
+ for (var i = 0; i < pathParts.length - 1; i++) {
18770
+ if (!cur[pathParts[i]] || typeof cur[pathParts[i]] !== 'object') cur[pathParts[i]] = {};
18771
+ cur = cur[pathParts[i]];
18772
+ }
18773
+ cur[pathParts[pathParts.length - 1]] = parsed;
18774
+ _builderCanvasEditor.updateNodeDataFromId(nodeId, newData);
18775
+ // Re-decorate the node visually
18776
+ _decorateBuilderNodeById(nodeId, newData.kind || 'prompt', newData);
18777
+ _scheduleBuilderSave(300);
18778
+ }
18779
+
18780
+ function _deleteSelectedNode() {
18781
+ if (!_builderCanvasEditor || !_builderConfigOpenNodeId) return;
18782
+ if (!confirm('Delete this node?')) return;
18783
+ _builderCanvasEditor.removeNodeId('node-' + _builderConfigOpenNodeId);
18784
+ _closeNodeConfigPanel();
18785
+ _scheduleBuilderSave(150);
18786
+ }
18787
+
18788
+ // ── Test runs (Phase 2d/2e) ─────────────────────────────────────
18789
+
18790
+ var _builderActiveRunId = null;
18791
+ var _builderRunStepResults = {};
18792
+
18793
+ async function testBuilderCanvas() {
18794
+ if (!_builderCanvasOpenId) { toast('Open a workflow first', 'info'); return; }
18795
+ if (_builderActiveRunId) { toast('A test is already running', 'info'); return; }
18796
+ // Always flush any pending save so the test sees the latest graph
18797
+ if (_builderSaveTimer) { clearTimeout(_builderSaveTimer); _builderSaveTimer = null; await _flushBuilderSave(); }
18798
+ _clearRunVisualState();
18799
+ try {
18800
+ var r = await apiJson('POST', '/api/builder/workflows/' + encodeURIComponent(_builderCanvasOpenId) + '/test', { mode: 'mock' });
18801
+ if (r.error) { toast(r.error, 'error'); return; }
18802
+ _builderActiveRunId = r.runId;
18803
+ _builderRunStepResults = {};
18804
+ var cancel = document.getElementById('builder-canvas-cancel-btn');
18805
+ if (cancel) cancel.style.display = '';
18806
+ _setBuilderSaveStatus('saved'); // override status with a more useful one below
18807
+ _setRunFooter('Running test… (' + r.runId.slice(0, 8) + ')');
18808
+ } catch (err) {
18809
+ toast('Test failed to start: ' + err, 'error');
18810
+ }
18811
+ }
18812
+
18813
+ async function cancelBuilderTest() {
18814
+ if (!_builderActiveRunId) return;
18815
+ try {
18816
+ await apiJson('POST', '/api/builder/runs/' + encodeURIComponent(_builderActiveRunId) + '/cancel', {});
18817
+ } catch (err) { /* SSE will still report the cancellation */ }
18818
+ }
18819
+
18820
+ function _clearRunVisualState() {
18821
+ document.querySelectorAll('#builder-canvas .drawflow-node').forEach(function(n) {
18822
+ n.classList.remove('cl-step-running', 'cl-step-done', 'cl-step-failed', 'cl-step-skipped', 'cl-step-cancelled', 'cl-step-timeout');
18823
+ });
18824
+ }
18825
+
18826
+ function _setRunFooter(text) {
18827
+ var el = document.getElementById('builder-canvas-status');
18828
+ if (el) { el.textContent = text || ''; el.style.color = 'var(--text-muted)'; }
18829
+ }
18830
+
18831
+ function _findNodeElForStepId(stepId) {
18832
+ if (!_builderCanvasEditor) return null;
18833
+ var data = _builderCanvasEditor.export();
18834
+ var nodes = data && data.drawflow && data.drawflow.Home && data.drawflow.Home.data ? data.drawflow.Home.data : {};
18835
+ for (var k in nodes) {
18836
+ var d = nodes[k].data || {};
18837
+ if (d.stepId === stepId) return document.querySelector('.drawflow-node[id="node-' + k + '"]');
18838
+ }
18839
+ return null;
18840
+ }
18841
+
18842
+ function _onRunEvent(evt) {
18843
+ if (!evt || !evt.runId || evt.workflowId !== _builderCanvasOpenId) return;
18844
+ if (evt.type === 'run:started') {
18845
+ _setRunFooter('Running test… (' + evt.runId.slice(0, 8) + ')');
18846
+ return;
18847
+ }
18848
+ if (evt.type === 'run:step-status') {
18849
+ var p = evt.payload || {};
18850
+ var nodeEl = _findNodeElForStepId(p.stepId);
18851
+ if (nodeEl) {
18852
+ nodeEl.classList.remove('cl-step-running', 'cl-step-done', 'cl-step-failed', 'cl-step-skipped', 'cl-step-cancelled', 'cl-step-timeout');
18853
+ nodeEl.classList.add('cl-step-' + p.status);
18854
+ }
18855
+ return;
18856
+ }
18857
+ if (evt.type === 'run:step-output') {
18858
+ var po = evt.payload || {};
18859
+ _builderRunStepResults[po.stepId] = po;
18860
+ return;
18861
+ }
18862
+ if (evt.type === 'run:completed' || evt.type === 'run:cancelled') {
18863
+ var ec = (evt.payload && evt.payload.status) || (evt.type === 'run:cancelled' ? 'cancelled' : 'ok');
18864
+ var dur = (evt.payload && evt.payload.durationMs) || 0;
18865
+ _builderActiveRunId = null;
18866
+ var cancel = document.getElementById('builder-canvas-cancel-btn');
18867
+ if (cancel) cancel.style.display = 'none';
18868
+ _setRunFooter('Test ' + ec + ' in ' + dur + 'ms — click a node for output');
18869
+ return;
18870
+ }
18871
+ }
18872
+
18873
+ function showStepOutput(stepId) {
18874
+ var po = _builderRunStepResults[stepId];
18875
+ if (!po) { toast('No output for ' + stepId + ' yet', 'info'); return; }
18876
+ var lines = ['Step: ' + stepId, 'Status: ' + po.status, ''];
18877
+ if (po.error) lines.push('Error: ' + po.error, '');
18878
+ if (po.output != null) lines.push(typeof po.output === 'string' ? po.output : JSON.stringify(po.output, null, 2));
18879
+ alert(lines.join('\\n'));
18880
+ }
18881
+
18882
+ async function _populateMcpDropdowns() {
18883
+ try {
18884
+ if (!_builderMcpToolsCache) {
18885
+ // Fetch from the agent-side discovery — proxied through a simple endpoint
18886
+ // (we build a small client-side bridge by listing available servers via /api/builder/mcp-discovery)
18887
+ var r = await apiFetch('/api/builder/mcp-discovery');
18888
+ _builderMcpToolsCache = await r.json();
18889
+ }
18890
+ var serverSel = document.getElementById('cfg-mcp-server');
18891
+ var toolSel = document.getElementById('cfg-mcp-tool');
18892
+ if (!serverSel || !toolSel) return;
18893
+ var current = serverSel.value;
18894
+ var opts = '<option value="">— pick —</option>';
18895
+ var servers = (_builderMcpToolsCache && _builderMcpToolsCache.servers) || [];
18896
+ for (var i = 0; i < servers.length; i++) {
18897
+ var s = servers[i];
18898
+ opts += '<option value="' + esc(s.name) + '"' + (s.name === current ? ' selected' : '') + '>' + esc(s.name) + (s.enabled ? '' : ' (off)') + '</option>';
18899
+ }
18900
+ serverSel.innerHTML = opts;
18901
+ var rebuildTools = function() {
18902
+ var s = servers.filter(function(x) { return x.name === serverSel.value; })[0];
18903
+ var tools = (s && s.tools) || [];
18904
+ var curT = toolSel.value;
18905
+ var t = '<option value="">— pick —</option>';
18906
+ for (var j = 0; j < tools.length; j++) {
18907
+ t += '<option value="' + esc(tools[j]) + '"' + (tools[j] === curT ? ' selected' : '') + '>' + esc(tools[j]) + '</option>';
18908
+ }
18909
+ toolSel.innerHTML = t;
18910
+ };
18911
+ rebuildTools();
18912
+ serverSel.addEventListener('change', rebuildTools);
18913
+ } catch (err) {
18914
+ /* non-fatal — dropdowns just stay sparse */
18915
+ }
17242
18916
  }
17243
18917
 
17244
18918
  async function refreshBuilderSkills() {
@@ -17840,6 +19514,76 @@ function formatBytes(n) {
17840
19514
  return (n / 1024 / 1024 / 1024).toFixed(2) + ' GB';
17841
19515
  }
17842
19516
 
19517
+ async function memoryHealthAction(action) {
19518
+ var labels = { 'janitor': 'cleanup', 'rebuild-fts': 'FTS rebuild', 'fix-orphans': 'orphan fix' };
19519
+ if (!confirm('Run ' + (labels[action] || action) + ' now?')) return;
19520
+ try {
19521
+ var r = await apiJson('POST', '/api/memory/health/action', { action: action });
19522
+ if (r.error) { toast('Action failed: ' + r.error, 'error'); return; }
19523
+ var detail = '';
19524
+ if (r.result) detail = ' — ' + Object.entries(r.result).slice(0, 4).map(function(p) { return p[0] + ':' + p[1]; }).join(', ');
19525
+ if (r.report) detail = ' — orphans nulled: ' + (r.report.orphanRefsNulled || 0) + ', FTS rebuilds: ' + (r.report.ftsRebuilds || 0);
19526
+ toast(action + ' complete' + detail, 'success');
19527
+ refreshMemoryHealth();
19528
+ } catch (err) {
19529
+ toast('Action error: ' + err, 'error');
19530
+ }
19531
+ }
19532
+
19533
+ // ── Goals: inline create form ────────────────────────────────────
19534
+ function openNewGoalForm() {
19535
+ var el = document.getElementById('new-goal-form');
19536
+ if (!el) return;
19537
+ el.style.display = '';
19538
+ setTimeout(function() {
19539
+ var t = document.getElementById('new-goal-title');
19540
+ if (t) t.focus();
19541
+ }, 50);
19542
+ }
19543
+
19544
+ async function submitNewGoal() {
19545
+ var titleEl = document.getElementById('new-goal-title');
19546
+ var descEl = document.getElementById('new-goal-desc');
19547
+ var title = (titleEl?.value || '').trim();
19548
+ if (!title) { toast('Goal needs a title', 'error'); return; }
19549
+ try {
19550
+ var r = await apiJson('POST', '/api/goals', { title: title, description: (descEl?.value || '').trim() });
19551
+ if (r && r.error) { toast('Create failed: ' + r.error, 'error'); return; }
19552
+ if (titleEl) titleEl.value = '';
19553
+ if (descEl) descEl.value = '';
19554
+ document.getElementById('new-goal-form').style.display = 'none';
19555
+ toast('Goal created', 'success');
19556
+ if (typeof refreshGoals === 'function') refreshGoals();
19557
+ } catch (err) { toast('Create error: ' + err, 'error'); }
19558
+ }
19559
+
19560
+ // ── Workflows: open Builder for a brand-new workflow ─────────────
19561
+ function openBuilderForNewWorkflow() {
19562
+ navigateTo('builder');
19563
+ setTimeout(function() {
19564
+ var typeSel = document.getElementById('builder-type');
19565
+ if (typeSel) { typeSel.value = 'workflow'; updateBuilderMode(); }
19566
+ var name = prompt('Name your new workflow:');
19567
+ if (!name) return;
19568
+ apiJson('POST', '/api/builder/workflows', { name: name }).then(function(r) {
19569
+ if (r && r.error) { toast('Create failed: ' + r.error, 'error'); return; }
19570
+ if (r && r.id) {
19571
+ // Refresh picker, then open the new workflow
19572
+ refreshBuilderCanvasPicker('workflow');
19573
+ setTimeout(function() { openBuilderWorkflow(r.id); }, 200);
19574
+ }
19575
+ });
19576
+ }, 100);
19577
+ }
19578
+
19579
+ // ── Unleashed: open the start-task picker ────────────────────────
19580
+ function openStartUnleashedTask() {
19581
+ // Reuse the existing cron list — pick a cron job, run it in unleashed mode.
19582
+ // For v1 just route to Automations where the existing controls live.
19583
+ navigateTo('automations');
19584
+ toast('Pick a cron job and click "Run unleashed" — kicks off long-running mode.', 'info');
19585
+ }
19586
+
17843
19587
  async function refreshMemoryHealth() {
17844
19588
  var el = document.getElementById('memory-health-content');
17845
19589
  if (!el) return;
@@ -18555,6 +20299,131 @@ async function saveDiscordSetup() {
18555
20299
  } catch(e) { toast(String(e), 'error'); }
18556
20300
  }
18557
20301
 
20302
+ function toggleHomeRail() {
20303
+ var rail = document.getElementById('home-rail');
20304
+ if (!rail) return;
20305
+ // Mobile: open/close. Desktop: collapse/show.
20306
+ if (window.matchMedia('(max-width: 1024px)').matches) {
20307
+ rail.classList.toggle('open');
20308
+ } else {
20309
+ rail.classList.toggle('collapsed');
20310
+ }
20311
+ }
20312
+
20313
+ async function refreshHomeRail() {
20314
+ // Daemon status
20315
+ try {
20316
+ var rs = await apiFetch('/api/status');
20317
+ var ds = await rs.json();
20318
+ var pip = document.querySelector('#rail-daemon-body .agent-activity-dot');
20319
+ var label = document.querySelector('#rail-daemon-body .agent-activity span:last-child');
20320
+ if (label) label.textContent = ds.running ? 'Daemon running' : 'Daemon stopped';
20321
+ if (pip) pip.style.background = ds.running ? '#22c55e' : '#ef4444';
20322
+ var up = document.getElementById('rail-daemon-uptime');
20323
+ if (up && ds.uptimeMs) up.textContent = Math.round(ds.uptimeMs / 60000) + 'm';
20324
+ } catch { /* */ }
20325
+
20326
+ // Today's plan (compact)
20327
+ try {
20328
+ var rp = await apiFetch('/api/daily-plan');
20329
+ var dp = await rp.json();
20330
+ var planEl = document.getElementById('home-plan-content');
20331
+ if (planEl) {
20332
+ if (!dp || !dp.plan) {
20333
+ planEl.innerHTML = '<div style="font-size:12px;color:var(--text-muted)">No plan yet today.</div>';
20334
+ } else {
20335
+ var items = (dp.plan.items || []).slice(0, 4);
20336
+ if (items.length === 0) {
20337
+ planEl.innerHTML = '<div style="font-size:12px;color:var(--text-muted)">No items in today\\x27s plan.</div>';
20338
+ } else {
20339
+ planEl.innerHTML = items.map(function(it) {
20340
+ return '<div class="rail-row"><span class="label">' + esc(it.title || it.text || '') + '</span><span class="meta">' + esc(it.time || '') + '</span></div>';
20341
+ }).join('');
20342
+ }
20343
+ }
20344
+ }
20345
+ } catch {
20346
+ var pe = document.getElementById('home-plan-content');
20347
+ if (pe) pe.innerHTML = '<div style="font-size:11px;color:var(--text-muted)">No plan available.</div>';
20348
+ }
20349
+
20350
+ // Upcoming cron fires (next 3)
20351
+ try {
20352
+ var rc = await apiFetch('/api/cron');
20353
+ var dc = await rc.json();
20354
+ var jobs = (dc.jobs || []).filter(function(j) { return j.enabled && j.nextRun; });
20355
+ jobs.sort(function(a, b) { return new Date(a.nextRun).getTime() - new Date(b.nextRun).getTime(); });
20356
+ var top = jobs.slice(0, 3);
20357
+ var ue = document.getElementById('rail-upcoming');
20358
+ var uc = document.getElementById('rail-upcoming-count');
20359
+ if (uc) uc.textContent = String(jobs.length);
20360
+ if (ue) {
20361
+ ue.innerHTML = top.length ? top.map(function(j) {
20362
+ 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>';
20363
+ }).join('') : '<div style="font-size:11px;color:var(--text-muted)">Nothing scheduled soon.</div>';
20364
+ }
20365
+ } catch { /* */ }
20366
+
20367
+ // Active unleashed runs
20368
+ try {
20369
+ var ru = await apiFetch('/api/unleashed');
20370
+ var du = await ru.json();
20371
+ var active = (du.tasks || []).filter(function(t) { return t.status === 'running'; });
20372
+ var ae = document.getElementById('rail-active');
20373
+ var ac = document.getElementById('rail-active-count');
20374
+ if (ac) {
20375
+ if (active.length > 0) { ac.style.display = ''; ac.textContent = String(active.length); }
20376
+ else ac.style.display = 'none';
20377
+ }
20378
+ if (ae) {
20379
+ ae.innerHTML = active.length ? active.map(function(t) {
20380
+ 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>';
20381
+ }).join('') : '<div style="font-size:11px;color:var(--text-muted)">Nothing running.</div>';
20382
+ }
20383
+ } catch { /* */ }
20384
+
20385
+ // Time saved (rough: cron runs * 5min + activity exchanges * 2min, this week)
20386
+ try {
20387
+ var rm = await apiFetch('/api/metrics?period=week');
20388
+ var dm = await rm.json();
20389
+ var minutes = ((dm.cronRuns || 0) * 5) + ((dm.exchanges || 0) * 2);
20390
+ var ts = document.getElementById('rail-time-saved');
20391
+ if (ts) {
20392
+ 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>';
20393
+ 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>';
20394
+ }
20395
+ } catch { /* */ }
20396
+
20397
+ // Approvals (self-improve proposals + pending skills)
20398
+ try {
20399
+ var rsi = await apiFetch('/api/self-improve');
20400
+ var dsi = await rsi.json();
20401
+ var pending = (dsi.proposals || []).filter(function(p) { return p.status === 'pending'; });
20402
+ var ae2 = document.getElementById('rail-approvals');
20403
+ var ac2 = document.getElementById('rail-approvals-count');
20404
+ if (ac2) {
20405
+ if (pending.length > 0) { ac2.style.display = ''; ac2.textContent = String(pending.length); }
20406
+ else ac2.style.display = 'none';
20407
+ }
20408
+ if (ae2) {
20409
+ if (pending.length === 0) ae2.innerHTML = '<div style="font-size:11px;color:var(--text-muted)">Nothing pending.</div>';
20410
+ else ae2.innerHTML = pending.slice(0, 3).map(function(p) {
20411
+ 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>';
20412
+ }).join('');
20413
+ }
20414
+ } catch { /* */ }
20415
+ }
20416
+
20417
+ function timeUntil(iso) {
20418
+ if (!iso) return '';
20419
+ var ms = new Date(iso).getTime() - Date.now();
20420
+ if (ms < 0) return 'past';
20421
+ var min = Math.round(ms / 60000);
20422
+ if (min < 60) return 'in ' + min + 'm';
20423
+ if (min < 24 * 60) return 'in ' + Math.round(min / 60) + 'h';
20424
+ return 'in ' + Math.round(min / (24 * 60)) + 'd';
20425
+ }
20426
+
18558
20427
  async function refreshAll() {
18559
20428
  // Use batch init for core data — avoids concurrent requests that freeze the event loop
18560
20429
  try {
@@ -18563,6 +20432,8 @@ async function refreshAll() {
18563
20432
  if (d.status) refreshStatus(d.status);
18564
20433
  if (d.activity) refreshActivity(false, d.activity);
18565
20434
  if (d.office) refreshTeamNav(d.office);
20435
+ // Home rail data — fire and forget, doesn't block init render.
20436
+ if (currentPage === 'home') refreshHomeRail();
18566
20437
  if (d.version) {
18567
20438
  if (d.version.needsRestart && !_restartBannerShown) {
18568
20439
  _restartBannerShown = true;
@@ -21932,6 +23803,9 @@ try {
21932
23803
  toast('Daemon restarted \u2014 refreshing data...', 'info');
21933
23804
  setTimeout(function() { refreshAll(); }, 1500);
21934
23805
  }
23806
+ if (evt.type === 'builder') {
23807
+ try { _handleBuilderEvent(evt.data); } catch(e) { /* non-fatal */ }
23808
+ }
21935
23809
  if (evt.type === 'deep_result') {
21936
23810
  try {
21937
23811
  var container = document.getElementById('chat-messages');