aimeat 1.15.1 → 1.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/locales/en.json +35 -1
  2. package/dist/locales/fi.json +35 -1
  3. package/dist/public/css/views/agents-detail.css +135 -0
  4. package/dist/public/js/services/agent-tasks.js +11 -0
  5. package/dist/public/views/profile/agents/agent-card.js +5 -1
  6. package/dist/public/views/profile/agents/tab-quality.js +159 -0
  7. package/dist/public/views/profile/agents-tasks-subtab.js +43 -1
  8. package/dist/src/generated/api-types.d.ts +172 -3
  9. package/dist/src/generated/api-types.d.ts.map +1 -1
  10. package/dist/src/models/agent-task-schemas.d.ts +28 -0
  11. package/dist/src/models/agent-task-schemas.d.ts.map +1 -1
  12. package/dist/src/models/agent-task-schemas.js +21 -0
  13. package/dist/src/models/agent-task-schemas.js.map +1 -1
  14. package/dist/src/routes/agent-activity.d.ts +2 -0
  15. package/dist/src/routes/agent-activity.d.ts.map +1 -1
  16. package/dist/src/routes/agent-activity.js +41 -0
  17. package/dist/src/routes/agent-activity.js.map +1 -1
  18. package/dist/src/routes/agent-tasks.d.ts +3 -0
  19. package/dist/src/routes/agent-tasks.d.ts.map +1 -1
  20. package/dist/src/routes/agent-tasks.js +91 -1
  21. package/dist/src/routes/agent-tasks.js.map +1 -1
  22. package/dist/src/services/agent-statistics.d.ts +80 -0
  23. package/dist/src/services/agent-statistics.d.ts.map +1 -0
  24. package/dist/src/services/agent-statistics.js +180 -0
  25. package/dist/src/services/agent-statistics.js.map +1 -0
  26. package/dist/src/storage/interface.d.ts +35 -1
  27. package/dist/src/storage/interface.d.ts.map +1 -1
  28. package/dist/src/storage/interface.js +2 -1
  29. package/dist/src/storage/interface.js.map +1 -1
  30. package/dist/src/storage/providers/mongodb/index.d.ts.map +1 -1
  31. package/dist/src/storage/providers/mongodb/index.js +3 -0
  32. package/dist/src/storage/providers/mongodb/index.js.map +1 -1
  33. package/dist/src/storage/providers/sqlite/repos/agent-task.d.ts.map +1 -1
  34. package/dist/src/storage/providers/sqlite/repos/agent-task.js +6 -4
  35. package/dist/src/storage/providers/sqlite/repos/agent-task.js.map +1 -1
  36. package/dist/src/storage/providers/sqlite/schema.d.ts.map +1 -1
  37. package/dist/src/storage/providers/sqlite/schema.js +4 -1
  38. package/dist/src/storage/providers/sqlite/schema.js.map +1 -1
  39. package/package.json +1 -1
  40. package/prisma/schema.prisma +1 -0
@@ -473,6 +473,8 @@
473
473
  "requestChangesError": "Failed to send change request",
474
474
  "revisionWaiting": "Waiting for the agent to propose a revised plan.",
475
475
  "outdatedTodos": "Previously proposed (outdated)",
