@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.
- package/dist/agent-heartbeat-EGMBRD3R.js +510 -0
- package/dist/chunk-5C3SCMY5.js +4457 -0
- package/dist/chunk-6OZYUTPL.js +1224 -0
- package/dist/chunk-ZR4Z42HT.js +3679 -0
- package/dist/cli-agent-PLMDHMRR.js +1602 -0
- package/dist/cli-serve-PLBAWN7N.js +114 -0
- package/dist/cli.js +3 -3
- package/dist/dashboard/app.js +3 -0
- package/dist/dashboard/components/icons.js +1 -0
- package/dist/dashboard/pages/activity.js +14 -1
- package/dist/dashboard/pages/agent-detail/activity.js +17 -1
- package/dist/dashboard/pages/agent-detail/autonomy.js +17 -1
- package/dist/dashboard/pages/agent-detail/budget.js +25 -2
- package/dist/dashboard/pages/agent-detail/channels.js +10 -1
- package/dist/dashboard/pages/agent-detail/communication.js +10 -1
- package/dist/dashboard/pages/agent-detail/configuration.js +42 -1
- package/dist/dashboard/pages/agent-detail/deployment.js +147 -16
- package/dist/dashboard/pages/agent-detail/email.js +10 -1
- package/dist/dashboard/pages/agent-detail/guardrails.js +27 -3
- package/dist/dashboard/pages/agent-detail/manager.js +15 -1
- package/dist/dashboard/pages/agent-detail/meeting-browser.js +14 -2
- package/dist/dashboard/pages/agent-detail/memory.js +24 -5
- package/dist/dashboard/pages/agent-detail/overview.js +159 -21
- package/dist/dashboard/pages/agent-detail/permissions.js +28 -7
- package/dist/dashboard/pages/agent-detail/personal-details.js +17 -1
- package/dist/dashboard/pages/agent-detail/security.js +21 -5
- package/dist/dashboard/pages/agent-detail/skills-section.js +9 -1
- package/dist/dashboard/pages/agent-detail/tool-security.js +35 -8
- package/dist/dashboard/pages/agent-detail/tools.js +10 -1
- package/dist/dashboard/pages/agent-detail/whatsapp.js +11 -1
- package/dist/dashboard/pages/agent-detail/workforce.js +19 -4
- package/dist/dashboard/pages/agents.js +15 -1
- package/dist/dashboard/pages/approvals.js +15 -1
- package/dist/dashboard/pages/audit.js +23 -1
- package/dist/dashboard/pages/compliance.js +24 -2
- package/dist/dashboard/pages/dashboard.js +25 -6
- package/dist/dashboard/pages/dlp.js +23 -2
- package/dist/dashboard/pages/domain-status.js +51 -7
- package/dist/dashboard/pages/guardrails.js +29 -3
- package/dist/dashboard/pages/journal.js +24 -4
- package/dist/dashboard/pages/knowledge-contributions.js +69 -3
- package/dist/dashboard/pages/knowledge-import.js +6 -1
- package/dist/dashboard/pages/knowledge.js +51 -9
- package/dist/dashboard/pages/messages.js +28 -5
- package/dist/dashboard/pages/org-chart.js +18 -1
- package/dist/dashboard/pages/settings.js +30 -6
- package/dist/dashboard/pages/skill-connections.js +18 -4
- package/dist/dashboard/pages/skills.js +11 -1
- package/dist/dashboard/pages/task-pipeline.js +455 -0
- package/dist/dashboard/pages/users.js +14 -1
- package/dist/dashboard/pages/vault.js +22 -2
- package/dist/dashboard/pages/workforce.js +17 -1
- package/dist/index.js +3 -3
- package/dist/routes-KHABOHOV.js +13273 -0
- package/dist/runtime-6WFHCG3N.js +45 -0
- package/dist/server-BENJQHTB.js +15 -0
- package/dist/setup-7RQIFV5Y.js +20 -0
- package/package.json +1 -1
- package/src/dashboard/HELP-TOOLTIPS-GUIDE.md +45 -0
- package/src/dashboard/app.js +3 -0
- package/src/dashboard/components/icons.js +1 -0
- package/src/dashboard/pages/activity.js +14 -1
- package/src/dashboard/pages/agent-detail/activity.js +17 -1
- package/src/dashboard/pages/agent-detail/autonomy.js +17 -1
- package/src/dashboard/pages/agent-detail/budget.js +25 -2
- package/src/dashboard/pages/agent-detail/channels.js +10 -1
- package/src/dashboard/pages/agent-detail/communication.js +10 -1
- package/src/dashboard/pages/agent-detail/configuration.js +42 -1
- package/src/dashboard/pages/agent-detail/deployment.js +147 -16
- package/src/dashboard/pages/agent-detail/email.js +10 -1
- package/src/dashboard/pages/agent-detail/guardrails.js +27 -3
- package/src/dashboard/pages/agent-detail/manager.js +15 -1
- package/src/dashboard/pages/agent-detail/meeting-browser.js +14 -2
- package/src/dashboard/pages/agent-detail/memory.js +24 -5
- package/src/dashboard/pages/agent-detail/overview.js +159 -21
- package/src/dashboard/pages/agent-detail/permissions.js +28 -7
- package/src/dashboard/pages/agent-detail/personal-details.js +17 -1
- package/src/dashboard/pages/agent-detail/security.js +21 -5
- package/src/dashboard/pages/agent-detail/skills-section.js +9 -1
- package/src/dashboard/pages/agent-detail/tool-security.js +35 -8
- package/src/dashboard/pages/agent-detail/tools.js +10 -1
- package/src/dashboard/pages/agent-detail/whatsapp.js +11 -1
- package/src/dashboard/pages/agent-detail/workforce.js +19 -4
- package/src/dashboard/pages/agents.js +15 -1
- package/src/dashboard/pages/approvals.js +15 -1
- package/src/dashboard/pages/audit.js +23 -1
- package/src/dashboard/pages/compliance.js +24 -2
- package/src/dashboard/pages/dashboard.js +25 -6
- package/src/dashboard/pages/dlp.js +23 -2
- package/src/dashboard/pages/domain-status.js +51 -7
- package/src/dashboard/pages/guardrails.js +29 -3
- package/src/dashboard/pages/journal.js +24 -4
- package/src/dashboard/pages/knowledge-contributions.js +69 -3
- package/src/dashboard/pages/knowledge-import.js +6 -1
- package/src/dashboard/pages/knowledge.js +51 -9
- package/src/dashboard/pages/messages.js +28 -5
- package/src/dashboard/pages/org-chart.js +18 -1
- package/src/dashboard/pages/settings.js +30 -6
- package/src/dashboard/pages/skill-connections.js +18 -4
- package/src/dashboard/pages/skills.js +11 -1
- package/src/dashboard/pages/task-pipeline.js +455 -0
- package/src/dashboard/pages/users.js +14 -1
- package/src/dashboard/pages/vault.js +22 -2
- package/src/dashboard/pages/workforce.js +17 -1
- package/src/engine/model-fallback.ts +141 -0
- package/src/engine/routes.ts +5 -0
- package/src/engine/task-queue-after-spawn.ts +66 -0
- package/src/engine/task-queue-before-spawn.ts +109 -0
- package/src/engine/task-queue-routes.ts +133 -0
- 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
|
+
}
|
package/src/engine/routes.ts
CHANGED
|
@@ -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
|
+
}
|