apteva 0.4.15 → 0.4.16

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.
@@ -1,5 +1,5 @@
1
1
  import React, { useState, useEffect, useCallback, useRef } from "react";
2
- import { TasksIcon, CloseIcon } from "../common/Icons";
2
+ import { TasksIcon, CloseIcon, RecurringIcon, ScheduledIcon, TaskOnceIcon } from "../common/Icons";
3
3
  import { useAuth, useProjects } from "../../context";
4
4
  import { useTelemetry } from "../../context/TelemetryContext";
5
5
  import type { Task, TaskTrajectoryStep, ToolUseBlock, ToolResultBlock } from "../../types";
@@ -157,14 +157,7 @@ export function TasksPage({ onSelectAgent }: TasksPageProps) {
157
157
  <div className="flex items-start justify-between mb-2">
158
158
  <div className="flex-1">
159
159
  <h3 className="font-medium">{task.title}</h3>
160
- <p className="text-sm text-[#666]">
161
- {task.agentName}
162
- {task.execute_at && (
163
- <span className="ml-2">
164
- · Scheduled: {new Date(task.execute_at).toLocaleString()}
165
- </span>
166
- )}
167
- </p>
160
+ <p className="text-sm text-[#666]">{task.agentName}</p>
168
161
  </div>
169
162
  <span className={`px-2 py-1 rounded text-xs font-medium ${statusColors[task.status] || statusColors.pending}`}>
170
163
  {task.status}
@@ -178,10 +171,23 @@ export function TasksPage({ onSelectAgent }: TasksPageProps) {
178
171
  )}
179
172
 
180
173
  <div className="flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-[#555]">
181
- <span>Type: {task.type}</span>
174
+ <span className="flex items-center gap-1">
175
+ {task.type === "recurring"
176
+ ? <RecurringIcon className="w-3.5 h-3.5" />
177
+ : task.execute_at
178
+ ? <ScheduledIcon className="w-3.5 h-3.5" />
179
+ : <TaskOnceIcon className="w-3.5 h-3.5" />
180
+ }
181
+ {task.type === "recurring" && task.recurrence ? formatCron(task.recurrence) : task.type}
182
+ </span>
182
183
  <span>Priority: {task.priority}</span>
183
- {task.recurrence && <span>Recurrence: {task.recurrence}</span>}
184
- <span>Created: {new Date(task.created_at).toLocaleString()}</span>
184
+ {task.next_run && (
185
+ <span className="text-[#f97316]">{formatRelativeTime(task.next_run)}</span>
186
+ )}
187
+ {!task.next_run && task.execute_at && (
188
+ <span className="text-[#f97316]">{formatRelativeTime(task.execute_at)}</span>
189
+ )}
190
+ <span>Created: {new Date(task.created_at).toLocaleDateString()}</span>
185
191
  </div>
186
192
  </div>
187
193
  ))}
