reflectt-node 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "reflectt-node",
3
- "version": "0.1.2",
4
- "description": "Local node server for agent-to-agent communication via OpenClaw",
3
+ "version": "0.1.4",
4
+ "description": "Coordinate your AI agent team. Shared tasks, memory, reflections, and presence. Self-host for free.",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
7
7
  "bin": {
@@ -32,14 +32,13 @@
32
32
  "prepack": "chmod +x dist/cli.js"
33
33
  },
34
34
  "keywords": [
35
- "reflectt",
36
- "openclaw",
37
35
  "ai-agents",
38
36
  "agent-coordination",
39
37
  "task-management",
40
38
  "self-hosted",
41
- "team-health",
42
- "task-board"
39
+ "ai",
40
+ "developer-tools",
41
+ "reflectt"
43
42
  ],
44
43
  "author": "Team Reflectt",
45
44
  "license": "Apache-2.0",
@@ -164,6 +164,14 @@ function formatProductiveText(agent) {
164
164
  function esc(s) { const d = document.createElement('div'); d.textContent = s || ''; return d.innerHTML; }
165
165
  function formatBytes(b) { if (!b || b < 1024) return b + ' B'; if (b < 1048576) return (b/1024).toFixed(1) + ' KB'; return (b/1048576).toFixed(1) + ' MB'; }
166
166
  function truncate(s, n) { return s && s.length > n ? s.slice(0, n) + '…' : (s || ''); }
167
+ function humanizeMinutes(m) {
168
+ if (m == null || m === '') return '—';
169
+ const n = Number(m);
170
+ if (!Number.isFinite(n) || n >= 9999) return '—';
171
+ if (n < 60) return n + 'm';
172
+ if (n < 1440) return Math.round(n / 60) + 'h';
173
+ return Math.round(n / 1440) + 'd';
174
+ }
167
175
  function renderTaskTags(tags) {
168
176
  if (!Array.isArray(tags) || tags.length === 0) return '';
169
177
  const shown = tags.filter(Boolean).slice(0, 3);
@@ -495,7 +503,7 @@ function renderCompliance(compliance) {
495
503
 
496
504
  const chipsHtml = chips.map(c => {
497
505
  const state = complianceState(c.value, c.threshold);
498
- return '<div class="sla-chip ' + state + '"><span>' + esc(c.label) + '</span><strong>' + formatDurationMin(c.value) + '</strong></div>';
506
+ return '<div class="sla-chip ' + state + '"><span>' + esc(c.label) + '</span><strong>' + humanizeMinutes(c.value) + '</strong></div>';
499
507
  }).join('');
500
508
 
501
509
  const rows = agents.map(a => {
@@ -504,7 +512,7 @@ function renderCompliance(compliance) {
504
512
  return '<tr>' +
505
513
  '<td>' + esc(a.agent) + '</td>' +
506
514
  '<td>' + taskCell + '</td>' +
507
- '<td>' + formatDurationMin(a.lastValidStatusAgeMin) + '</td>' +
515
+ '<td>' + humanizeMinutes(a.lastValidStatusAgeMin) + '</td>' +
508
516
  '<td>' + a.expectedCadenceMin + 'm</td>' +
509
517
  '<td><span class="state-pill ' + a.state + ' compliance-state-' + a.state + '">' + esc(a.state) + '</span></td>' +
510
518
  '<td><button class="copy-template-btn" data-agent="' + esc(a.agent) + '" data-task="' + esc(taskValue) + '" onclick="copyStatusTemplate(this.dataset.agent, this.dataset.task)">Copy template</button></td>' +
@@ -781,10 +789,12 @@ function renderKanban() {
781
789
  kanban.innerHTML = cols.map(col => {
782
790
  const items = grouped[col];
783
791
  const isDone = col === 'done';
784
- const shown = isDone ? items.slice(0, 3) : items;
785
- const cards = shown.length === 0
792
+ const isTodo = col === 'todo';
793
+ const colLimit = isDone ? 3 : isTodo ? 10 : items.length;
794
+ const cards = items.length === 0
786
795
  ? '<div class="empty">—</div>'
787
- : shown.map(t => {
796
+ : items.map((t, idx) => {
797
+ const isHidden = idx >= colLimit;
788
798
  const assigneeAgent = t.assignee ? AGENTS.find(a => a.name === t.assignee) : null;
789
799
  const assigneeDisplay = t.assignee
790
800
  ? `<span class="assignee-tag">👤 ${esc(t.assignee)}${assigneeAgent ? ' <span class="role-small">' + esc(assigneeAgent.role) + '</span>' : ''}</span>`
@@ -793,7 +803,7 @@ function renderKanban() {
793
803
  ? `<div style="margin-top:4px"><span class="assignee-tag" style="font-family:monospace;font-size:10px;color:var(--accent)">🌿 ${esc(t.metadata.branch)}</span></div>`
794
804
  : '';
795
805
  return `
796
- <div class="task-card" data-task-id="${t.id}">
806
+ <div class="task-card${isHidden ? ' hidden' : ''}" data-task-id="${t.id}">
797
807
  <div class="task-title">${esc(truncate(t.title, 60))}</div>
798
808
  <div class="task-meta">
799
809
  ${t.priority ? '<span class="priority-badge ' + t.priority + '">' + t.priority + '</span>' : ''}
@@ -808,8 +818,9 @@ function renderKanban() {
808
818
  ${renderQaContract(t)}
809
819
  </div>`;
810
820
  }).join('');
811
- const extra = isDone && items.length > 3
812
- ? `<button class="done-toggle" onclick="this.parentElement.querySelectorAll('.task-card.hidden').forEach(c=>c.classList.remove('hidden'));this.remove()">+ ${items.length - 3} more</button>` : '';
821
+ const hasMore = items.length > colLimit;
822
+ const extra = hasMore
823
+ ? `<button class="done-toggle" onclick="this.parentElement.querySelectorAll('.task-card.hidden').forEach(c=>c.classList.remove('hidden'));this.remove()">+ ${items.length - colLimit} more</button>` : '';
813
824
  return `<div class="kanban-col" data-status="${col}">
814
825
  <div class="kanban-col-header">${col} <span class="cnt">${items.length}</span></div>
815
826
  ${cards}${extra}
@@ -0,0 +1,413 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Agent Intensity Toggle — Design Mock</title>
7
+ <style>
8
+ /* === Reflectt Node Tokens (subset) === */
9
+ :root {
10
+ --bg: #0a0e14;
11
+ --surface: #141920;
12
+ --surface-raised: #1a2028;
13
+ --border: #252d38;
14
+ --border-subtle: #1e2530;
15
+ --text: #d4dae3;
16
+ --text-bright: #eef1f5;
17
+ --text-muted: #6b7a8d;
18
+ --accent: #4da6ff;
19
+ --accent-dim: rgba(77, 166, 255, 0.12);
20
+ --yellow: #d4a017;
21
+ --yellow-dim: rgba(212, 160, 23, 0.12);
22
+ --orange: #e08a20;
23
+ --orange-dim: rgba(224, 138, 32, 0.12);
24
+ --green: #3fb950;
25
+ --green-dim: rgba(63, 185, 80, 0.12);
26
+ --red: #f85149;
27
+ --radius-sm: 6px;
28
+ --radius-md: 8px;
29
+ --space-2: 8px;
30
+ --space-3: 12px;
31
+ --space-4: 16px;
32
+ --space-5: 20px;
33
+ --text-xs: 10px;
34
+ --text-sm: 11px;
35
+ --text-base: 13px;
36
+ --text-md: 14px;
37
+ --font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
38
+ --font-weight-normal: 400;
39
+ --font-weight-medium: 500;
40
+ --font-weight-semibold: 600;
41
+ --focus-ring: 0 0 0 2px rgba(77, 166, 255, 0.5);
42
+ }
43
+
44
+ * { box-sizing: border-box; margin: 0; padding: 0; }
45
+
46
+ body {
47
+ font-family: var(--font-family);
48
+ background: var(--bg);
49
+ color: var(--text);
50
+ min-height: 100vh;
51
+ display: flex;
52
+ flex-direction: column;
53
+ align-items: center;
54
+ padding: 40px 20px;
55
+ -webkit-font-smoothing: antialiased;
56
+ }
57
+
58
+ h1 {
59
+ font-size: 18px;
60
+ font-weight: 700;
61
+ color: var(--text-bright);
62
+ margin-bottom: 8px;
63
+ }
64
+ .subtitle {
65
+ font-size: var(--text-base);
66
+ color: var(--text-muted);
67
+ margin-bottom: 32px;
68
+ }
69
+
70
+ /* === Mock Dashboard Panel === */
71
+ .panel {
72
+ background: var(--surface);
73
+ border: 1px solid var(--border);
74
+ border-radius: var(--radius-md);
75
+ width: 100%;
76
+ max-width: 480px;
77
+ overflow: hidden;
78
+ }
79
+ .panel-header {
80
+ padding: var(--space-3) var(--space-4);
81
+ border-bottom: 1px solid var(--border);
82
+ font-size: var(--text-md);
83
+ font-weight: var(--font-weight-semibold);
84
+ color: var(--text-bright);
85
+ display: flex;
86
+ align-items: center;
87
+ gap: 8px;
88
+ }
89
+ .panel-body {
90
+ padding: var(--space-4) var(--space-5);
91
+ }
92
+
93
+ /* === Runtime Stats (mock) === */
94
+ .runtime-stats {
95
+ display: grid;
96
+ grid-template-columns: 1fr 1fr;
97
+ gap: var(--space-3);
98
+ margin-bottom: 0;
99
+ }
100
+ .stat {
101
+ display: flex;
102
+ flex-direction: column;
103
+ gap: 2px;
104
+ }
105
+ .stat-label {
106
+ font-size: var(--text-xs);
107
+ color: var(--text-muted);
108
+ text-transform: uppercase;
109
+ letter-spacing: 0.05em;
110
+ }
111
+ .stat-value {
112
+ font-size: var(--text-md);
113
+ font-weight: var(--font-weight-semibold);
114
+ color: var(--text);
115
+ }
116
+ .stat-value.online { color: var(--green); }
117
+
118
+ /* === Intensity Control === */
119
+ .intensity-control {
120
+ margin-top: var(--space-4);
121
+ padding-top: var(--space-4);
122
+ border-top: 1px solid var(--border);
123
+ }
124
+ .intensity-label {
125
+ font-size: var(--text-sm);
126
+ font-weight: var(--font-weight-semibold);
127
+ color: var(--text);
128
+ margin-bottom: var(--space-2);
129
+ }
130
+ .intensity-segments {
131
+ display: flex;
132
+ gap: 2px;
133
+ background: var(--surface);
134
+ border: 1px solid var(--border);
135
+ border-radius: var(--radius-md);
136
+ padding: 3px;
137
+ }
138
+ .intensity-seg {
139
+ flex: 1;
140
+ padding: 8px 12px;
141
+ border: 1px solid transparent;
142
+ border-radius: var(--radius-sm);
143
+ background: transparent;
144
+ color: var(--text-muted);
145
+ font-size: var(--text-sm);
146
+ font-weight: var(--font-weight-medium);
147
+ cursor: pointer;
148
+ transition: all 150ms ease;
149
+ min-height: 44px;
150
+ display: flex;
151
+ align-items: center;
152
+ justify-content: center;
153
+ gap: 4px;
154
+ font-family: inherit;
155
+ }
156
+ .intensity-seg:focus-visible {
157
+ outline: none;
158
+ box-shadow: var(--focus-ring);
159
+ }
160
+ .intensity-seg:hover:not(.active) {
161
+ color: var(--text);
162
+ background: rgba(255,255,255,0.03);
163
+ }
164
+
165
+ /* Active states — color-coded by level */
166
+ .intensity-seg.active[data-level="low"] {
167
+ background: var(--yellow-dim);
168
+ color: var(--yellow);
169
+ border-color: var(--yellow);
170
+ font-weight: var(--font-weight-semibold);
171
+ }
172
+ .intensity-seg.active[data-level="normal"] {
173
+ background: var(--accent-dim);
174
+ color: var(--accent);
175
+ border-color: var(--accent);
176
+ font-weight: var(--font-weight-semibold);
177
+ }
178
+ .intensity-seg.active[data-level="high"] {
179
+ background: var(--orange-dim);
180
+ color: var(--orange);
181
+ border-color: var(--orange);
182
+ font-weight: var(--font-weight-semibold);
183
+ }
184
+
185
+ .intensity-desc {
186
+ font-size: var(--text-xs);
187
+ color: var(--text-muted);
188
+ margin-top: var(--space-2);
189
+ line-height: 1.5;
190
+ min-height: 30px;
191
+ }
192
+
193
+ /* === Toast === */
194
+ .toast {
195
+ position: fixed;
196
+ bottom: 24px;
197
+ left: 50%;
198
+ transform: translateX(-50%) translateY(80px);
199
+ background: var(--surface-raised);
200
+ border: 1px solid var(--border);
201
+ border-radius: var(--radius-md);
202
+ padding: 10px 20px;
203
+ font-size: var(--text-sm);
204
+ color: var(--text);
205
+ opacity: 0;
206
+ transition: all 300ms ease;
207
+ pointer-events: none;
208
+ white-space: nowrap;
209
+ }
210
+ .toast.show {
211
+ transform: translateX(-50%) translateY(0);
212
+ opacity: 1;
213
+ }
214
+
215
+ /* === Mapping Table === */
216
+ .mapping-section {
217
+ margin-top: 40px;
218
+ width: 100%;
219
+ max-width: 640px;
220
+ }
221
+ .mapping-section h2 {
222
+ font-size: 15px;
223
+ font-weight: 700;
224
+ color: var(--text-bright);
225
+ margin-bottom: 12px;
226
+ }
227
+ table {
228
+ width: 100%;
229
+ border-collapse: collapse;
230
+ font-size: var(--text-sm);
231
+ }
232
+ th, td {
233
+ padding: 8px 12px;
234
+ text-align: left;
235
+ border-bottom: 1px solid var(--border);
236
+ }
237
+ th {
238
+ color: var(--text-muted);
239
+ font-weight: var(--font-weight-semibold);
240
+ text-transform: uppercase;
241
+ letter-spacing: 0.05em;
242
+ font-size: var(--text-xs);
243
+ }
244
+ td { color: var(--text); }
245
+ td.highlight { color: var(--accent); font-weight: var(--font-weight-medium); }
246
+
247
+ /* === Footer === */
248
+ .mock-footer {
249
+ margin-top: 40px;
250
+ font-size: var(--text-xs);
251
+ color: var(--text-muted);
252
+ text-align: center;
253
+ }
254
+
255
+ @media (hover: hover) and (pointer: fine) {
256
+ .intensity-seg:hover:not(.active) {
257
+ color: var(--text);
258
+ background: rgba(255,255,255,0.03);
259
+ }
260
+ }
261
+ @media not all and (hover: hover) {
262
+ .intensity-seg:hover:not(.active) {
263
+ color: var(--text-muted);
264
+ background: transparent;
265
+ }
266
+ }
267
+ </style>
268
+ </head>
269
+ <body>
270
+
271
+ <h1>Agent Intensity Toggle</h1>
272
+ <p class="subtitle">Design mock — where it lives in the dashboard</p>
273
+
274
+ <!-- Mock Runtime Truth Card panel -->
275
+ <div class="panel">
276
+ <div class="panel-header">🧭 Runtime Truth Card</div>
277
+ <div class="panel-body">
278
+
279
+ <!-- Existing runtime stats (mock) -->
280
+ <div class="runtime-stats">
281
+ <div class="stat">
282
+ <span class="stat-label">Agents</span>
283
+ <span class="stat-value online">6 online</span>
284
+ </div>
285
+ <div class="stat">
286
+ <span class="stat-label">Tasks</span>
287
+ <span class="stat-value">3 active</span>
288
+ </div>
289
+ <div class="stat">
290
+ <span class="stat-label">Heartbeat</span>
291
+ <span class="stat-value">30s</span>
292
+ </div>
293
+ <div class="stat">
294
+ <span class="stat-label">Uptime</span>
295
+ <span class="stat-value">4h 22m</span>
296
+ </div>
297
+ </div>
298
+
299
+ <!-- ✨ NEW: Intensity Control -->
300
+ <div class="intensity-control" role="radiogroup" aria-label="Agent intensity">
301
+ <div class="intensity-label">⚡ Agent Intensity</div>
302
+ <div class="intensity-segments">
303
+ <button role="radio" aria-checked="false" data-level="low" class="intensity-seg" onclick="setIntensity('low')">
304
+ 🐢 Low
305
+ </button>
306
+ <button role="radio" aria-checked="true" data-level="normal" class="intensity-seg active" onclick="setIntensity('normal')">
307
+ ⚡ Normal
308
+ </button>
309
+ <button role="radio" aria-checked="false" data-level="high" class="intensity-seg" onclick="setIntensity('high')">
310
+ 🔥 High
311
+ </button>
312
+ </div>
313
+ <div class="intensity-desc" id="intensity-desc">
314
+ Agents work at standard pace. Tasks processed each heartbeat.
315
+ </div>
316
+ </div>
317
+
318
+ </div>
319
+ </div>
320
+
321
+ <!-- Mapping reference table -->
322
+ <div class="mapping-section">
323
+ <h2>What each level changes</h2>
324
+ <table>
325
+ <thead>
326
+ <tr>
327
+ <th>Setting</th>
328
+ <th>🐢 Low</th>
329
+ <th>⚡ Normal</th>
330
+ <th>🔥 High</th>
331
+ </tr>
332
+ </thead>
333
+ <tbody>
334
+ <tr>
335
+ <td>Heartbeat</td>
336
+ <td>2× slower</td>
337
+ <td class="highlight">Default</td>
338
+ <td>2× faster</td>
339
+ </tr>
340
+ <tr>
341
+ <td>Tasks / cycle</td>
342
+ <td>Max 1</td>
343
+ <td class="highlight">Config default</td>
344
+ <td>Unlimited</td>
345
+ </tr>
346
+ <tr>
347
+ <td>Approval</td>
348
+ <td>All manual</td>
349
+ <td class="highlight">Routine auto</td>
350
+ <td>All auto (except close)</td>
351
+ </tr>
352
+ <tr>
353
+ <td>Parallel tasks</td>
354
+ <td>No</td>
355
+ <td class="highlight">No</td>
356
+ <td>Yes</td>
357
+ </tr>
358
+ </tbody>
359
+ </table>
360
+ </div>
361
+
362
+ <!-- Toast -->
363
+ <div class="toast" id="toast"></div>
364
+
365
+ <div class="mock-footer">
366
+ Design mock by pixel 🎨 · task-1772244618727-695h6hdsk · reflectt-node
367
+ </div>
368
+
369
+ <script>
370
+ const COPY = {
371
+ low: 'Agents check in less often. Tasks batch up.',
372
+ normal: 'Agents work at standard pace. Tasks processed each heartbeat.',
373
+ high: 'Agents move fast. Parallel tasks enabled.',
374
+ };
375
+
376
+ const TOAST_MSG = {
377
+ low: '🐢 Intensity → Low — agents will slow down',
378
+ normal: '⚡ Intensity → Normal — standard pace',
379
+ high: '🔥 Intensity → High — agents moving fast',
380
+ };
381
+
382
+ function setIntensity(level) {
383
+ document.querySelectorAll('.intensity-seg').forEach(btn => {
384
+ const active = btn.dataset.level === level;
385
+ btn.classList.toggle('active', active);
386
+ btn.setAttribute('aria-checked', active);
387
+ });
388
+ document.getElementById('intensity-desc').textContent = COPY[level];
389
+ showToast(TOAST_MSG[level]);
390
+ }
391
+
392
+ let toastTimer;
393
+ function showToast(msg) {
394
+ const el = document.getElementById('toast');
395
+ el.textContent = msg;
396
+ el.classList.add('show');
397
+ clearTimeout(toastTimer);
398
+ toastTimer = setTimeout(() => el.classList.remove('show'), 2500);
399
+ }
400
+
401
+ // Keyboard nav for radiogroup
402
+ document.querySelector('.intensity-segments').addEventListener('keydown', e => {
403
+ const btns = [...document.querySelectorAll('.intensity-seg')];
404
+ const idx = btns.indexOf(document.activeElement);
405
+ if (idx < 0) return;
406
+ let next;
407
+ if (e.key === 'ArrowRight' || e.key === 'ArrowDown') next = btns[(idx + 1) % btns.length];
408
+ if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') next = btns[(idx - 1 + btns.length) % btns.length];
409
+ if (next) { e.preventDefault(); next.focus(); next.click(); }
410
+ });
411
+ </script>
412
+ </body>
413
+ </html>