@agenticmail/enterprise 0.5.199 → 0.5.201

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 (110) hide show
  1. package/dist/agent-heartbeat-EGMBRD3R.js +510 -0
  2. package/dist/chunk-5C3SCMY5.js +4457 -0
  3. package/dist/chunk-6OZYUTPL.js +1224 -0
  4. package/dist/chunk-ZR4Z42HT.js +3679 -0
  5. package/dist/cli-agent-PLMDHMRR.js +1602 -0
  6. package/dist/cli-serve-PLBAWN7N.js +114 -0
  7. package/dist/cli.js +3 -3
  8. package/dist/dashboard/app.js +3 -0
  9. package/dist/dashboard/components/icons.js +1 -0
  10. package/dist/dashboard/pages/activity.js +14 -1
  11. package/dist/dashboard/pages/agent-detail/activity.js +17 -1
  12. package/dist/dashboard/pages/agent-detail/autonomy.js +17 -1
  13. package/dist/dashboard/pages/agent-detail/budget.js +25 -2
  14. package/dist/dashboard/pages/agent-detail/channels.js +10 -1
  15. package/dist/dashboard/pages/agent-detail/communication.js +10 -1
  16. package/dist/dashboard/pages/agent-detail/configuration.js +42 -1
  17. package/dist/dashboard/pages/agent-detail/deployment.js +147 -16
  18. package/dist/dashboard/pages/agent-detail/email.js +10 -1
  19. package/dist/dashboard/pages/agent-detail/guardrails.js +27 -3
  20. package/dist/dashboard/pages/agent-detail/manager.js +15 -1
  21. package/dist/dashboard/pages/agent-detail/meeting-browser.js +14 -2
  22. package/dist/dashboard/pages/agent-detail/memory.js +24 -5
  23. package/dist/dashboard/pages/agent-detail/overview.js +159 -21
  24. package/dist/dashboard/pages/agent-detail/permissions.js +28 -7
  25. package/dist/dashboard/pages/agent-detail/personal-details.js +17 -1
  26. package/dist/dashboard/pages/agent-detail/security.js +21 -5
  27. package/dist/dashboard/pages/agent-detail/skills-section.js +9 -1
  28. package/dist/dashboard/pages/agent-detail/tool-security.js +35 -8
  29. package/dist/dashboard/pages/agent-detail/tools.js +10 -1
  30. package/dist/dashboard/pages/agent-detail/whatsapp.js +11 -1
  31. package/dist/dashboard/pages/agent-detail/workforce.js +19 -4
  32. package/dist/dashboard/pages/agents.js +15 -1
  33. package/dist/dashboard/pages/approvals.js +15 -1
  34. package/dist/dashboard/pages/audit.js +23 -1
  35. package/dist/dashboard/pages/compliance.js +24 -2
  36. package/dist/dashboard/pages/dashboard.js +25 -6
  37. package/dist/dashboard/pages/dlp.js +23 -2
  38. package/dist/dashboard/pages/domain-status.js +51 -7
  39. package/dist/dashboard/pages/guardrails.js +29 -3
  40. package/dist/dashboard/pages/journal.js +24 -4
  41. package/dist/dashboard/pages/knowledge-contributions.js +69 -3
  42. package/dist/dashboard/pages/knowledge-import.js +6 -1
  43. package/dist/dashboard/pages/knowledge.js +51 -9
  44. package/dist/dashboard/pages/messages.js +28 -5
  45. package/dist/dashboard/pages/org-chart.js +18 -1
  46. package/dist/dashboard/pages/settings.js +30 -6
  47. package/dist/dashboard/pages/skill-connections.js +18 -4
  48. package/dist/dashboard/pages/skills.js +11 -1
  49. package/dist/dashboard/pages/task-pipeline.js +455 -0
  50. package/dist/dashboard/pages/users.js +14 -1
  51. package/dist/dashboard/pages/vault.js +22 -2
  52. package/dist/dashboard/pages/workforce.js +17 -1
  53. package/dist/index.js +3 -3
  54. package/dist/routes-KHABOHOV.js +13273 -0
  55. package/dist/runtime-6WFHCG3N.js +45 -0
  56. package/dist/server-BENJQHTB.js +15 -0
  57. package/dist/setup-7RQIFV5Y.js +20 -0
  58. package/package.json +1 -1
  59. package/src/dashboard/HELP-TOOLTIPS-GUIDE.md +45 -0
  60. package/src/dashboard/app.js +3 -0
  61. package/src/dashboard/components/icons.js +1 -0
  62. package/src/dashboard/pages/activity.js +14 -1
  63. package/src/dashboard/pages/agent-detail/activity.js +17 -1
  64. package/src/dashboard/pages/agent-detail/autonomy.js +17 -1
  65. package/src/dashboard/pages/agent-detail/budget.js +25 -2
  66. package/src/dashboard/pages/agent-detail/channels.js +10 -1
  67. package/src/dashboard/pages/agent-detail/communication.js +10 -1
  68. package/src/dashboard/pages/agent-detail/configuration.js +42 -1
  69. package/src/dashboard/pages/agent-detail/deployment.js +147 -16
  70. package/src/dashboard/pages/agent-detail/email.js +10 -1
  71. package/src/dashboard/pages/agent-detail/guardrails.js +27 -3
  72. package/src/dashboard/pages/agent-detail/manager.js +15 -1
  73. package/src/dashboard/pages/agent-detail/meeting-browser.js +14 -2
  74. package/src/dashboard/pages/agent-detail/memory.js +24 -5
  75. package/src/dashboard/pages/agent-detail/overview.js +159 -21
  76. package/src/dashboard/pages/agent-detail/permissions.js +28 -7
  77. package/src/dashboard/pages/agent-detail/personal-details.js +17 -1
  78. package/src/dashboard/pages/agent-detail/security.js +21 -5
  79. package/src/dashboard/pages/agent-detail/skills-section.js +9 -1
  80. package/src/dashboard/pages/agent-detail/tool-security.js +35 -8
  81. package/src/dashboard/pages/agent-detail/tools.js +10 -1
  82. package/src/dashboard/pages/agent-detail/whatsapp.js +11 -1
  83. package/src/dashboard/pages/agent-detail/workforce.js +19 -4
  84. package/src/dashboard/pages/agents.js +15 -1
  85. package/src/dashboard/pages/approvals.js +15 -1
  86. package/src/dashboard/pages/audit.js +23 -1
  87. package/src/dashboard/pages/compliance.js +24 -2
  88. package/src/dashboard/pages/dashboard.js +25 -6
  89. package/src/dashboard/pages/dlp.js +23 -2
  90. package/src/dashboard/pages/domain-status.js +51 -7
  91. package/src/dashboard/pages/guardrails.js +29 -3
  92. package/src/dashboard/pages/journal.js +24 -4
  93. package/src/dashboard/pages/knowledge-contributions.js +69 -3
  94. package/src/dashboard/pages/knowledge-import.js +6 -1
  95. package/src/dashboard/pages/knowledge.js +51 -9
  96. package/src/dashboard/pages/messages.js +28 -5
  97. package/src/dashboard/pages/org-chart.js +18 -1
  98. package/src/dashboard/pages/settings.js +30 -6
  99. package/src/dashboard/pages/skill-connections.js +18 -4
  100. package/src/dashboard/pages/skills.js +11 -1
  101. package/src/dashboard/pages/task-pipeline.js +455 -0
  102. package/src/dashboard/pages/users.js +14 -1
  103. package/src/dashboard/pages/vault.js +22 -2
  104. package/src/dashboard/pages/workforce.js +17 -1
  105. package/src/engine/model-fallback.ts +141 -0
  106. package/src/engine/routes.ts +5 -0
  107. package/src/engine/task-queue-after-spawn.ts +66 -0
  108. package/src/engine/task-queue-before-spawn.ts +109 -0
  109. package/src/engine/task-queue-routes.ts +133 -0
  110. package/src/engine/task-queue.ts +369 -0
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Model Fallback / Backup Provider System
3
+ *
4
+ * Provides a chain of fallback models when the primary model fails.
5
+ * Configurable per-org and per-agent. Tracks which models failed and
6
+ * which backup was ultimately used.
7
+ */
8
+
9
+ export interface ModelFallbackConfig {
10
+ primary: string; // e.g. 'openai/gpt-4o'
11
+ fallbacks: string[]; // ordered fallback chain
12
+ maxRetries: number; // retries per model before moving to next
13
+ retryDelayMs: number; // delay between retries
14
+ enabled: boolean;
15
+ }
16
+
17
+ export interface FallbackResult {
18
+ modelUsed: string;
19
+ wasFallback: boolean;
20
+ attemptedModels: string[];
21
+ failureReasons: Record<string, string>;
22
+ }
23
+
24
+ const DEFAULT_CONFIG: ModelFallbackConfig = {
25
+ primary: '',
26
+ fallbacks: [],
27
+ maxRetries: 2,
28
+ retryDelayMs: 1000,
29
+ enabled: true,
30
+ };
31
+
32
+ /**
33
+ * Build a model chain from agent/org config.
34
+ * Checks agent-level fallbacks first, then org-level defaults.
35
+ */
36
+ export function buildModelChain(
37
+ agentConfig?: { model?: string; fallbackModels?: string[]; modelFallback?: ModelFallbackConfig },
38
+ orgConfig?: { defaultModel?: string; fallbackModels?: string[]; modelFallback?: ModelFallbackConfig }
39
+ ): ModelFallbackConfig {
40
+ // Agent-level explicit config
41
+ if (agentConfig?.modelFallback?.enabled !== false && agentConfig?.modelFallback) {
42
+ return { ...DEFAULT_CONFIG, ...agentConfig.modelFallback };
43
+ }
44
+
45
+ const primary = (typeof agentConfig?.model === 'string' ? agentConfig.model : '')
46
+ || orgConfig?.defaultModel || '';
47
+
48
+ const fallbacks = agentConfig?.fallbackModels
49
+ || orgConfig?.fallbackModels
50
+ || orgConfig?.modelFallback?.fallbacks
51
+ || [];
52
+
53
+ return {
54
+ ...DEFAULT_CONFIG,
55
+ primary,
56
+ fallbacks: fallbacks.filter(m => m !== primary),
57
+ };
58
+ }
59
+
60
+ /**
61
+ * Execute a function with model fallback.
62
+ * Tries primary model, then each fallback in order.
63
+ *
64
+ * @param chain - The model fallback configuration
65
+ * @param fn - Async function that takes a model name and executes
66
+ * @returns The result + metadata about which model was used
67
+ */
68
+ export async function withModelFallback<T>(
69
+ chain: ModelFallbackConfig,
70
+ fn: (model: string) => Promise<T>
71
+ ): Promise<{ result: T } & FallbackResult> {
72
+ const models = [chain.primary, ...chain.fallbacks].filter(Boolean);
73
+ if (!models.length) throw new Error('No models configured');
74
+
75
+ const failureReasons: Record<string, string> = {};
76
+ const attemptedModels: string[] = [];
77
+
78
+ for (let mi = 0; mi < models.length; mi++) {
79
+ const model = models[mi];
80
+ attemptedModels.push(model);
81
+
82
+ for (let retry = 0; retry < chain.maxRetries; retry++) {
83
+ try {
84
+ const result = await fn(model);
85
+ return {
86
+ result,
87
+ modelUsed: model,
88
+ wasFallback: mi > 0,
89
+ attemptedModels,
90
+ failureReasons,
91
+ };
92
+ } catch (err: any) {
93
+ const reason = err?.message || String(err);
94
+
95
+ // Don't retry on auth errors or invalid model — move to next model immediately
96
+ if (/auth|unauthorized|forbidden|invalid.*model|not.*found|does.*not.*exist/i.test(reason)) {
97
+ failureReasons[model] = reason;
98
+ break;
99
+ }
100
+
101
+ // Rate limit / overloaded — retry after delay
102
+ if (/rate.?limit|overloaded|too.?many|429|503|capacity/i.test(reason)) {
103
+ failureReasons[model] = reason;
104
+ if (retry < chain.maxRetries - 1) {
105
+ await sleep(chain.retryDelayMs * (retry + 1));
106
+ continue;
107
+ }
108
+ break;
109
+ }
110
+
111
+ // Other errors — retry
112
+ failureReasons[model] = reason;
113
+ if (retry < chain.maxRetries - 1) {
114
+ await sleep(chain.retryDelayMs);
115
+ }
116
+ }
117
+ }
118
+ }
119
+
120
+ throw new ModelFallbackExhaustedError(
121
+ `All models failed: ${models.join(', ')}`,
122
+ attemptedModels,
123
+ failureReasons
124
+ );
125
+ }
126
+
127
+ export class ModelFallbackExhaustedError extends Error {
128
+ attemptedModels: string[];
129
+ failureReasons: Record<string, string>;
130
+
131
+ constructor(message: string, attemptedModels: string[], failureReasons: Record<string, string>) {
132
+ super(message);
133
+ this.name = 'ModelFallbackExhaustedError';
134
+ this.attemptedModels = attemptedModels;
135
+ this.failureReasons = failureReasons;
136
+ }
137
+ }
138
+
139
+ function sleep(ms: number): Promise<void> {
140
+ return new Promise(resolve => setTimeout(resolve, ms));
141
+ }
@@ -86,6 +86,8 @@ import { createChatWebhookRoutes } from './chat-webhook-routes.js';
86
86
  import { ChatPoller } from './chat-poller.js';
