@memberjunction/server 2.103.0 → 2.105.0
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/agents/skip-agent.d.ts +29 -0
- package/dist/agents/skip-agent.d.ts.map +1 -0
- package/dist/agents/skip-agent.js +1308 -0
- package/dist/agents/skip-agent.js.map +1 -0
- package/dist/agents/skip-sdk.d.ts +47 -0
- package/dist/agents/skip-sdk.d.ts.map +1 -0
- package/dist/agents/skip-sdk.js +269 -0
- package/dist/agents/skip-sdk.js.map +1 -0
- package/dist/config.d.ts +9 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/generated/generated.d.ts +3660 -3386
- package/dist/generated/generated.d.ts.map +1 -1
- package/dist/generated/generated.js +22009 -20223
- package/dist/generated/generated.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -18
- package/dist/index.js.map +1 -1
- package/dist/resolvers/AskSkipResolver.d.ts.map +1 -1
- package/dist/resolvers/AskSkipResolver.js +24 -9
- package/dist/resolvers/AskSkipResolver.js.map +1 -1
- package/dist/resolvers/ComponentRegistryResolver.d.ts +19 -0
- package/dist/resolvers/ComponentRegistryResolver.d.ts.map +1 -1
- package/dist/resolvers/ComponentRegistryResolver.js +140 -2
- package/dist/resolvers/ComponentRegistryResolver.js.map +1 -1
- package/dist/resolvers/CreateQueryResolver.d.ts +2 -2
- package/dist/resolvers/CreateQueryResolver.d.ts.map +1 -1
- package/dist/resolvers/CreateQueryResolver.js +12 -12
- package/dist/resolvers/CreateQueryResolver.js.map +1 -1
- package/dist/resolvers/EntityResolver.d.ts +2 -2
- package/dist/resolvers/EntityResolver.d.ts.map +1 -1
- package/dist/resolvers/EntityResolver.js +4 -4
- package/dist/resolvers/EntityResolver.js.map +1 -1
- package/dist/resolvers/FileCategoryResolver.d.ts +1 -1
- package/dist/resolvers/FileCategoryResolver.d.ts.map +1 -1
- package/dist/resolvers/FileCategoryResolver.js +2 -2
- package/dist/resolvers/FileCategoryResolver.js.map +1 -1
- package/dist/resolvers/FileResolver.d.ts +6 -6
- package/dist/resolvers/FileResolver.d.ts.map +1 -1
- package/dist/resolvers/FileResolver.js +14 -14
- package/dist/resolvers/FileResolver.js.map +1 -1
- package/dist/resolvers/PotentialDuplicateRecordResolver.d.ts.map +1 -1
- package/dist/resolvers/PotentialDuplicateRecordResolver.js +0 -2
- package/dist/resolvers/PotentialDuplicateRecordResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts +3 -3
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +28 -21
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTemplateResolver.js.map +1 -1
- package/dist/resolvers/TaskResolver.d.ts +18 -0
- package/dist/resolvers/TaskResolver.d.ts.map +1 -0
- package/dist/resolvers/TaskResolver.js +138 -0
- package/dist/resolvers/TaskResolver.js.map +1 -0
- package/dist/resolvers/UserFavoriteResolver.d.ts +2 -2
- package/dist/resolvers/UserFavoriteResolver.d.ts.map +1 -1
- package/dist/resolvers/UserFavoriteResolver.js +5 -5
- package/dist/resolvers/UserFavoriteResolver.js.map +1 -1
- package/dist/resolvers/UserResolver.d.ts +2 -2
- package/dist/resolvers/UserResolver.d.ts.map +1 -1
- package/dist/resolvers/UserResolver.js +7 -7
- package/dist/resolvers/UserResolver.js.map +1 -1
- package/dist/resolvers/UserViewResolver.d.ts +2 -2
- package/dist/resolvers/UserViewResolver.d.ts.map +1 -1
- package/dist/resolvers/UserViewResolver.js +8 -8
- package/dist/resolvers/UserViewResolver.js.map +1 -1
- package/dist/services/TaskOrchestrator.d.ts +52 -0
- package/dist/services/TaskOrchestrator.d.ts.map +1 -0
- package/dist/services/TaskOrchestrator.js +486 -0
- package/dist/services/TaskOrchestrator.js.map +1 -0
- package/package.json +30 -38
- package/src/agents/skip-agent.ts +1433 -0
- package/src/agents/skip-sdk.ts +541 -0
- package/src/config.ts +3 -2
- package/src/generated/generated.ts +7948 -6811
- package/src/index.ts +7 -21
- package/src/resolvers/AskSkipResolver.ts +32 -10
- package/src/resolvers/ComponentRegistryResolver.ts +133 -4
- package/src/resolvers/CreateQueryResolver.ts +6 -6
- package/src/resolvers/EntityResolver.ts +4 -4
- package/src/resolvers/FileCategoryResolver.ts +2 -2
- package/src/resolvers/FileResolver.ts +12 -12
- package/src/resolvers/PotentialDuplicateRecordResolver.ts +2 -3
- package/src/resolvers/RunAIAgentResolver.ts +23 -10
- package/src/resolvers/RunTemplateResolver.ts +1 -2
- package/src/resolvers/TaskResolver.ts +142 -0
- package/src/resolvers/UserFavoriteResolver.ts +5 -5
- package/src/resolvers/UserResolver.ts +7 -7
- package/src/resolvers/UserViewResolver.ts +8 -8
- package/src/services/TaskOrchestration-Integration.md +188 -0
- package/src/services/TaskOrchestrator.ts +756 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { Resolver, Mutation, Arg, Ctx, ObjectType, Field, PubSub, PubSubEngine } from 'type-graphql';
|
|
2
|
+
import { AppContext } from '../types.js';
|
|
3
|
+
import { LogError, LogStatus } from '@memberjunction/core';
|
|
4
|
+
import { ResolverBase } from '../generic/ResolverBase.js';
|
|
5
|
+
import { TaskOrchestrator, TaskGraphResponse, TaskExecutionResult } from '../services/TaskOrchestrator.js';
|
|
6
|
+
|
|
7
|
+
@ObjectType()
|
|
8
|
+
export class TaskExecutionResultType {
|
|
9
|
+
@Field()
|
|
10
|
+
taskId: string;
|
|
11
|
+
|
|
12
|
+
@Field()
|
|
13
|
+
success: boolean;
|
|
14
|
+
|
|
15
|
+
@Field({ nullable: true })
|
|
16
|
+
output?: string;
|
|
17
|
+
|
|
18
|
+
@Field({ nullable: true })
|
|
19
|
+
error?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@ObjectType()
|
|
23
|
+
export class ExecuteTaskGraphResult {
|
|
24
|
+
@Field()
|
|
25
|
+
success: boolean;
|
|
26
|
+
|
|
27
|
+
@Field({ nullable: true })
|
|
28
|
+
errorMessage?: string;
|
|
29
|
+
|
|
30
|
+
@Field(() => [TaskExecutionResultType])
|
|
31
|
+
results: TaskExecutionResultType[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* TaskOrchestrationResolver handles multi-step task orchestration.
|
|
36
|
+
* This resolver is called when the Conversation Manager returns a task graph
|
|
37
|
+
* for complex workflows that require multiple agents working in sequence or parallel.
|
|
38
|
+
*/
|
|
39
|
+
@Resolver()
|
|
40
|
+
export class TaskOrchestrationResolver extends ResolverBase {
|
|
41
|
+
/**
|
|
42
|
+
* Execute a task graph from the Conversation Manager.
|
|
43
|
+
* This creates tasks in the database, manages dependencies, and executes them in proper order.
|
|
44
|
+
*
|
|
45
|
+
* @param taskGraphJson - JSON string containing the task graph from Conversation Manager
|
|
46
|
+
* @param conversationDetailId - ID of the conversation detail that triggered this workflow
|
|
47
|
+
* @param environmentId - Environment ID for the tasks
|
|
48
|
+
*/
|
|
49
|
+
@Mutation(() => ExecuteTaskGraphResult)
|
|
50
|
+
async ExecuteTaskGraph(
|
|
51
|
+
@Arg('taskGraphJson') taskGraphJson: string,
|
|
52
|
+
@Arg('conversationDetailId') conversationDetailId: string,
|
|
53
|
+
@Arg('environmentId') environmentId: string,
|
|
54
|
+
@Arg('sessionId') sessionId: string,
|
|
55
|
+
@PubSub() pubSub: PubSubEngine,
|
|
56
|
+
@Ctx() { userPayload }: AppContext
|
|
57
|
+
): Promise<ExecuteTaskGraphResult> {
|
|
58
|
+
try {
|
|
59
|
+
LogStatus(`=== EXECUTING TASK GRAPH FOR CONVERSATION: ${conversationDetailId} ===`);
|
|
60
|
+
|
|
61
|
+
// Parse task graph
|
|
62
|
+
const taskGraph: TaskGraphResponse = JSON.parse(taskGraphJson);
|
|
63
|
+
|
|
64
|
+
// Validate task graph
|
|
65
|
+
if (!taskGraph.workflowName || !taskGraph.tasks || taskGraph.tasks.length === 0) {
|
|
66
|
+
throw new Error('Invalid task graph: must have workflowName and at least one task');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Get current user
|
|
70
|
+
const currentUser = this.GetUserFromPayload(userPayload);
|
|
71
|
+
if (!currentUser) {
|
|
72
|
+
throw new Error('Unable to determine current user');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
LogStatus(`Workflow: ${taskGraph.workflowName} (${taskGraph.tasks.length} tasks)`);
|
|
76
|
+
if (taskGraph.reasoning) {
|
|
77
|
+
LogStatus(`Reasoning: ${taskGraph.reasoning}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Create task orchestrator with PubSub for progress updates
|
|
81
|
+
const orchestrator = new TaskOrchestrator(currentUser, pubSub, sessionId, userPayload);
|
|
82
|
+
|
|
83
|
+
// Create parent task and child tasks with dependencies
|
|
84
|
+
const { parentTaskId, taskIdMap } = await orchestrator.createTasksFromGraph(
|
|
85
|
+
taskGraph,
|
|
86
|
+
conversationDetailId,
|
|
87
|
+
environmentId
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
LogStatus(`Created parent task ${parentTaskId} with ${taskIdMap.size} child tasks`);
|
|
91
|
+
|
|
92
|
+
// Execute tasks in proper order
|
|
93
|
+
const results = await orchestrator.executeTasksForParent(
|
|
94
|
+
parentTaskId
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
// Log results
|
|
98
|
+
const successCount = results.filter(r => r.success).length;
|
|
99
|
+
LogStatus(`Completed ${successCount} of ${results.length} tasks successfully`);
|
|
100
|
+
|
|
101
|
+
for (const result of results) {
|
|
102
|
+
if (result.success) {
|
|
103
|
+
LogStatus(`✅ Task ${result.taskId} completed successfully`);
|
|
104
|
+
} else {
|
|
105
|
+
LogError(`❌ Task ${result.taskId} failed: ${result.error}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Convert results to GraphQL types
|
|
110
|
+
const graphqlResults: TaskExecutionResultType[] = results.map(r => ({
|
|
111
|
+
taskId: r.taskId,
|
|
112
|
+
success: r.success,
|
|
113
|
+
output: r.output ? JSON.stringify(r.output) : undefined,
|
|
114
|
+
error: r.error
|
|
115
|
+
}));
|
|
116
|
+
|
|
117
|
+
LogStatus(`=== TASK GRAPH EXECUTION COMPLETE ===`);
|
|
118
|
+
|
|
119
|
+
const result = {
|
|
120
|
+
success: true,
|
|
121
|
+
results: graphqlResults
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
LogStatus(`Returning ExecuteTaskGraph result: ${JSON.stringify({
|
|
125
|
+
success: result.success,
|
|
126
|
+
resultsCount: result.results.length,
|
|
127
|
+
firstResult: result.results[0]
|
|
128
|
+
})}`);
|
|
129
|
+
|
|
130
|
+
return result;
|
|
131
|
+
|
|
132
|
+
} catch (error) {
|
|
133
|
+
LogError(`Task graph execution failed:`, undefined, error);
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
success: false,
|
|
137
|
+
errorMessage: (error as Error).message || 'Unknown error occurred',
|
|
138
|
+
results: []
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
} from '@memberjunction/server';
|
|
16
16
|
import { UserCache } from '@memberjunction/sqlserver-dataprovider';
|
|
17
17
|
|
|
18
|
-
import {
|
|
18
|
+
import { MJUserFavorite_, MJUserFavoriteResolverBase } from '../generated/generated.js';
|
|
19
19
|
import { GetReadOnlyProvider } from '../util.js';
|
|
20
20
|
|
|
21
21
|
//****************************************************************************
|
|
@@ -66,15 +66,15 @@ export class UserFavoriteResult {
|
|
|
66
66
|
Success: boolean;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
@Resolver(
|
|
70
|
-
export class UserFavoriteResolver extends
|
|
71
|
-
@Query(() => [
|
|
69
|
+
@Resolver(MJUserFavorite_)
|
|
70
|
+
export class UserFavoriteResolver extends MJUserFavoriteResolverBase {
|
|
71
|
+
@Query(() => [MJUserFavorite_])
|
|
72
72
|
async UserFavoritesByUserID(@Arg('UserID', () => Int) UserID: number, @Ctx() { providers, userPayload }: AppContext) {
|
|
73
73
|
const provider = GetReadOnlyProvider(providers, {allowFallbackToReadWrite: true})
|
|
74
74
|
return await this.findBy(provider, 'User Favorites', { UserID }, userPayload.userRecord);
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
@Query(() => [
|
|
77
|
+
@Query(() => [MJUserFavorite_])
|
|
78
78
|
async UserFavoriteSearchByParams(@Arg('params', () => Int) params: UserFavoriteSearchParams, @Ctx() { providers, userPayload }: AppContext) {
|
|
79
79
|
const provider = GetReadOnlyProvider(providers, {allowFallbackToReadWrite: true})
|
|
80
80
|
return await this.findBy(provider, 'User Favorites', params, userPayload.userRecord);
|
|
@@ -1,31 +1,31 @@
|
|
|
1
1
|
import { AppContext, Arg, Ctx, Int, Query, Resolver } from '@memberjunction/server';
|
|
2
|
-
import {
|
|
2
|
+
import { MJUser_, MJUserResolverBase } from '../generated/generated.js';
|
|
3
3
|
import { GetReadOnlyProvider } from '../util.js';
|
|
4
4
|
|
|
5
|
-
@Resolver(
|
|
6
|
-
export class UserResolver extends
|
|
7
|
-
@Query(() =>
|
|
5
|
+
@Resolver(MJUser_)
|
|
6
|
+
export class UserResolver extends MJUserResolverBase {
|
|
7
|
+
@Query(() => MJUser_)
|
|
8
8
|
async CurrentUser(@Ctx() context: AppContext) {
|
|
9
9
|
const result = await this.UserByEmail(context.userPayload.email, context);
|
|
10
10
|
console.log('CurrentUser', result);
|
|
11
11
|
return result;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
@Query(() =>
|
|
14
|
+
@Query(() => MJUser_)
|
|
15
15
|
async UserByID(@Arg('ID', () => Int) ID: number, @Ctx() { providers, userPayload }: AppContext) {
|
|
16
16
|
const provider = GetReadOnlyProvider(providers, {allowFallbackToReadWrite: true})
|
|
17
17
|
const retVal = super.safeFirstArrayElement(await this.findBy(provider, 'Users', { ID }, userPayload.userRecord));
|
|
18
18
|
return this.MapFieldNamesToCodeNames('Users', retVal);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
@Query(() =>
|
|
21
|
+
@Query(() => MJUser_)
|
|
22
22
|
async UserByEmployeeID(@Arg('EmployeeID', () => Int) EmployeeID: number, @Ctx() { providers, userPayload }: AppContext) {
|
|
23
23
|
const provider = GetReadOnlyProvider(providers, {allowFallbackToReadWrite: true})
|
|
24
24
|
const retVal = super.safeFirstArrayElement(await this.findBy(provider, 'Users', { EmployeeID }, userPayload.userRecord));
|
|
25
25
|
return this.MapFieldNamesToCodeNames('Users', retVal);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
@Query(() =>
|
|
28
|
+
@Query(() => MJUser_)
|
|
29
29
|
async UserByEmail(@Arg('Email', () => String) Email: string, @Ctx() { providers, userPayload }: AppContext) {
|
|
30
30
|
// const searchEmail = userEmailMap[Email] ?? Email;
|
|
31
31
|
const searchEmail = Email;
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
2
|
import { EntitySaveOptions, Metadata } from '@memberjunction/core';
|
|
3
3
|
import { AppContext, Arg, Ctx, Int, Query, Resolver, UserPayload } from '@memberjunction/server';
|
|
4
|
-
import {
|
|
4
|
+
import { MJUserView_, MJUserViewResolverBase } from '../generated/generated.js';
|
|
5
5
|
import { UserResolver } from './UserResolver.js';
|
|
6
6
|
import { UserViewEntity, UserViewEntityExtended } from '@memberjunction/core-entities';
|
|
7
7
|
import { GetReadOnlyProvider } from '../util.js';
|
|
8
8
|
|
|
9
|
-
@Resolver(
|
|
10
|
-
export class UserViewResolver extends
|
|
11
|
-
@Query(() => [
|
|
9
|
+
@Resolver(MJUserView_)
|
|
10
|
+
export class UserViewResolver extends MJUserViewResolverBase {
|
|
11
|
+
@Query(() => [MJUserView_])
|
|
12
12
|
async UserViewsByUserID(@Arg('UserID', () => Int) UserID: number, @Ctx() { providers, userPayload }: AppContext) {
|
|
13
13
|
const provider = GetReadOnlyProvider(providers, {allowFallbackToReadWrite: true})
|
|
14
14
|
return await this.findBy(provider, 'User Views', { UserID }, userPayload.userRecord);
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
@Query(() => [
|
|
17
|
+
@Query(() => [MJUserView_])
|
|
18
18
|
async DefaultViewByUserAndEntity(
|
|
19
19
|
@Arg('UserID', () => Int) UserID: number,
|
|
20
20
|
@Arg('EntityID', () => Int) EntityID: number,
|
|
@@ -24,7 +24,7 @@ export class UserViewResolver extends UserViewResolverBase {
|
|
|
24
24
|
return await this.findBy(provider, 'User Views', { UserID, EntityID, IsDefault: true }, userPayload.userRecord);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
@Query(() => [
|
|
27
|
+
@Query(() => [MJUserView_])
|
|
28
28
|
async CurrentUserDefaultViewByEntityID(@Arg('EntityID', () => Int) EntityID: number, @Ctx() context: AppContext) {
|
|
29
29
|
const provider = GetReadOnlyProvider(context.providers, {allowFallbackToReadWrite: true})
|
|
30
30
|
return await this.findBy(provider, 'User Views', {
|
|
@@ -40,13 +40,13 @@ export class UserViewResolver extends UserViewResolverBase {
|
|
|
40
40
|
return user.ID;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
@Query(() => [
|
|
43
|
+
@Query(() => [MJUserView_])
|
|
44
44
|
async CurrentUserUserViewsByEntityID(@Arg('EntityID', () => Int) EntityID: number, @Ctx() context: AppContext) {
|
|
45
45
|
const provider = GetReadOnlyProvider(context.providers, {allowFallbackToReadWrite: true})
|
|
46
46
|
return this.findBy(provider, 'User Views', { UserID: await this.getCurrentUserID(context), EntityID}, context.userPayload.userRecord);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
@Query(() => [
|
|
49
|
+
@Query(() => [MJUserView_])
|
|
50
50
|
async UpdateWhereClause(@Arg('ID', () => String) ID: string, @Ctx() { userPayload, providers }: AppContext) {
|
|
51
51
|
// in this query we want to update the uesrView record in the DB with a new where clause
|
|
52
52
|
// this should normally not be a factor but we have this exposed in the GraphQL API so that
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# Task Orchestration Integration Guide
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
This document explains how to integrate the TaskOrchestrator into the Conversation Manager agent flow.
|
|
5
|
+
|
|
6
|
+
## Components Created
|
|
7
|
+
|
|
8
|
+
1. **TaskOrchestrator** (`/services/TaskOrchestrator.ts`)
|
|
9
|
+
- Creates tasks from LLM task graphs
|
|
10
|
+
- Manages task dependencies
|
|
11
|
+
- Executes tasks in proper order
|
|
12
|
+
- Passes outputs between tasks
|
|
13
|
+
|
|
14
|
+
2. **Updated Conversation Manager Prompt** (`/metadata/prompts/templates/conversations/conversation-manager-agent.template.md`)
|
|
15
|
+
- Now supports returning task graphs
|
|
16
|
+
- Backward compatible with simple agent delegation
|
|
17
|
+
|
|
18
|
+
## Integration Points
|
|
19
|
+
|
|
20
|
+
### 1. Where to Add Task Orchestration Logic
|
|
21
|
+
|
|
22
|
+
When the Conversation Manager agent returns a response, check the `payloadChangeRequest.newElements` for either:
|
|
23
|
+
|
|
24
|
+
**Simple delegation (existing):**
|
|
25
|
+
```typescript
|
|
26
|
+
if (newElements.invokeAgent) {
|
|
27
|
+
// Existing flow - delegate to single agent
|
|
28
|
+
const agentName = newElements.invokeAgent;
|
|
29
|
+
// ... invoke agent ...
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Multi-step orchestration (new):**
|
|
34
|
+
```typescript
|
|
35
|
+
if (newElements.taskGraph) {
|
|
36
|
+
const taskGraph = newElements.taskGraph;
|
|
37
|
+
|
|
38
|
+
if (taskGraph.isMultiStep) {
|
|
39
|
+
// Create tasks in database
|
|
40
|
+
const orchestrator = new TaskOrchestrator(contextUser);
|
|
41
|
+
const taskIdMap = await orchestrator.createTasksFromGraph(
|
|
42
|
+
taskGraph,
|
|
43
|
+
conversationDetailId,
|
|
44
|
+
environmentId
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// Execute tasks (respecting dependencies)
|
|
48
|
+
const results = await orchestrator.executeTasksForConversation(
|
|
49
|
+
conversationDetailId
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
// Send results back to conversation
|
|
53
|
+
// ... format and return results ...
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 2. Suggested Integration Location
|
|
59
|
+
|
|
60
|
+
**Option A: In RunAIAgent Resolver**
|
|
61
|
+
- File: `/packages/MJServer/src/resolvers/RunAIAgentResolver.ts`
|
|
62
|
+
- After agent execution, check the result payload for taskGraph
|
|
63
|
+
- Create and execute tasks if multi-step
|
|
64
|
+
|
|
65
|
+
**Option B: In AskSkip Resolver**
|
|
66
|
+
- File: `/packages/MJServer/src/resolvers/AskSkipResolver.ts`
|
|
67
|
+
- When processing conversation manager responses
|
|
68
|
+
- Check for taskGraph in the response payload
|
|
69
|
+
|
|
70
|
+
**Option C: New Task Resolver (Recommended)**
|
|
71
|
+
- Create `/packages/MJServer/src/resolvers/TaskResolver.ts`
|
|
72
|
+
- Add mutation: `ExecuteTaskGraph(taskGraph, conversationDetailId, environmentId)`
|
|
73
|
+
- Keep task orchestration logic separate and reusable
|
|
74
|
+
|
|
75
|
+
### 3. Example Integration Code
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
import { TaskOrchestrator, TaskGraphResponse } from '../services/TaskOrchestrator.js';
|
|
79
|
+
|
|
80
|
+
// In your resolver after Conversation Manager responds
|
|
81
|
+
async handleConversationManagerResponse(
|
|
82
|
+
agentResult: ExecuteAgentResult,
|
|
83
|
+
conversationDetailId: string,
|
|
84
|
+
environmentId: string,
|
|
85
|
+
contextUser: UserInfo
|
|
86
|
+
): Promise<void> {
|
|
87
|
+
// Check if response contains a task graph
|
|
88
|
+
if (agentResult.payload?.taskGraph) {
|
|
89
|
+
const taskGraph: TaskGraphResponse = agentResult.payload.taskGraph;
|
|
90
|
+
|
|
91
|
+
if (taskGraph.isMultiStep) {
|
|
92
|
+
LogStatus(`Multi-step workflow detected: ${taskGraph.tasks.length} tasks`);
|
|
93
|
+
|
|
94
|
+
// Create task orchestrator
|
|
95
|
+
const orchestrator = new TaskOrchestrator(contextUser);
|
|
96
|
+
|
|
97
|
+
// Create tasks and dependencies
|
|
98
|
+
const taskIdMap = await orchestrator.createTasksFromGraph(
|
|
99
|
+
taskGraph,
|
|
100
|
+
conversationDetailId,
|
|
101
|
+
environmentId
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
LogStatus(`Created ${taskIdMap.size} tasks with dependencies`);
|
|
105
|
+
|
|
106
|
+
// Execute tasks in proper order
|
|
107
|
+
const results = await orchestrator.executeTasksForConversation(
|
|
108
|
+
conversationDetailId
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
// Log results
|
|
112
|
+
for (const result of results) {
|
|
113
|
+
if (result.success) {
|
|
114
|
+
LogStatus(`Task ${result.taskId} completed successfully`);
|
|
115
|
+
} else {
|
|
116
|
+
LogError(`Task ${result.taskId} failed: ${result.error}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Return task completion summary to user
|
|
121
|
+
return {
|
|
122
|
+
success: true,
|
|
123
|
+
message: `Completed ${results.filter(r => r.success).length} of ${results.length} tasks`,
|
|
124
|
+
results: results
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Simple agent delegation (existing flow)
|
|
130
|
+
if (agentResult.payload?.invokeAgent) {
|
|
131
|
+
// ... existing single-agent delegation logic ...
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### 4. UI Integration
|
|
137
|
+
|
|
138
|
+
The existing task UI components already work with TaskEntity:
|
|
139
|
+
- `/packages/Angular/Generic/conversations/src/lib/components/task/task-list.component.ts`
|
|
140
|
+
- Tasks will automatically appear in the task list
|
|
141
|
+
- Users can see progress, dependencies, and results
|
|
142
|
+
|
|
143
|
+
### 5. Testing
|
|
144
|
+
|
|
145
|
+
**Test Single-Step (Backward Compatibility):**
|
|
146
|
+
```
|
|
147
|
+
User: "Analyze our sales data"
|
|
148
|
+
Expected: Conversation Manager delegates to Analysis Agent (existing behavior)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**Test Multi-Step:**
|
|
152
|
+
```
|
|
153
|
+
User: "Research associations with 5-30M revenue in USA, then create a GTM report"
|
|
154
|
+
Expected:
|
|
155
|
+
- Conversation Manager returns taskGraph
|
|
156
|
+
- Task 1 (Research) executes
|
|
157
|
+
- Task 2 (GTM Report) waits for Task 1
|
|
158
|
+
- Task 2 receives Task 1 output
|
|
159
|
+
- Both complete successfully
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Database Schema
|
|
163
|
+
|
|
164
|
+
No changes needed! TaskEntity and TaskDependencyEntity already exist with all required fields.
|
|
165
|
+
|
|
166
|
+
**Note:** For production, consider adding these optional columns to Task table:
|
|
167
|
+
- `InputPayload` NVARCHAR(MAX) - Better than embedding in Description
|
|
168
|
+
- `OutputPayload` NVARCHAR(MAX) - Better than embedding in Description
|
|
169
|
+
|
|
170
|
+
Current implementation uses Description field with `__TASK_METADATA__` and `__TASK_OUTPUT__` markers as a workaround.
|
|
171
|
+
|
|
172
|
+
## Future Enhancements
|
|
173
|
+
|
|
174
|
+
1. **Parallel Execution**: Modify executeTasksForConversation() to run independent tasks concurrently
|
|
175
|
+
2. **Progress Streaming**: Use WebSockets to stream task progress to UI
|
|
176
|
+
3. **Task Templates**: Save common workflows as reusable templates
|
|
177
|
+
4. **Error Recovery**: Add retry logic and error handling strategies
|
|
178
|
+
5. **Task Cancellation**: Allow users to cancel in-progress task chains
|
|
179
|
+
6. **Visualization**: Show task dependency graph in UI
|
|
180
|
+
|
|
181
|
+
## Questions?
|
|
182
|
+
|
|
183
|
+
- TaskOrchestrator handles all task lifecycle management
|
|
184
|
+
- Conversation Manager LLM determines when to use multi-step
|
|
185
|
+
- Existing UI components display tasks automatically
|
|
186
|
+
- Fully backward compatible with simple delegation
|
|
187
|
+
|
|
188
|
+
Integration should take ~1-2 hours for experienced developer familiar with the codebase.
|