476
+ "blurTitle": "Hide title (blur for screen recording)",
477
+ "unblurTitle": "Show title",
476
478
  "builder": {
477
479
  "title": "New Task",
478
480
  "placeholder": "What should the agent do? Describe in plain language or technical detail.",
@@ -653,7 +655,39 @@
653
655
  "directives": "Directives",
654
656
  "agent_config": "Agent Config",
655
657
  "activity": "Activity",
656
- "services": "Services"
658
+ "services": "Services",
659
+ "quality": "Quality"
660
+ },
661
+ "quality": {
662
+ "performanceTitle": "Performance",
663
+ "performanceDesc": "How much this agent has done and how fast — computed from its tasks.",
664
+ "reviewsTitle": "Reviews by context",
665
+ "reviewsDesc": "Per-context star ratings of the agent's deliverables. Factual contexts are source-grounded.",
666
+ "customTitle": "Custom metrics",
667
+ "customDesc": "Metrics the agent publishes about itself.",
668
+ "tasksTotal": "Tasks",
669
+ "tasksCompleted": "Completed",
670
+ "tasksFailed": "Failed",
671
+ "successRate": "Success rate",
672
+ "avgTime": "Avg make-time",
673
+ "events": "Events",
674
+ "overall": "Overall",
675
+ "ratings": "{count} ratings",
676
+ "lowConfidence": "Low confidence (small sample)",
677
+ "noReviews": "No reviews yet. Rate a completed task to start building quality stats.",
678
+ "noCustom": "No custom metrics published.",
679
+ "grounded": "{count} source-grounded",
680
+ "seconds": "s",
681
+ "contexts": {
682
+ "factual": "Factual",
683
+ "creative": "Creative",
684
+ "code": "Code",
685
+ "planning": "Planning",
686
+ "summarization": "Summarization",
687
+ "research": "Research",
688
+ "communication": "Communication",
689
+ "other": "Other"
690
+ }
657
691
  },