87
87
  import { EmailPoller } from './email-poller.js';
88
88
  import { MessagingPoller } from './messaging-poller.js';
89
+ import { TaskQueueManager } from './task-queue.js';
90
+ import { createTaskQueueRoutes } from './task-queue-routes.js';
89
91
  import type { DatabaseAdapter } from '../db/adapter.js';
90
92
 
91
93
  const engine = new Hono<AppEnv>();
@@ -128,6 +130,7 @@ const knowledgeContribution = new KnowledgeContributionManager({ memoryCallback:
128
130
  import { AgentHierarchyManager } from './agent-hierarchy.js';
129
131
  let hierarchyManager: AgentHierarchyManager | null = null;
130
132
  const knowledgeImport = new KnowledgeImportManager({ knowledgeContribution });
133
+ const taskQueue = new TaskQueueManager();
131
134
  const skillUpdater = new SkillAutoUpdater({ registry: communityRegistry });
132
135
 
133
136
  // Wire onboarding into guardrails for onboarding gate checks
@@ -190,6 +193,7 @@ engine.route('/anomaly-rules', createAnomalyRoutes(guardrails));
190
193
  engine.route('/journal', createJournalRoutes(journal));
191
194
  engine.route('/messages', createCommunicationRoutes(commBus));
192
195
  engine.route('/tasks', createTaskRoutes(commBus));
196
+ engine.route('/task-pipeline', createTaskQueueRoutes(taskQueue));
193
197
  engine.route('/compliance', createComplianceRoutes(compliance));
194
198
 
195
199
  engine.route('/', createCatalogRoutes({
@@ -486,6 +490,7 @@ export async function setEngineDb(
486
490
  vault.setDb(db),
487
491
  storageManager.setDb(db),
488
492
  policyImporter.setDb(db),
493
+ (async () => { (taskQueue as any).db = (db as any)?.db || db; await taskQueue.init(); })(),
489
494
  ]);
490
495
  // Initialize hierarchy manager + start background task monitor
491
496
  hierarchyManager = new AgentHierarchyManager(db);
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Task Queue — After Spawn Hook
3
+ *
4
+ * Called AFTER an agent finishes a task (success, failure, or cancellation).
5
+ * Records the outcome, model used, tokens, cost, and any result metadata.
6
+ */
7
+
8
+ import type { TaskQueueManager, TaskStatus } from './task-queue.js';
9
+
10
+ export interface AfterSpawnContext {
11
+ taskId: string;
12
+ status: 'completed' | 'failed' | 'cancelled';
13
+ result?: Record<string, unknown>;
14
+ error?: string;
15
+ modelUsed?: string;
16
+ tokensUsed?: number;
17
+ costUsd?: number;
18
+ sessionId?: string;
19
+ }
20
+
21
+ /**
22
+ * Record task outcome AFTER the agent finishes.
23
+ */
24
+ export async function afterSpawn(
25
+ taskQueue: TaskQueueManager,
26
+ ctx: AfterSpawnContext
27
+ ): Promise<void> {
28
+ const updates: Record<string, unknown> = {
29
+ status: ctx.status as TaskStatus,
30
+ };
31
+
32
+ if (ctx.result) updates.result = ctx.result;
33
+ if (ctx.error) updates.error = ctx.error;
34
+ if (ctx.modelUsed) updates.modelUsed = ctx.modelUsed;
35
+ if (ctx.tokensUsed !== undefined) updates.tokensUsed = ctx.tokensUsed;
36
+ if (ctx.costUsd !== undefined) updates.costUsd = ctx.costUsd;
37
+ if (ctx.sessionId) updates.sessionId = ctx.sessionId;
38
+
39
+ await taskQueue.updateTask(ctx.taskId, updates as any);
40
+ }
41
+
42
+ /**
43
+ * Mark a task as in_progress (call when agent session actually starts executing).
44
+ */
45
+ export async function markInProgress(
46
+ taskQueue: TaskQueueManager,
47
+ taskId: string,
48
+ opts?: { sessionId?: string; modelUsed?: string }
49
+ ): Promise<void> {
50
+ await taskQueue.updateTask(taskId, {
51
+ status: 'in_progress',
52
+ ...(opts?.sessionId ? { sessionId: opts.sessionId } : {}),
53
+ ...(opts?.modelUsed ? { modelUsed: opts.modelUsed } : {}),
54
+ });
55
+ }
56
+
57
+ /**
58
+ * Update task progress (0-100) during execution.
59
+ */
60
+ export async function updateProgress(
61
+ taskQueue: TaskQueueManager,
62
+ taskId: string,
63
+ progress: number
64
+ ): Promise<void> {
65
+ await taskQueue.updateTask(taskId, { progress: Math.min(100, Math.max(0, progress)) });
66
+ }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Task Queue — Before Spawn Hook
3
+ *
4
+ * Called BEFORE an agent is spawned for a task. Records the task intent
5
+ * with smart metadata extraction from the request context.
6
+ *
7
+ * Usage:
8
+ * const taskId = await beforeSpawn(taskQueue, { ... });
9
+ * // ... spawn agent ...
10
+ * await afterSpawn(taskQueue, taskId, { ... });
11
+ */
12
+
13
+ import type { TaskQueueManager, TaskPriority } from './task-queue.js';
14
+
15
+ export interface BeforeSpawnContext {
16
+ orgId: string;
17
+ agentId: string;
18
+ agentName: string;
19
+ createdBy?: string; // who initiated — agent ID, user ID, or 'system'
20
+ createdByName?: string;
21
+ task: string; // raw task description
22
+ model?: string;
23
+ fallbackModel?: string;
24
+ sessionId?: string;
25
+ parentTaskId?: string;
26
+ relatedAgentIds?: string[];
27
+ priority?: TaskPriority;
28
+ estimatedDurationMs?: number;
29
+ }
30
+
31
+ /**
32
+ * Extract smart metadata from a task description.
33
+ * Infers category, title, tags, and priority from natural language.
34
+ */
35
+ function extractTaskMetadata(task: string): {
36
+ title: string;
37
+ category: string;
38
+ tags: string[];
39
+ priority: TaskPriority;
40
+ } {
41
+ const lower = task.toLowerCase();
42
+
43
+ // Extract a short title (first sentence or first 80 chars)
44
+ let title = task.split(/[.\n!?]/)[0]?.trim() || task;
45
+ if (title.length > 80) title = title.slice(0, 77) + '...';
46
+
47
+ // Infer category
48
+ let category = 'custom';
49
+ if (/\b(email|inbox|reply|forward|send mail|compose)\b/.test(lower)) category = 'email';
50
+ else if (/\b(research|search|find|look up|investigate|analyze)\b/.test(lower)) category = 'research';
51
+ else if (/\b(meeting|calendar|schedule|call|agenda)\b/.test(lower)) category = 'meeting';
52
+ else if (/\b(workflow|pipeline|automat|process|batch)\b/.test(lower)) category = 'workflow';
53
+ else if (/\b(write|draft|document|report|summary|blog|article)\b/.test(lower)) category = 'writing';
54
+ else if (/\b(deploy|build|compile|publish|release|ship)\b/.test(lower)) category = 'deployment';
55
+ else if (/\b(review|approve|check|audit|verify)\b/.test(lower)) category = 'review';
56
+ else if (/\b(monitor|watch|track|alert|notify)\b/.test(lower)) category = 'monitoring';
57
+
58
+ // Extract tags from common patterns
59
+ const tags: string[] = [];
60
+ if (/\burgent\b/i.test(task)) tags.push('urgent');
61
+ if (/\basap\b/i.test(task)) tags.push('asap');
62
+ if (/\bfollow[- ]?up\b/i.test(task)) tags.push('follow-up');
63
+ if (/\bbug\b|error\b|fix\b/i.test(task)) tags.push('bug-fix');
64
+ if (/\bcustomer\b|client\b/i.test(task)) tags.push('customer');
65
+ if (/\binternal\b/i.test(task)) tags.push('internal');
66
+
67
+ // Infer priority
68
+ let priority: TaskPriority = 'normal';
69
+ if (/\b(urgent|critical|emergency|asap|immediately)\b/i.test(task)) priority = 'urgent';
70
+ else if (/\b(important|high.?priority|priority|rush)\b/i.test(task)) priority = 'high';
71
+ else if (/\b(low.?priority|when.?you.?can|no.?rush|whenever)\b/i.test(task)) priority = 'low';
72
+
73
+ return { title, category, tags, priority };
74
+ }
75
+
76
+ /**
77
+ * Record a task BEFORE spawning the agent.
78
+ * Returns the task ID for linking with afterSpawn.
79
+ */
80
+ export async function beforeSpawn(
81
+ taskQueue: TaskQueueManager,
82
+ ctx: BeforeSpawnContext
83
+ ): Promise<string> {
84
+ const meta = extractTaskMetadata(ctx.task);
85
+
86
+ const task = await taskQueue.createTask({
87
+ orgId: ctx.orgId,
88
+ assignedTo: ctx.agentId,
89
+ assignedToName: ctx.agentName,
90
+ createdBy: ctx.createdBy || 'system',
91
+ createdByName: ctx.createdByName || 'System',
92
+ title: meta.title,
93
+ description: ctx.task,
94
+ category: meta.category,
95
+ tags: meta.tags,
96
+ priority: ctx.priority || meta.priority,
97
+ parentTaskId: ctx.parentTaskId,
98
+ relatedAgentIds: ctx.relatedAgentIds,
99
+ sessionId: ctx.sessionId,
100
+ model: ctx.model,
101
+ fallbackModel: ctx.fallbackModel,
102
+ estimatedDurationMs: ctx.estimatedDurationMs,
103
+ });
104
+
105
+ // Immediately mark as assigned
106
+ await taskQueue.updateTask(task.id, { status: 'assigned' });
107
+
108
+ return task.id;
109
+ }
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Task Queue Routes
3
+ *
4
+ * REST + SSE endpoints for the centralized task pipeline.
5
+ * Dashboard subscribes to /task-pipeline/stream for real-time updates.
6
+ */
7
+
8
+ import { Hono } from 'hono';
9
+ import type { TaskQueueManager } from './task-queue.js';
10
+
11
+ export function createTaskQueueRoutes(taskQueue: TaskQueueManager) {
12
+ const router = new Hono();
13
+
14
+ // GET /task-pipeline — all tasks (paginated)
15
+ router.get('/', async (c) => {
16
+ const orgId = c.req.query('orgId') || '';
17
+ const limit = parseInt(c.req.query('limit') || '100');
18
+ const offset = parseInt(c.req.query('offset') || '0');
19
+ const tasks = await taskQueue.getTaskHistory(orgId, limit, offset);
20
+ return c.json({ tasks });
21
+ });
22
+
23
+ // GET /task-pipeline/active — only active tasks
24
+ router.get('/active', (c) => {
25
+ const orgId = c.req.query('orgId') || '';
26
+ const tasks = taskQueue.getActiveTasks(orgId);
27
+ return c.json({ tasks });
28
+ });
29
+
30
+ // GET /task-pipeline/stats — pipeline statistics
31
+ router.get('/stats', (c) => {
32
+ const orgId = c.req.query('orgId') || '';
33
+ const stats = taskQueue.getPipelineStats(orgId);
34
+ return c.json(stats);
35
+ });
36
+
37
+ // GET /task-pipeline/agent/:agentId — tasks for specific agent
38
+ router.get('/agent/:agentId', (c) => {
39
+ const agentId = c.req.param('agentId');
40
+ const includeCompleted = c.req.query('completed') === 'true';
41
+ const tasks = taskQueue.getAgentTasks(agentId, includeCompleted);
42
+ return c.json({ tasks });
43
+ });
44
+
45
+ // GET /task-pipeline/:id — single task detail
46
+ router.get('/:id', (c) => {
47
+ const task = taskQueue.getTask(c.req.param('id'));
48
+ if (!task) return c.json({ error: 'Task not found' }, 404);
49
+ return c.json({ task });
50
+ });
51
+
52
+ // POST /task-pipeline — create task manually
53
+ router.post('/', async (c) => {
54
+ const body = await c.req.json();
55
+ const task = await taskQueue.createTask({
56
+ orgId: body.orgId || '',
57
+ assignedTo: body.assignedTo || '',
58
+ assignedToName: body.assignedToName || '',
59
+ createdBy: body.createdBy || 'dashboard',
60
+ createdByName: body.createdByName || 'Dashboard User',
61
+ title: body.title || 'Untitled Task',
62
+ description: body.description || '',
63
+ category: body.category,
64
+ tags: body.tags,
65
+ priority: body.priority,
66
+ parentTaskId: body.parentTaskId,
67
+ relatedAgentIds: body.relatedAgentIds,
68
+ model: body.model,
69
+ fallbackModel: body.fallbackModel,
70
+ });
71
+ return c.json({ task }, 201);
72
+ });
73
+
74
+ // PATCH /task-pipeline/:id — update task
75
+ router.patch('/:id', async (c) => {
76
+ const body = await c.req.json();
77
+ const task = await taskQueue.updateTask(c.req.param('id'), body);
78
+ if (!task) return c.json({ error: 'Task not found' }, 404);
79
+ return c.json({ task });
80
+ });
81
+
82
+ // POST /task-pipeline/:id/cancel — cancel a task
83
+ router.post('/:id/cancel', async (c) => {
84
+ const task = await taskQueue.updateTask(c.req.param('id'), { status: 'cancelled' });
85
+ if (!task) return c.json({ error: 'Task not found' }, 404);
86
+ return c.json({ task });
87
+ });
88
+
89
+ // ─── SSE Stream ────────────────────────────────────────
90
+ router.get('/stream', (c) => {
91
+ let alive = true;
92
+ const stream = new ReadableStream({
93
+ start(controller) {
94
+ const enc = new TextEncoder();
95
+ const send = (data: string) => {
96
+ if (!alive) return;
97
+ try { controller.enqueue(enc.encode(data)); } catch { alive = false; }
98
+ };
99
+
100
+ // Send initial state
101
+ const active = taskQueue.getActiveTasks();
102
+ const stats = taskQueue.getPipelineStats();
103
+ send(`data: ${JSON.stringify({ type: 'init', tasks: active, stats })}\n\n`);
104
+
105
+ // Subscribe to real-time events
106
+ const unsub = taskQueue.subscribe((event) => {
107
+ send(`data: ${JSON.stringify(event)}\n\n`);
108
+ });
109
+
110
+ // Heartbeat every 30s
111
+ const hb = setInterval(() => { send(': heartbeat\n\n'); }, 30_000);
112
+
113
+ // Cleanup on close
114
+ c.req.raw.signal?.addEventListener('abort', () => {
115
+ alive = false;
116
+ unsub();
117
+ clearInterval(hb);
118
+ try { controller.close(); } catch { /* ignore */ }
119
+ });
120
+ },
121
+ });
122
+
123
+ return new Response(stream, {
124
+ headers: {
125
+ 'Content-Type': 'text/event-stream',
126
+ 'Cache-Control': 'no-cache',
127
+ 'Connection': 'keep-alive',
128
+ },
129
+ });
130
+ });
131
+
132
+ return router;
133
+ }