@@ -266,7 +272,8 @@ function TaskDetailPanel({ task, statusColors, onClose, onSelectAgent, loading }
266
272
  {task.recurrence && (
267
273
  <div>
268
274
  <span className="text-[#666]">Recurrence</span>
269
- <p>{task.recurrence}</p>
275
+ <p>{formatCron(task.recurrence)}</p>
276
+ <p className="text-xs text-[#444] mt-0.5 font-mono">{task.recurrence}</p>
270
277
  </div>
271
278
  )}
272
279
  </div>
@@ -280,7 +287,7 @@ function TaskDetailPanel({ task, statusColors, onClose, onSelectAgent, loading }
280
287
  {task.execute_at && (
281
288
  <div className="flex justify-between">
282
289
  <span className="text-[#666]">Scheduled</span>
283
- <span>{new Date(task.execute_at).toLocaleString()}</span>
290
+ <span className="text-[#f97316]">{formatRelativeTime(task.execute_at)}</span>
284
291
  </div>
285
292
  )}
286
293
  {task.executed_at && (
@@ -298,7 +305,7 @@ function TaskDetailPanel({ task, statusColors, onClose, onSelectAgent, loading }
298
305
  {task.next_run && (
299
306
  <div className="flex justify-between">
300
307
  <span className="text-[#666]">Next Run</span>
301
- <span>{new Date(task.next_run).toLocaleString()}</span>
308
+ <span className="text-[#f97316]">{formatRelativeTime(task.next_run)}</span>
302
309
  </div>
303
310
  )}
304
311
  </div>
@@ -488,3 +495,103 @@ function TrajectoryView({ trajectory }: { trajectory: TaskTrajectoryStep[] }) {
488
495
  </div>
489
496
  );
490
497
  }
498
+
499
+ // --- Schedule formatting helpers ---
500
+
501
+ const DAY_NAMES = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
502
+
503
+ function formatCron(cron: string): string {
504
+ try {
505
+ const parts = cron.trim().split(/\s+/);
506
+ if (parts.length !== 5) return cron;
507
+ const [minute, hour, dayOfMonth, month, dayOfWeek] = parts;
508
+
509
+ // Every N minutes: */N * * * *
510
+ if (minute.startsWith("*/") && hour === "*" && dayOfMonth === "*" && month === "*" && dayOfWeek === "*") {
511
+ const n = parseInt(minute.slice(2));
512
+ if (n === 1) return "Every minute";
513
+ return `Every ${n} minutes`;
514
+ }
515
+
516
+ // Every hour: 0 * * * *
517
+ if (minute !== "*" && !minute.includes("/") && hour === "*" && dayOfMonth === "*" && month === "*" && dayOfWeek === "*") {
518
+ return "Every hour";
519
+ }
520
+
521
+ // Every N hours: 0 */N * * *
522
+ if (hour.startsWith("*/") && dayOfMonth === "*" && month === "*" && dayOfWeek === "*") {
523
+ const n = parseInt(hour.slice(2));
524
+ if (n === 1) return "Every hour";
525
+ return `Every ${n} hours`;
526
+ }
527
+
528
+ const formatTime = (h: string, m: string): string => {
529
+ const hr = parseInt(h);
530
+ const mn = parseInt(m);
531
+ if (isNaN(hr)) return "";
532
+ const ampm = hr >= 12 ? "PM" : "AM";
533
+ const h12 = hr === 0 ? 12 : hr > 12 ? hr - 12 : hr;
534
+ return `${h12}:${mn.toString().padStart(2, "0")} ${ampm}`;
535
+ };
536
+
537
+ if (hour !== "*" && !hour.includes("/") && dayOfMonth === "*" && month === "*") {
538
+ const timeStr = formatTime(hour, minute);
539
+
540
+ if (dayOfWeek === "*") return `Daily at ${timeStr}`;
541
+
542
+ const days = dayOfWeek.split(",").map(d => {
543
+ const num = parseInt(d.trim());
544
+ return DAY_NAMES[num] || d;
545
+ });
546
+
547
+ if (days.length === 7) return `Daily at ${timeStr}`;
548
+ if (days.length === 5 && !days.includes("Sat") && !days.includes("Sun")) {
549
+ return `Weekdays at ${timeStr}`;
550
+ }
551
+ if (days.length === 1) return `Weekly on ${days[0]} at ${timeStr}`;
552
+ return `${days.join(" & ")} at ${timeStr}`;
553
+ }
554
+
555
+ return cron;
556
+ } catch {
557
+ return cron;
558
+ }
559
+ }
560
+
561
+ function formatRelativeTime(dateStr: string): string {
562
+ const date = new Date(dateStr);
563
+ const now = new Date();
564
+ const diffMs = date.getTime() - now.getTime();
565
+ const absDiffMs = Math.abs(diffMs);
566
+ const isFuture = diffMs > 0;
567
+
568
+ const minutes = Math.floor(absDiffMs / 60000);
569
+ const hours = Math.floor(absDiffMs / 3600000);
570
+ const days = Math.floor(absDiffMs / 86400000);
571
+
572
+ const timeStr = date.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" });
573
+
574
+ const isToday = date.toDateString() === now.toDateString();
575
+ const tomorrow = new Date(now);
576
+ tomorrow.setDate(tomorrow.getDate() + 1);
577
+ const isTomorrow = date.toDateString() === tomorrow.toDateString();
578
+ const yesterday = new Date(now);
579
+ yesterday.setDate(yesterday.getDate() - 1);
580
+ const isYesterday = date.toDateString() === yesterday.toDateString();
581
+
582
+ if (isToday) {
583
+ if (minutes < 1) return isFuture ? "now" : "just now";
584
+ if (minutes < 60) return isFuture ? `in ${minutes} min (${timeStr})` : `${minutes} min ago`;
585
+ return isFuture ? `in ${hours}h (${timeStr})` : `${hours}h ago`;
586
+ }
587
+
588
+ if (isTomorrow) return `Tomorrow at ${timeStr}`;
589
+ if (isYesterday) return `Yesterday at ${timeStr}`;
590
+
591
+ if (days < 7) {
592
+ const dayName = DAY_NAMES[date.getDay()];
593
+ return `${dayName} at ${timeStr}`;
594
+ }
595
+
596
+ return date.toLocaleDateString([], { month: "short", day: "numeric" }) + ` at ${timeStr}`;
597
+ }
package/src/web/types.ts CHANGED
@@ -143,7 +143,7 @@ export interface OnboardingStatus {
143
143
  has_any_keys: boolean;
144
144
  }
145
145
 
146
- export type Route = "dashboard" | "agents" | "tasks" | "mcp" | "skills" | "tests" | "telemetry" | "settings" | "api";
146
+ export type Route = "dashboard" | "activity" | "agents" | "tasks" | "mcp" | "skills" | "tests" | "telemetry" | "settings" | "api";
147
147
 
148
148
  // Tool use content block in trajectory
149
149
  export interface ToolUseBlock {