apteva 0.4.15 → 0.4.17
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/dist/App.fq4xbpcz.js +228 -0
- package/dist/index.html +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/auth/middleware.ts +10 -0
- package/src/db.ts +159 -4
- package/src/mcp-handler.ts +387 -0
- package/src/routes/api/agent-utils.ts +12 -2
- package/src/routes/api/mcp.ts +174 -3
- package/src/server.ts +3 -3
- package/src/web/App.tsx +11 -2
- package/src/web/components/activity/ActivityPage.tsx +326 -0
- package/src/web/components/activity/index.ts +1 -0
- package/src/web/components/common/Icons.tsx +35 -0
- package/src/web/components/common/index.ts +1 -0
- package/src/web/components/dashboard/Dashboard.tsx +81 -1
- package/src/web/components/index.ts +1 -0
- package/src/web/components/layout/Sidebar.tsx +7 -1
- package/src/web/components/mcp/McpPage.tsx +9 -3
- package/src/web/components/tasks/TasksPage.tsx +122 -15
- package/src/web/context/TelemetryContext.tsx +14 -1
- package/src/web/context/index.ts +1 -1
- package/src/web/types.ts +1 -1
- package/dist/App.jdzxkzm1.js +0 -228
|
@@ -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
|
|
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.
|
|
184
|
-
|
|
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>{
|
|
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>{
|
|
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
|
+
}
|
|
@@ -20,6 +20,7 @@ interface TelemetryContextValue {
|
|
|
20
20
|
lastActivityByAgent: Record<string, { timestamp: string; category: string; type: string }>;
|
|
21
21
|
activeAgents: Record<string, { type: string; expiresAt: number }>;
|
|
22
22
|
statusChangeCounter: number;
|
|
23
|
+
taskChangeCounter: number;
|
|
23
24
|
clearEvents: () => void;
|
|
24
25
|
}
|
|
25
26
|
|
|
@@ -33,6 +34,7 @@ export function TelemetryProvider({ children }: { children: React.ReactNode }) {
|
|
|
33
34
|
const [lastActivityByAgent, setLastActivityByAgent] = useState<Record<string, { timestamp: string; category: string; type: string }>>({});
|
|
34
35
|
const [activeAgents, setActiveAgents] = useState<Record<string, { type: string; expiresAt: number }>>({});
|
|
35
36
|
const [statusChangeCounter, setStatusChangeCounter] = useState(0);
|
|
37
|
+
const [taskChangeCounter, setTaskChangeCounter] = useState(0);
|
|
36
38
|
const eventSourceRef = useRef<EventSource | null>(null);
|
|
37
39
|
const reconnectTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
38
40
|
|
|
@@ -117,6 +119,11 @@ export function TelemetryProvider({ children }: { children: React.ReactNode }) {
|
|
|
117
119
|
if (data.some((e: TelemetryEvent) => e.category === "system" && (e.type === "agent_started" || e.type === "agent_stopped"))) {
|
|
118
120
|
setStatusChangeCounter(c => c + 1);
|
|
119
121
|
}
|
|
122
|
+
|
|
123
|
+
// Detect task change events
|
|
124
|
+
if (data.some((e: TelemetryEvent) => e.category === "TASK" && (e.type === "task_created" || e.type === "task_updated" || e.type === "task_deleted"))) {
|
|
125
|
+
setTaskChangeCounter(c => c + 1);
|
|
126
|
+
}
|
|
120
127
|
}
|
|
121
128
|
} catch {
|
|
122
129
|
// Ignore parse errors (likely keepalive or empty message)
|
|
@@ -162,7 +169,7 @@ export function TelemetryProvider({ children }: { children: React.ReactNode }) {
|
|
|
162
169
|
}, []);
|
|
163
170
|
|
|
164
171
|
return (
|
|
165
|
-
<TelemetryContext.Provider value={{ connected, events, lastActivityByAgent, activeAgents, statusChangeCounter, clearEvents }}>
|
|
172
|
+
<TelemetryContext.Provider value={{ connected, events, lastActivityByAgent, activeAgents, statusChangeCounter, taskChangeCounter, clearEvents }}>
|
|
166
173
|
{children}
|
|
167
174
|
</TelemetryContext.Provider>
|
|
168
175
|
);
|
|
@@ -235,3 +242,9 @@ export function useAgentStatusChange(): number {
|
|
|
235
242
|
const { statusChangeCounter } = useTelemetryContext();
|
|
236
243
|
return statusChangeCounter;
|
|
237
244
|
}
|
|
245
|
+
|
|
246
|
+
// Hook to trigger task count refetch on task mutations
|
|
247
|
+
export function useTaskChange(): number {
|
|
248
|
+
const { taskChangeCounter } = useTelemetryContext();
|
|
249
|
+
return taskChangeCounter;
|
|
250
|
+
}
|
package/src/web/context/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { TelemetryProvider, useTelemetryContext, useTelemetry, useAgentActivity, useAgentStatusChange } from "./TelemetryContext";
|
|
1
|
+
export { TelemetryProvider, useTelemetryContext, useTelemetry, useAgentActivity, useAgentStatusChange, useTaskChange } from "./TelemetryContext";
|
|
2
2
|
export type { TelemetryEvent } from "./TelemetryContext";
|
|
3
3
|
|
|
4
4
|
export { AuthProvider, useAuth, useAuthHeaders } from "./AuthContext";
|
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 {
|