658
692
  "state": {
659
693
  "new": "New",
@@ -473,6 +473,8 @@
473
473
  "requestChangesError": "Muutospyynnön lähetys epäonnistui",
474
474
  "revisionWaiting": "Odotetaan agentin korjattua ehdotusta.",
475
475
  "outdatedTodos": "Aiemmin ehdotetut (vanhentuneet)",
476
+ "blurTitle": "Piilota otsikko (sumenna ruutunauhoitusta varten)",
477
+ "unblurTitle": "Näytä otsikko",
476
478
  "builder": {
477
479
  "title": "Uusi tehtävä",
478
480
  "placeholder": "Mitä agentin pitäisi tehdä? Kuvaile selkokielellä tai teknisesti.",
@@ -653,7 +655,39 @@
653
655
  "directives": "Ohjeistukset",
654
656
  "agent_config": "Agenttiasetukset",
655
657
  "activity": "Toiminta",
656
- "services": "Palvelut"
658
+ "services": "Palvelut",
659
+ "quality": "Laatu"
660
+ },
661
+ "quality": {
662
+ "performanceTitle": "Suorituskyky",
663
+ "performanceDesc": "Kuinka paljon agentti on tehnyt ja kuinka nopeasti — laskettu sen tehtävistä.",
664
+ "reviewsTitle": "Arviot konteksteittain",
665
+ "reviewsDesc": "Agentin tuotosten tähtiarviot konteksteittain. Faktakontekstit ovat lähteisiin perustuvia.",
666
+ "customTitle": "Omat mittarit",
667
+ "customDesc": "Mittarit, joita agentti julkaisee itsestään.",
668
+ "tasksTotal": "Tehtäviä",
669
+ "tasksCompleted": "Valmiit",
670
+ "tasksFailed": "Epäonnistuneet",
671
+ "successRate": "Onnistumisaste",
672
+ "avgTime": "Keskim. tekoaika",
673
+ "events": "Tapahtumia",
674
+ "overall": "Kokonaisuus",
675
+ "ratings": "{count} arviota",
676
+ "lowConfidence": "Matala luottamus (pieni otos)",
677
+ "noReviews": "Ei vielä arvioita. Arvioi valmis tehtävä aloittaaksesi laatutilastojen kerryttämisen.",
678
+ "noCustom": "Ei julkaistuja omia mittareita.",
679
+ "grounded": "{count} lähteistettyä",
680
+ "seconds": "s",
681
+ "contexts": {
682
+ "factual": "Faktatieto",
683
+ "creative": "Luova",
684
+ "code": "Koodi",
685
+ "planning": "Suunnittelu",
686
+ "summarization": "Tiivistäminen",
687
+ "research": "Tutkimus",
688
+ "communication": "Viestintä",
689
+ "other": "Muu"
690
+ }
657
691
  },
658
692
  "state": {
659
693
  "new": "Uusi",
@@ -23,8 +23,10 @@
23
23
  * v2.8.0 -- 2026-05-31 -- Add pop-out button (pf-agd-popout-btn), drag-to-reorder rows (pf-agd-dnd-*), and solo pop-out window layout (pf-agd-solo*)
24
24
  * v2.9.0 -- 2026-05-31 -- Collapsed bar two-row layout (pf-agd-collapsed-main + pf-agd-collapsed-meta divider) to stop status overflow; move drag grip into the left margin so the card border stays flush
25
25
  * v2.10.0 -- 2026-05-31 -- Extend the drag-row drop target left over the grip gutter (padding-left + negative margin-left) so dropping on/near the handle registers; card border stays aligned
26
+ * v2.11.0 -- 2026-05-31 -- Add Quality tab styles (per-context review rows, duration chips, custom-metric rows)
26
27
  * v2.11.0 -- 2026-05-31 -- Widen the solo pop-out window (and pf-agd-solo max-width) so the agent card matches the list-view width and the full tab bar fits
27
28
  * v2.12.0 -- 2026-05-31 -- Add completed-task deliverable styles (pf-agd-deliverable*) linking a task to its agent-memory output key
29
+ * v2.13.0 -- 2026-05-31 -- Add per-task blur-title toggle styles (pf-agd-blur-toggle, pf-agd-task-title--blurred) for hiding sensitive titles in screen recordings
28
30
  */
29
31
 
30
32
  /* Header row: title left, connect button right */
@@ -2528,6 +2530,29 @@
2528
2530
  align-items: center;
2529
2531
  gap: 0.5rem;
2530
2532
  }
2533
+
2534
+ /* Per-task "hide title" toggle (eye icon) -- used to blur sensitive titles for
2535
+ screen recordings. Preference is browser-only (localStorage). */
2536
+ .pf .pf-agd-blur-toggle {
2537
+ flex-shrink: 0;
2538
+ background: transparent;
2539
+ border: none;
2540
+ padding: 0;
2541
+ margin: 0;
2542
+ font-size: 0.95rem;
2543
+ line-height: 1;
2544
+ cursor: pointer;
2545
+ opacity: 0.45;
2546
+ transition: opacity 0.15s ease;
2547
+ }
2548
+ .pf .pf-agd-blur-toggle:hover { opacity: 0.9; }
2549
+ .pf .pf-agd-blur-toggle--on { opacity: 0.85; }
2550
+
2551
+ .pf .pf-agd-task-title--blurred {
2552
+ filter: blur(5px);
2553
+ user-select: none;
2554
+ cursor: pointer;
2555
+ }
2531
2556
  .pf .agd-task-group-title ,
2532
2557
  .pf .pf-agd-task-group-title {
2533
2558
  margin-top: 0.5rem;
@@ -2655,3 +2680,113 @@
2655
2680
  border-left: 2px solid var(--info, #3B82F6);
2656
2681
  padding-left: 0.5rem;
2657
2682
  }
2683
+
2684
+ /* ── Quality tab ── */
2685
+
2686
+ .pf .pf-agd-quality-desc {
2687
+ font-size: 0.8rem;
2688
+ color: var(--text-dim, #6B7280);
2689
+ margin: 0.25rem 0 0.75rem;
2690
+ line-height: 1.45;
2691
+ }
2692
+
2693
+ .pf .pf-agd-quality .pf-agd-section-title {
2694
+ margin-top: 1.25rem;
2695
+ }
2696
+
2697
+ /* Duration-by-context chips under the performance cards */
2698
+ .pf .pf-agd-quality-durations {
2699
+ display: flex;
2700
+ flex-wrap: wrap;
2701
+ gap: 0.4rem;
2702
+ margin-bottom: 0.5rem;
2703
+ }
2704
+ .pf .pf-agd-quality-dur-chip {
2705
+ font-size: 0.75rem;
2706
+ color: var(--text-dim, #6B7280);
2707
+ background: var(--bg-dim, #F3F4F6);
2708
+ border: 1px solid var(--border, #E5E7EB);
2709
+ border-radius: 6px;
2710
+ padding: 0.15rem 0.5rem;
2711
+ }
2712
+
2713
+ /* Per-context review rows */
2714
+ .pf .pf-agd-quality-reviews {
2715
+ display: flex;
2716
+ flex-direction: column;
2717
+ gap: 0.5rem;
2718
+ }
2719
+ .pf .pf-agd-quality-review-row {
2720
+ border: 1px solid var(--border, #E5E7EB);
2721
+ border-radius: 8px;
2722
+ padding: 0.6rem 0.75rem;
2723
+ background: var(--card-bg, #FFFFFF);
2724
+ }
2725
+ .pf .pf-agd-quality-review-head {
2726
+ display: flex;
2727
+ align-items: center;
2728
+ flex-wrap: wrap;
2729
+ gap: 0.5rem;
2730
+ }
2731
+ .pf .pf-agd-quality-ctx {
2732
+ font-weight: 600;
2733
+ color: var(--text, #1A1A2E);
2734
+ min-width: 7rem;
2735
+ }
2736
+ .pf .pf-agd-quality-stars {
2737
+ color: var(--accent, #E8564A);
2738
+ letter-spacing: 1px;
2739
+ }
2740
+ .pf .pf-agd-quality-avg {
2741
+ font-weight: 600;
2742
+ color: var(--text, #1A1A2E);
2743
+ }
2744
+ .pf .pf-agd-quality-n {
2745
+ font-size: 0.75rem;
2746
+ color: var(--text-muted, #9CA3AF);
2747
+ }
2748
+ .pf .pf-agd-quality-lowconf {
2749
+ font-size: 0.7rem;
2750
+ color: var(--warning, #D97706);
2751
+ border: 1px solid var(--warning, #D97706);
2752
+ border-radius: 6px;
2753
+ padding: 0.05rem 0.4rem;
2754
+ }
2755
+ .pf .pf-agd-quality-review-meta {
2756
+ font-size: 0.72rem;
2757
+ color: var(--text-muted, #9CA3AF);
2758
+ margin-top: 0.3rem;
2759
+ }
2760
+ .pf .pf-agd-quality-overall {
2761
+ margin-top: 0.4rem;
2762
+ font-size: 0.85rem;
2763
+ color: var(--text-dim, #6B7280);
2764
+ }
2765
+ .pf .pf-agd-quality-overall .pf-agd-quality-stars,
2766
+ .pf .pf-agd-quality-overall {
2767
+ letter-spacing: 0.5px;
2768
+ }
2769
+
2770
+ /* Custom metric rows */
2771
+ .pf .pf-agd-quality-custom {
2772
+ display: flex;
2773
+ flex-direction: column;
2774
+ gap: 0.3rem;
2775
+ }
2776
+ .pf .pf-agd-quality-custom-row {
2777
+ display: flex;
2778
+ justify-content: space-between;
2779
+ gap: 1rem;
2780
+ font-size: 0.8rem;
2781
+ padding: 0.3rem 0;
2782
+ border-bottom: 1px solid var(--border, #E5E7EB);
2783
+ }
2784
+ .pf .pf-agd-quality-custom-key {
2785
+ color: var(--text-dim, #6B7280);
2786
+ font-family: var(--font-mono, monospace);
2787
+ }
2788
+ .pf .pf-agd-quality-custom-val {
2789
+ color: var(--text, #1A1A2E);
2790
+ text-align: right;
2791
+ word-break: break-word;
2792
+ }
@@ -6,6 +6,7 @@
6
6
  * @version-history
7
7
  * v1.0.0 -- 2026-05-21 -- Initial creation for Agent Dashboard Phase 1
8
8
  * v1.1.0 -- 2026-05-29 -- Add requestChanges() for the owner-asks-agent-to-revise-todos flow.
9
+ * v1.2.0 -- 2026-05-31 -- Add rateTask() and getAgentStatistics() for the Quality tab.
9
10
  */
10
11
  import { apiGet, apiPost, apiDelete } from '/js/api.js';
11
12
 
@@ -49,3 +50,13 @@ export async function requestChanges(agentName, taskId, message) {
49
50
  export async function listEvents(agentName, taskId) {
50
51
  return apiGet(`/v1/agents/${encodeURIComponent(agentName)}/tasks/${encodeURIComponent(taskId)}/events`);
51
52
  }
53
+
54
+ /** Review a completed task's deliverable. body: { stars, context, comment?, source_grounded?, unsupported?, evaluated_model? } */
55
+ export async function rateTask(agentName, taskId, body) {
56
+ return apiPost(`/v1/agents/${encodeURIComponent(agentName)}/tasks/${encodeURIComponent(taskId)}/rate`, body);
57
+ }
58
+
59
+ /** Quality tab: recomputed performance + per-context review rollups + custom metrics. */
60
+ export async function getAgentStatistics(agentName) {
61
+ return apiGet(`/v1/agents/${encodeURIComponent(agentName)}/statistics`);
62
+ }
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * @file agent-card.js
3
3
  * @description Agent card component with collapsed/expanded states,
4
- * Two-Zone Header (identity + state-dependent status), and 8-tab bar.
4
+ * Two-Zone Header (identity + state-dependent status), and tab bar.
5
5
  * @version-history
6
+ * v1.8.0 -- 2026-05-31 -- Add Quality tab (per-context reviews + performance + custom stats)
6
7
  * v1.0.0 -- 2026-05-24 -- Initial creation for Agent Detail Tab-View
7
8
  * v1.1.0 -- 2026-05-24 -- Fix C6: remove GAII from Zone 1, M1: add Next prefix, C1: production stats, C2: problem action buttons
8
9
  * v1.3.0 -- 2026-05-24 -- Audit fix: use proper down arrow glyph for collapse icon
@@ -36,6 +37,7 @@ import TabDataAccess from './tab-data-access.js';
36
37
  import TabDirectives from './tab-directives.js';
37
38
  import TabAgentConfig from './tab-agent-config.js';
38
39
  import TabActivity from './tab-activity.js';
40
+ import TabQuality from './tab-quality.js';
39
41
  import TabServices from './tab-services.js';
40
42
 
41
43
  const html = htm.bind(h);
@@ -50,6 +52,7 @@ const TABS = [
50
52
  { id: 'directives', key: 'profile.agents.detail.tabs.directives' },
51
53
  { id: 'agent-config', key: 'profile.agents.detail.tabs.agent_config' },
52
54
  { id: 'activity', key: 'profile.agents.detail.tabs.activity' },
55
+ { id: 'quality', key: 'profile.agents.detail.tabs.quality' },
53
56
  { id: 'services', key: 'profile.agents.detail.tabs.services' },
54
57
  ];
55
58
 
@@ -488,6 +491,7 @@ function renderTabContent(activeTab, agent, onboarding, session, showToast, allA
488
491
  case 'directives': return html`<${TabDirectives} ...${props} />`;
489
492
  case 'agent-config': return html`<${TabAgentConfig} ...${props} />`;
490
493
  case 'activity': return html`<${TabActivity} ...${props} />`;
494
+ case 'quality': return html`<${TabQuality} ...${props} />`;
491
495
  case 'services': return html`<${TabServices} ...${props} />`;
492
496
  default: return null;
493
497
  }
@@ -0,0 +1,159 @@
1
+ /**
2
+ * @file tab-quality.js
3
+ * @description Quality tab for the expanded agent view. Reads the recomputed
4
+ * GET /v1/agents/:name/statistics rollups and renders three sections:
5
+ * Performance (task counts, success rate, make-time, durations by context),
6
+ * Reviews by context (per-context star ratings + sample size + low-confidence
7
+ * flag), and Custom metrics (whatever the agent published under
8
+ * agents.<name>.statistics.custom.*). Quality READS statistics + reviews — it
9
+ * is the evaluative lens over the objective data.
10
+ * @structure default export TabQuality({ agentName })
11
+ * @usage rendered by agent-card.js renderTabContent() for the 'quality' tab
12
+ * @version-history
13
+ * v1.0.0 -- 2026-05-31 -- Initial Quality tab (per-context reviews + performance + custom)
14
+ */
15
+
16
+ import { h } from 'preact';
17
+ import { useState, useEffect, useRef } from 'preact/hooks';
18
+ import htm from 'htm';
19
+ import { t } from '/js/i18n.js';
20
+ import { getAgentStatistics } from '/js/services/agent-tasks.js';
21
+
22
+ const html = htm.bind(h);
23
+
24
+ /** Glyph row for an average star value (rounded to nearest whole star). */
25
+ function starGlyphs(value) {
26
+ const full = Math.max(0, Math.min(5, Math.round(value)));
27
+ return '★★★★★'.slice(0, full) + '☆☆☆☆☆'.slice(0, 5 - full);
28
+ }
29
+
30
+ /** Localised context label, falling back to the raw enum value. */
31
+ function ctxLabel(ctx) {
32
+ const key = `profile.agents.detail.quality.contexts.${ctx}`;
33
+ const label = t(key);
34
+ return label !== key ? label : ctx;
35
+ }
36
+
37
+ function fmtSeconds(secs) {
38
+ return `${Number(secs || 0).toLocaleString()}${t('profile.agents.detail.quality.seconds')}`;
39
+ }
40
+
41
+ export default function TabQuality({ agentName }) {
42
+ const [data, setData] = useState(null);
43
+ const [loading, setLoading] = useState(true);
44
+
45
+ async function loadData() {
46
+ setLoading(true);
47
+ try {
48
+ const resp = await getAgentStatistics(agentName);
49
+ setData(resp?.data || null);
50
+ } catch { setData(null); }
51
+ setLoading(false);
52
+ }
53
+
54
+ useEffect(() => { loadData(); }, [agentName]);
55
+
56
+ // Live updates: refresh when a rating lands or tasks change.
57
+ const loadRef = useRef(loadData);
58
+ loadRef.current = loadData;
59
+ useEffect(() => {
60
+ const handler = () => loadRef.current();
61
+ window.addEventListener('aimeat-live-update', handler);
62
+ return () => window.removeEventListener('aimeat-live-update', handler);
63
+ }, []);
64
+
65
+ if (loading) {
66
+ return html`<div class="pf-agd-empty">${t('profile.loading')}</div>`;
67
+ }
68
+
69
+ const perf = data?.performance || {};
70
+ const reviews = data?.reviews || {};
71
+ const custom = data?.custom || [];
72
+ const byContext = reviews.byContext || {};
73
+ const contextKeys = Object.keys(byContext);
74
+ const durByContext = perf.duration?.byContext || {};
75
+ const durKeys = Object.keys(durByContext);
76
+
77
+ return html`
78
+ <div class="pf-agd-quality">
79
+ <!-- Performance -->
80
+ <div class="pf-agd-section-title">${t('profile.agents.detail.quality.performanceTitle')}</div>
81
+ <div class="pf-agd-quality-desc">${t('profile.agents.detail.quality.performanceDesc')}</div>
82
+ <div class="pf-agd-stats-grid">
83
+ <div class="pf-agd-stat-card">
84
+ <div class="pf-agd-stat-value">${perf.tasks?.total ?? 0}</div>
85
+ <div class="pf-agd-stat-label">${t('profile.agents.detail.quality.tasksTotal')}</div>
86
+ </div>
87
+ <div class="pf-agd-stat-card">
88
+ <div class="pf-agd-stat-value">${perf.tasks?.completed ?? 0}</div>
89
+ <div class="pf-agd-stat-label">${t('profile.agents.detail.quality.tasksCompleted')}</div>
90
+ </div>
91
+ <div class="pf-agd-stat-card">
92
+ <div class="pf-agd-stat-value">${perf.tasks?.successRate != null ? `${Math.round(perf.tasks.successRate * 100)}%` : '-'}</div>
93
+ <div class="pf-agd-stat-label">${t('profile.agents.detail.quality.successRate')}</div>
94
+ </div>
95
+ <div class="pf-agd-stat-card">
96
+ <div class="pf-agd-stat-value">${fmtSeconds(perf.duration?.avgCompletionSeconds)}</div>
97
+ <div class="pf-agd-stat-label">${t('profile.agents.detail.quality.avgTime')}</div>
98
+ </div>
99
+ <div class="pf-agd-stat-card">
100
+ <div class="pf-agd-stat-value">${perf.events?.total ?? 0}</div>
101
+ <div class="pf-agd-stat-label">${t('profile.agents.detail.quality.events')}</div>
102
+ </div>
103
+ </div>
104
+ ${durKeys.length > 0 && html`
105
+ <div class="pf-agd-quality-durations">
106
+ ${durKeys.map(ctx => html`
107
+ <span key=${ctx} class="pf-agd-quality-dur-chip">
108
+ ${ctxLabel(ctx)}: ${fmtSeconds(durByContext[ctx].avgSeconds)} (${durByContext[ctx].count})
109
+ </span>
110
+ `)}
111
+ </div>
112
+ `}
113
+
114
+ <!-- Reviews by context -->
115
+ <div class="pf-agd-section-title">${t('profile.agents.detail.quality.reviewsTitle')}</div>
116
+ <div class="pf-agd-quality-desc">${t('profile.agents.detail.quality.reviewsDesc')}</div>
117
+ ${contextKeys.length === 0
118
+ ? html`<div class="pf-agd-empty">${t('profile.agents.detail.quality.noReviews')}</div>`
119
+ : html`
120
+ <div class="pf-agd-quality-reviews">
121
+ ${contextKeys.map(ctx => {
122
+ const s = byContext[ctx];
123
+ return html`
124
+ <div key=${ctx} class="pf-agd-quality-review-row">
125
+ <div class="pf-agd-quality-review-head">
126
+ <span class="pf-agd-quality-ctx">${ctxLabel(ctx)}</span>
127
+ <span class="pf-agd-quality-stars">${starGlyphs(s.avgStars)}</span>
128
+ <span class="pf-agd-quality-avg">${Number(s.avgStars).toFixed(1)}</span>
129
+ <span class="pf-agd-quality-n">${t('profile.agents.detail.quality.ratings', { count: s.n })}</span>
130
+ ${s.lowConfidence && html`<span class="pf-agd-quality-lowconf">${t('profile.agents.detail.quality.lowConfidence')}</span>`}
131
+ </div>
132
+ <div class="pf-agd-quality-review-meta">${t('profile.agents.detail.quality.grounded', { count: s.sourceGroundedN })}</div>
133
+ </div>
134
+ `;
135
+ })}
136
+ <div class="pf-agd-quality-overall">
137
+ ${t('profile.agents.detail.quality.overall')}: ${starGlyphs(reviews.overall?.avgStars || 0)} ${Number(reviews.overall?.avgStars || 0).toFixed(1)} (${t('profile.agents.detail.quality.ratings', { count: reviews.overall?.n || 0 })})
138
+ </div>
139
+ </div>
140
+ `}
141
+
142
+ <!-- Custom metrics -->
143
+ <div class="pf-agd-section-title">${t('profile.agents.detail.quality.customTitle')}</div>
144
+ <div class="pf-agd-quality-desc">${t('profile.agents.detail.quality.customDesc')}</div>
145
+ ${custom.length === 0
146
+ ? html`<div class="pf-agd-empty">${t('profile.agents.detail.quality.noCustom')}</div>`
147
+ : html`
148
+ <div class="pf-agd-quality-custom">
149
+ ${custom.map(c => html`
150
+ <div key=${c.key} class="pf-agd-quality-custom-row">
151
+ <span class="pf-agd-quality-custom-key">${c.key}</span>
152
+ <span class="pf-agd-quality-custom-val">${typeof c.value === 'object' ? JSON.stringify(c.value) : String(c.value)}</span>
153
+ </div>
154
+ `)}
155
+ </div>
156
+ `}
157
+ </div>
158
+ `;
159
+ }
@@ -9,6 +9,9 @@
9
9
  * - TaskItem -- task row with expand/collapse, todo list, start button
10
10
  * - RequestChangesModal -- inline modal for owner to send a free-text change request
11
11
  * @version-history
12
+ * v4.3.0 -- 2026-05-31 -- Add per-task "blur title" toggle in the collapsed
13
+ * task row (eye icon). Hides a task title behind a CSS blur for screen
14
+ * recordings; preference persists per task ID in localStorage (browser-only).
12
15
  * v3.2.0 -- 2026-05-24 -- Add interactive filter pills, scope/rules display, migrate to pf-agd- CSS prefix
13
16
  * v3.1.0 -- 2026-05-24 -- Fix: use correct locale key for empty state
14
17
  * v3.0.0 -- 2026-05-22 -- Replace TaskCreateForm with TaskCreationBuilder (design spec split-panel)
@@ -43,6 +46,29 @@ import { useConfirm, Modal } from '/components/Modal.js';
43
46
 
44
47
  const TASK_FILTERS = ['all', 'active', 'queued', 'completed', 'failed'];
45
48
 
49
+ // Per-browser "blur the title" preference. Used when screen-recording the tab
50
+ // so sensitive task titles can be hidden without affecting other viewers or
51
+ // the server. Stored as an array of task IDs in localStorage; survives reloads
52
+ // but never leaves this browser.
53
+ const BLUR_STORAGE_KEY = 'aimeat.blurredTaskTitles';
54
+
55
+ function readBlurredSet() {
56
+ try {
57
+ const parsed = JSON.parse(localStorage.getItem(BLUR_STORAGE_KEY));
58
+ return new Set(Array.isArray(parsed) ? parsed : []);
59
+ } catch { return new Set(); }
60
+ }
61
+
62
+ function isTaskBlurred(taskId) {
63
+ return readBlurredSet().has(taskId);
64
+ }
65
+
66
+ function setTaskBlurred(taskId, blurred) {
67
+ const set = readBlurredSet();
68
+ if (blurred) set.add(taskId); else set.delete(taskId);
69
+ try { localStorage.setItem(BLUR_STORAGE_KEY, JSON.stringify([...set])); } catch { /* storage full/blocked -- preference just won't persist */ }
70
+ }
71
+
46
72
  // Character limits -- keep in sync with AgentTaskCreateSchema in
47
73
  // src/models/agent-task-schemas.ts (title max 256, description max 10000).
48
74
  // The agent crew reads task.description as its prompt, so that is the field
@@ -186,8 +212,18 @@ function TaskItem({ task, agentName, showToast, onRefresh }) {
186
212
  const [showOutdated, setShowOutdated] = useState(false);
187
213
  // Deliverable preview: null = not requested, {loading}|{notFound}|{value}.
188
214
  const [deliverable, setDeliverable] = useState(null);
215
+ // Local-only "hide the title" toggle (for screen recordings). Persisted per
216
+ // task ID in localStorage; see helpers at top of file.
217
+ const [blurred, setBlurred] = useState(() => isTaskBlurred(task.id));
189
218
  const { confirm, ConfirmUI } = useConfirm();
190
219
 
220
+ function handleToggleBlur(e) {
221
+ e.stopPropagation();
222
+ const next = !blurred;
223
+ setBlurred(next);
224
+ setTaskBlurred(task.id, next);
225
+ }
226
+
191
227
  // Fetch the task's published deliverable from the agent's memory namespace.
192
228
  // Uses the owner list endpoint (?agent=<gaii>&prefix=<key>) which returns the
193
229
  // value inline; if the exact key is gone, show that it no longer exists.
@@ -338,7 +374,13 @@ function TaskItem({ task, agentName, showToast, onRefresh }) {
338
374
  <div>
339
375
  <div class="pf-agd-task-item" onClick=${handleExpand}>
340
376
  <div class="pf-agd-task-title-row">
341
- <span class="pf-agd-task-title">${task.title || task.id}</span>
377
+ <button
378
+ class=${`pf-agd-blur-toggle ${blurred ? 'pf-agd-blur-toggle--on' : ''}`}
379
+ onClick=${handleToggleBlur}
380
+ title=${blurred ? t('profile.agents.tasks.unblurTitle') : t('profile.agents.tasks.blurTitle')}
381
+ aria-pressed=${blurred}
382
+ >${blurred ? '🙈' : '👁'}</button>
383
+ <span class=${`pf-agd-task-title ${blurred ? 'pf-agd-task-title--blurred' : ''}`}>${task.title || task.id}</span>
342
384
  ${progress && html`<span class="pf-agd-todo-progress">${progress}</span>`}
343
385
  </div>
344
386
  <div class="pf-agd-task-meta">