fix:merge conflicts clickup branch
This commit is contained in:
commit
c47529ef3b
10 changed files with 230 additions and 39 deletions
|
|
@ -9,6 +9,7 @@ from modules.serviceCenter.services.serviceAgent.datamodelAgent import ToolResul
|
||||||
from modules.serviceCenter.services.serviceAgent.toolRegistry import ToolRegistry
|
from modules.serviceCenter.services.serviceAgent.toolRegistry import ToolRegistry
|
||||||
|
|
||||||
from modules.serviceCenter.services.serviceAgent.coreTools._helpers import (
|
from modules.serviceCenter.services.serviceAgent.coreTools._helpers import (
|
||||||
|
_buildResolverDbFromServices,
|
||||||
_getOrCreateTempFolder,
|
_getOrCreateTempFolder,
|
||||||
_looksLikeBinary,
|
_looksLikeBinary,
|
||||||
_resolveFileScope,
|
_resolveFileScope,
|
||||||
|
|
@ -22,20 +23,6 @@ def _registerConnectionTools(registry: ToolRegistry, services):
|
||||||
"""Auto-extracted from registerCoreTools."""
|
"""Auto-extracted from registerCoreTools."""
|
||||||
# ---- Connection tools (external data sources) ----
|
# ---- Connection tools (external data sources) ----
|
||||||
|
|
||||||
def _buildResolverDb():
|
|
||||||
"""Build a DB adapter that ConnectorResolver can use to load UserConnections.
|
|
||||||
interfaceDbApp has getUserConnectionById; ConnectorResolver expects getUserConnection."""
|
|
||||||
chatService = services.chat
|
|
||||||
appIf = getattr(chatService, "interfaceDbApp", None)
|
|
||||||
if appIf and hasattr(appIf, "getUserConnectionById"):
|
|
||||||
class _Adapter:
|
|
||||||
def __init__(self, app):
|
|
||||||
self._app = app
|
|
||||||
def getUserConnection(self, connectionId: str):
|
|
||||||
return self._app.getUserConnectionById(connectionId)
|
|
||||||
return _Adapter(appIf)
|
|
||||||
return getattr(chatService, "interfaceDbComponent", None)
|
|
||||||
|
|
||||||
async def _listConnections(args: Dict[str, Any], context: Dict[str, Any]):
|
async def _listConnections(args: Dict[str, Any], context: Dict[str, Any]):
|
||||||
try:
|
try:
|
||||||
chatService = services.chat
|
chatService = services.chat
|
||||||
|
|
@ -49,7 +36,12 @@ def _registerConnectionTools(registry: ToolRegistry, services):
|
||||||
authorityVal = authority.value if hasattr(authority, "value") else str(authority)
|
authorityVal = authority.value if hasattr(authority, "value") else str(authority)
|
||||||
username = conn.get("externalUsername", "") if isinstance(conn, dict) else getattr(conn, "externalUsername", "")
|
username = conn.get("externalUsername", "") if isinstance(conn, dict) else getattr(conn, "externalUsername", "")
|
||||||
email = conn.get("externalEmail", "") if isinstance(conn, dict) else getattr(conn, "externalEmail", "")
|
email = conn.get("externalEmail", "") if isinstance(conn, dict) else getattr(conn, "externalEmail", "")
|
||||||
lines.append(f"- connectionId: {connId} | {authorityVal} | {username} ({email})")
|
cid = conn.get("id", "") if isinstance(conn, dict) else getattr(conn, "id", "")
|
||||||
|
ref = f"connection:{authorityVal}:{username}"
|
||||||
|
lines.append(
|
||||||
|
f"- {ref} connectionId={cid} ({email}) "
|
||||||
|
f"(use this full connection: line or connectionId as connectionReference)"
|
||||||
|
)
|
||||||
return ToolResult(toolCallId="", toolName="listConnections", success=True, data="\n".join(lines))
|
return ToolResult(toolCallId="", toolName="listConnections", success=True, data="\n".join(lines))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return ToolResult(toolCallId="", toolName="listConnections", success=False, error=str(e))
|
return ToolResult(toolCallId="", toolName="listConnections", success=False, error=str(e))
|
||||||
|
|
@ -65,7 +57,7 @@ def _registerConnectionTools(registry: ToolRegistry, services):
|
||||||
from modules.connectors.connectorResolver import ConnectorResolver
|
from modules.connectors.connectorResolver import ConnectorResolver
|
||||||
resolver = ConnectorResolver(
|
resolver = ConnectorResolver(
|
||||||
services.getService("security"),
|
services.getService("security"),
|
||||||
_buildResolverDb(),
|
_buildResolverDbFromServices(services),
|
||||||
)
|
)
|
||||||
adapter = await resolver.resolveService(connectionId, service)
|
adapter = await resolver.resolveService(connectionId, service)
|
||||||
chatService = services.chat
|
chatService = services.chat
|
||||||
|
|
@ -115,7 +107,7 @@ def _registerConnectionTools(registry: ToolRegistry, services):
|
||||||
from modules.connectors.connectorResolver import ConnectorResolver
|
from modules.connectors.connectorResolver import ConnectorResolver
|
||||||
resolver = ConnectorResolver(
|
resolver = ConnectorResolver(
|
||||||
services.getService("security"),
|
services.getService("security"),
|
||||||
_buildResolverDb(),
|
_buildResolverDbFromServices(services),
|
||||||
)
|
)
|
||||||
adapter = await resolver.resolveService(connectionId, "outlook")
|
adapter = await resolver.resolveService(connectionId, "outlook")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ from modules.serviceCenter.services.serviceAgent.datamodelAgent import ToolResul
|
||||||
from modules.serviceCenter.services.serviceAgent.toolRegistry import ToolRegistry
|
from modules.serviceCenter.services.serviceAgent.toolRegistry import ToolRegistry
|
||||||
|
|
||||||
from modules.serviceCenter.services.serviceAgent.coreTools._helpers import (
|
from modules.serviceCenter.services.serviceAgent.coreTools._helpers import (
|
||||||
|
_buildResolverDbFromServices,
|
||||||
_getOrCreateTempFolder,
|
_getOrCreateTempFolder,
|
||||||
_looksLikeBinary,
|
_looksLikeBinary,
|
||||||
_resolveFileScope,
|
_resolveFileScope,
|
||||||
|
|
@ -88,7 +89,7 @@ def _registerDataSourceTools(registry: ToolRegistry, services):
|
||||||
from modules.connectors.connectorResolver import ConnectorResolver
|
from modules.connectors.connectorResolver import ConnectorResolver
|
||||||
resolver = ConnectorResolver(
|
resolver = ConnectorResolver(
|
||||||
services.getService("security"),
|
services.getService("security"),
|
||||||
_buildResolverDb(),
|
_buildResolverDbFromServices(services),
|
||||||
)
|
)
|
||||||
adapter = await resolver.resolveService(connectionId, service)
|
adapter = await resolver.resolveService(connectionId, service)
|
||||||
entries = await adapter.browse(browsePath, filter=args.get("filter"))
|
entries = await adapter.browse(browsePath, filter=args.get("filter"))
|
||||||
|
|
@ -124,7 +125,7 @@ def _registerDataSourceTools(registry: ToolRegistry, services):
|
||||||
from modules.connectors.connectorResolver import ConnectorResolver
|
from modules.connectors.connectorResolver import ConnectorResolver
|
||||||
resolver = ConnectorResolver(
|
resolver = ConnectorResolver(
|
||||||
services.getService("security"),
|
services.getService("security"),
|
||||||
_buildResolverDb(),
|
_buildResolverDbFromServices(services),
|
||||||
)
|
)
|
||||||
adapter = await resolver.resolveService(connectionId, service)
|
adapter = await resolver.resolveService(connectionId, service)
|
||||||
entries = await adapter.search(query, path=basePath)
|
entries = await adapter.search(query, path=basePath)
|
||||||
|
|
@ -160,7 +161,7 @@ def _registerDataSourceTools(registry: ToolRegistry, services):
|
||||||
fullPath = filePath if filePath.startswith("/") else f"{basePath.rstrip('/')}/{filePath}"
|
fullPath = filePath if filePath.startswith("/") else f"{basePath.rstrip('/')}/{filePath}"
|
||||||
resolver = ConnectorResolver(
|
resolver = ConnectorResolver(
|
||||||
services.getService("security"),
|
services.getService("security"),
|
||||||
_buildResolverDb(),
|
_buildResolverDbFromServices(services),
|
||||||
)
|
)
|
||||||
adapter = await resolver.resolveService(connectionId, service)
|
adapter = await resolver.resolveService(connectionId, service)
|
||||||
result = await adapter.download(fullPath)
|
result = await adapter.download(fullPath)
|
||||||
|
|
|
||||||
|
|
@ -201,13 +201,9 @@ def _registerFeatureSubAgentTools(registry: ToolRegistry, services):
|
||||||
"queryFeatureInstance", _queryFeatureInstance,
|
"queryFeatureInstance", _queryFeatureInstance,
|
||||||
description=(
|
description=(
|
||||||
"Query data from a feature instance (e.g. Trustee, CommCoach). "
|
"Query data from a feature instance (e.g. Trustee, CommCoach). "
|
||||||
"Delegates to a specialized sub-agent that knows the feature's data schema "
|
"Delegates to a sub-agent that knows the feature schema. "
|
||||||
"and can browse, filter, and aggregate its tables. Use this when the user "
|
"Requires the feature instance id from attached feature data sources. "
|
||||||
"has attached feature data sources or asks about feature-specific data.\n\n"
|
"Ask one precise, self-contained question per call."
|
||||||
"GUIDELINES:\n"
|
|
||||||
"- Ask a precise, self-contained question (include all context the sub-agent needs).\n"
|
|
||||||
"- Combine related data needs into ONE call instead of multiple small ones.\n"
|
|
||||||
"- Avoid calling this tool repeatedly with slight variations of the same question."
|
|
||||||
),
|
),
|
||||||
parameters={
|
parameters={
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
"""Shared helpers for core agent tools (file scope, binary detection, temp folder)."""
|
"""Shared helpers for core agent tools (file scope, binary detection, temp folder)."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
@ -77,3 +77,23 @@ def _getOrCreateTempFolder(chatService) -> Optional[str]:
|
||||||
logger.warning(f"Could not get/create Temp folder: {e}")
|
logger.warning(f"Could not get/create Temp folder: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _buildResolverDbFromServices(services: Any):
|
||||||
|
"""DB adapter for ConnectorResolver: load UserConnections by id.
|
||||||
|
|
||||||
|
interfaceDbApp exposes getUserConnectionById; ConnectorResolver expects getUserConnection.
|
||||||
|
"""
|
||||||
|
chatService = services.chat
|
||||||
|
appIf = getattr(chatService, "interfaceDbApp", None)
|
||||||
|
if appIf and hasattr(appIf, "getUserConnectionById"):
|
||||||
|
|
||||||
|
class _Adapter:
|
||||||
|
def __init__(self, app):
|
||||||
|
self._app = app
|
||||||
|
|
||||||
|
def getUserConnection(self, connectionId: str):
|
||||||
|
return self._app.getUserConnectionById(connectionId)
|
||||||
|
|
||||||
|
return _Adapter(appIf)
|
||||||
|
return getattr(chatService, "interfaceDbComponent", None)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
"""Agent service: entry point for running AI agents with tool use."""
|
"""Agent service: entry point for running AI agents with tool use."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Callable, Dict, List, Optional, AsyncGenerator
|
from typing import Any, Callable, Dict, List, Optional, Set, AsyncGenerator
|
||||||
|
|
||||||
from modules.datamodels.datamodelAi import (
|
from modules.datamodels.datamodelAi import (
|
||||||
AiCallRequest, AiCallOptions, AiCallResponse, OperationTypeEnum
|
AiCallRequest, AiCallOptions, AiCallResponse, OperationTypeEnum
|
||||||
|
|
@ -23,6 +23,40 @@ from modules.serviceCenter.services.serviceBilling.mainServiceBilling import (
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def _toolbox_connection_authorities(services: "_ServicesAdapter") -> List[str]:
|
||||||
|
"""Collect connection authority strings for toolbox gating (requiresConnection).
|
||||||
|
|
||||||
|
The optional ``connection`` service is not always registered; fall back to
|
||||||
|
``chat.getUserConnections()`` (same source as workspace UI).
|
||||||
|
Toolbox entries use ``microsoft`` while UserConnection may store ``msft``.
|
||||||
|
"""
|
||||||
|
seen: Set[str] = set()
|
||||||
|
try:
|
||||||
|
conn_svc = services.getService("connection")
|
||||||
|
if conn_svc and hasattr(conn_svc, "getConnections"):
|
||||||
|
for c in conn_svc.getConnections() or []:
|
||||||
|
auth = c.get("authority") if isinstance(c, dict) else getattr(c, "authority", None)
|
||||||
|
val = auth.value if hasattr(auth, "value") else str(auth or "")
|
||||||
|
if val:
|
||||||
|
seen.add(val)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
chat = services.chat
|
||||||
|
if chat and hasattr(chat, "getUserConnections"):
|
||||||
|
for c in chat.getUserConnections() or []:
|
||||||
|
auth = c.get("authority") if isinstance(c, dict) else getattr(c, "authority", None)
|
||||||
|
val = auth.value if hasattr(auth, "value") else str(auth or "")
|
||||||
|
if val:
|
||||||
|
seen.add(val)
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug("toolbox authorities from chat: %s", e)
|
||||||
|
if "msft" in seen:
|
||||||
|
seen.add("microsoft")
|
||||||
|
return list(seen)
|
||||||
|
|
||||||
|
|
||||||
class _ServicesAdapter:
|
class _ServicesAdapter:
|
||||||
"""Adapter providing service access from (context, get_service)."""
|
"""Adapter providing service access from (context, get_service)."""
|
||||||
|
|
||||||
|
|
@ -61,10 +95,33 @@ class _ServicesAdapter:
|
||||||
def extraction(self):
|
def extraction(self):
|
||||||
return self._getService("extraction")
|
return self._getService("extraction")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def rbac(self):
|
||||||
|
"""Same RbacClass as workflow hub (MethodBase permission checks during discoverMethods)."""
|
||||||
|
try:
|
||||||
|
chat_svc = self.chat
|
||||||
|
app = getattr(chat_svc, "interfaceDbApp", None)
|
||||||
|
if app is not None:
|
||||||
|
return getattr(app, "rbac", None)
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
return None
|
||||||
|
|
||||||
def getService(self, name: str):
|
def getService(self, name: str):
|
||||||
"""Access any service by name."""
|
"""Access any service by name."""
|
||||||
return self._getService(name)
|
return self._getService(name)
|
||||||
|
|
||||||
|
def __getattr__(self, name: str):
|
||||||
|
"""Resolve e.g. services.clickup for MethodClickup / ActionExecutor (discoverMethods)."""
|
||||||
|
if name.startswith("_"):
|
||||||
|
raise AttributeError(name)
|
||||||
|
try:
|
||||||
|
return self._getService(name)
|
||||||
|
except KeyError:
|
||||||
|
raise AttributeError(
|
||||||
|
f"{type(self).__name__!r} object has no attribute {name!r}"
|
||||||
|
) from None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def featureCode(self) -> Optional[str]:
|
def featureCode(self) -> Optional[str]:
|
||||||
w = self.workflow
|
w = self.workflow
|
||||||
|
|
@ -268,7 +325,12 @@ class AgentService:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from modules.workflows.processing.shared.methodDiscovery import discoverMethods
|
from modules.workflows.processing.shared.methodDiscovery import discoverMethods
|
||||||
|
|
||||||
discoverMethods(self.services)
|
discoverMethods(self.services)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning("discoverMethods failed before action tools: %s", e)
|
||||||
|
|
||||||
|
try:
|
||||||
from modules.workflows.processing.core.actionExecutor import ActionExecutor
|
from modules.workflows.processing.core.actionExecutor import ActionExecutor
|
||||||
actionExecutor = ActionExecutor(self.services)
|
actionExecutor = ActionExecutor(self.services)
|
||||||
adapter = ActionToolAdapter(actionExecutor)
|
adapter = ActionToolAdapter(actionExecutor)
|
||||||
|
|
@ -293,7 +355,7 @@ class AgentService:
|
||||||
from modules.serviceCenter.services.serviceAgent.toolboxRegistry import getToolboxRegistry
|
from modules.serviceCenter.services.serviceAgent.toolboxRegistry import getToolboxRegistry
|
||||||
tbRegistry = getToolboxRegistry()
|
tbRegistry = getToolboxRegistry()
|
||||||
|
|
||||||
userConnections: List[str] = []
|
userConnections: List[str] = _toolbox_connection_authorities(self.services)
|
||||||
try:
|
try:
|
||||||
chatService = self.services.chat if hasattr(self.services, "chat") else None
|
chatService = self.services.chat if hasattr(self.services, "chat") else None
|
||||||
if chatService and hasattr(chatService, "getUserConnections"):
|
if chatService and hasattr(chatService, "getUserConnections"):
|
||||||
|
|
@ -301,7 +363,7 @@ class AgentService:
|
||||||
for c in connections:
|
for c in connections:
|
||||||
authority = c.get("authority", "") if isinstance(c, dict) else getattr(c, "authority", "")
|
authority = c.get("authority", "") if isinstance(c, dict) else getattr(c, "authority", "")
|
||||||
authorityVal = authority.value if hasattr(authority, "value") else str(authority)
|
authorityVal = authority.value if hasattr(authority, "value") else str(authority)
|
||||||
if authorityVal:
|
if authorityVal and authorityVal not in userConnections:
|
||||||
userConnections.append(authorityVal)
|
userConnections.append(authorityVal)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug("Could not resolve user connections for toolbox activation: %s", e)
|
logger.debug("Could not resolve user connections for toolbox activation: %s", e)
|
||||||
|
|
@ -377,6 +439,7 @@ class AgentService:
|
||||||
activatedCount += 1
|
activatedCount += 1
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
|
<<<<<<< HEAD
|
||||||
from modules.serviceCenter.services.serviceAgent.coreTools import registerCoreTools
|
from modules.serviceCenter.services.serviceAgent.coreTools import registerCoreTools
|
||||||
registerCoreTools(registry, self.services)
|
registerCoreTools(registry, self.services)
|
||||||
if registry.isValidTool(toolName):
|
if registry.isValidTool(toolName):
|
||||||
|
|
@ -388,6 +451,15 @@ class AgentService:
|
||||||
try:
|
try:
|
||||||
from modules.serviceCenter.services.serviceAgent.actionToolAdapter import ActionToolAdapter
|
from modules.serviceCenter.services.serviceAgent.actionToolAdapter import ActionToolAdapter
|
||||||
from modules.workflows.processing.core.actionExecutor import ActionExecutor
|
from modules.workflows.processing.core.actionExecutor import ActionExecutor
|
||||||
|
=======
|
||||||
|
from modules.workflows.processing.shared.methodDiscovery import discoverMethods
|
||||||
|
from modules.workflows.processing.core.actionExecutor import ActionExecutor
|
||||||
|
from modules.serviceCenter.services.serviceAgent.actionToolAdapter import (
|
||||||
|
ActionToolAdapter,
|
||||||
|
)
|
||||||
|
|
||||||
|
discoverMethods(self.services)
|
||||||
|
>>>>>>> origin/fix/click-up-connector
|
||||||
adapter = ActionToolAdapter(ActionExecutor(self.services))
|
adapter = ActionToolAdapter(ActionExecutor(self.services))
|
||||||
adapter.registerAll(registry)
|
adapter.registerAll(registry)
|
||||||
if registry.isValidTool(toolName):
|
if registry.isValidTool(toolName):
|
||||||
|
|
|
||||||
|
|
@ -173,7 +173,13 @@ def _registerDefaultToolboxes() -> None:
|
||||||
requiresConnection="clickup",
|
requiresConnection="clickup",
|
||||||
isDefault=False,
|
isDefault=False,
|
||||||
tools=[
|
tools=[
|
||||||
"clickup_searchTasks", "clickup_createTask", "clickup_updateTask",
|
"clickup_listTasks",
|
||||||
|
"clickup_listFields",
|
||||||
|
"clickup_searchTasks",
|
||||||
|
"clickup_getTask",
|
||||||
|
"clickup_createTask",
|
||||||
|
"clickup_updateTask",
|
||||||
|
"clickup_uploadAttachment",
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
ToolboxDefinition(
|
ToolboxDefinition(
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,7 @@ NAVIGATION_SECTIONS = [
|
||||||
"icon": "FaLink",
|
"icon": "FaLink",
|
||||||
"path": "/basedata/connections",
|
"path": "/basedata/connections",
|
||||||
"order": 10,
|
"order": 10,
|
||||||
|
"public": True,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "files",
|
"id": "files",
|
||||||
|
|
@ -100,6 +101,7 @@ NAVIGATION_SECTIONS = [
|
||||||
"icon": "FaRegFileAlt",
|
"icon": "FaRegFileAlt",
|
||||||
"path": "/basedata/files",
|
"path": "/basedata/files",
|
||||||
"order": 20,
|
"order": 20,
|
||||||
|
"public": True,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "prompts",
|
"id": "prompts",
|
||||||
|
|
@ -108,6 +110,7 @@ NAVIGATION_SECTIONS = [
|
||||||
"icon": "FaLightbulb",
|
"icon": "FaLightbulb",
|
||||||
"path": "/basedata/prompts",
|
"path": "/basedata/prompts",
|
||||||
"order": 30,
|
"order": 30,
|
||||||
|
"public": True,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
# Copyright (c) 2025 Patrick Motsch
|
||||||
|
# All rights reserved.
|
||||||
|
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from typing import Any, Dict
|
||||||
|
|
||||||
|
from modules.datamodels.datamodelChat import ActionDocument, ActionResult
|
||||||
|
from ..helpers.pathparse import parse_team_and_list
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
async def list_fields(self, parameters: Dict[str, Any]) -> ActionResult:
|
||||||
|
"""Return ClickUp custom / built-in field definitions for a list (GET /list/{id}/field)."""
|
||||||
|
connection_reference = parameters.get("connectionReference")
|
||||||
|
path_query = (parameters.get("pathQuery") or parameters.get("path") or "").strip()
|
||||||
|
list_id_param = (parameters.get("listId") or "").strip()
|
||||||
|
|
||||||
|
if not connection_reference:
|
||||||
|
return ActionResult.isFailure(error="connectionReference is required")
|
||||||
|
|
||||||
|
conn = self.connection.get_clickup_connection(connection_reference)
|
||||||
|
if not conn:
|
||||||
|
return ActionResult.isFailure(error="No valid ClickUp connection")
|
||||||
|
|
||||||
|
list_id = list_id_param
|
||||||
|
team_id = ""
|
||||||
|
if not list_id:
|
||||||
|
if not path_query:
|
||||||
|
return ActionResult.isFailure(
|
||||||
|
error="Provide listId or pathQuery like /team/{teamId}/list/{listId}"
|
||||||
|
)
|
||||||
|
team_id, list_id = parse_team_and_list(path_query)
|
||||||
|
if not list_id:
|
||||||
|
return ActionResult.isFailure(
|
||||||
|
error="path must be /team/{teamId}/list/{listId} (same as list picker / data source path)"
|
||||||
|
)
|
||||||
|
|
||||||
|
data = await self.services.clickup.getListFields(list_id)
|
||||||
|
if isinstance(data, dict) and data.get("error"):
|
||||||
|
return ActionResult.isFailure(error=str(data.get("error")) + (data.get("body") or ""))
|
||||||
|
|
||||||
|
doc = ActionDocument(
|
||||||
|
documentName="clickup_list_fields.json",
|
||||||
|
documentData=json.dumps(data, ensure_ascii=False, indent=2),
|
||||||
|
mimeType="application/json",
|
||||||
|
validationMetadata={
|
||||||
|
"actionType": "clickup.listFields",
|
||||||
|
"teamId": team_id,
|
||||||
|
"listId": list_id,
|
||||||
|
"path": path_query or f"/list/{list_id}",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return ActionResult.isSuccess(documents=[doc])
|
||||||
|
|
@ -3,28 +3,44 @@
|
||||||
"""Resolve ClickUp UserConnection and configure ClickupService."""
|
"""Resolve ClickUp UserConnection and configure ClickupService."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import re
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
_UUID_RE = re.compile(
|
||||||
|
r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
|
||||||
|
re.IGNORECASE,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ClickupConnectionHelper:
|
class ClickupConnectionHelper:
|
||||||
def __init__(self, method_instance: Any):
|
def __init__(self, method_instance: Any):
|
||||||
self.method = method_instance
|
self.method = method_instance
|
||||||
self.services = method_instance.services
|
self.services = method_instance.services
|
||||||
|
|
||||||
|
def _normalize_connection_reference(self, ref: str) -> str:
|
||||||
|
"""Match listConnections / getUserConnectionFromConnectionReference formats."""
|
||||||
|
if ref.startswith("connection:"):
|
||||||
|
return ref
|
||||||
|
if _UUID_RE.match(ref):
|
||||||
|
return ref
|
||||||
|
# LLM often copies "clickup:username" without the connection: prefix
|
||||||
|
if ":" in ref:
|
||||||
|
return f"connection:{ref}"
|
||||||
|
return ref
|
||||||
|
|
||||||
def get_clickup_connection(self, connection_reference: str) -> Optional[Dict[str, Any]]:
|
def get_clickup_connection(self, connection_reference: str) -> Optional[Dict[str, Any]]:
|
||||||
try:
|
try:
|
||||||
ref = (connection_reference or "").split(" [")[0].strip()
|
ref = (connection_reference or "").split(" [")[0].strip()
|
||||||
if not ref:
|
if not ref:
|
||||||
return None
|
return None
|
||||||
user_connection = None
|
ref = self._normalize_connection_reference(ref)
|
||||||
if ref.startswith("connection:"):
|
chat = getattr(self.services, "chat", None)
|
||||||
user_connection = self.services.chat.getUserConnectionFromConnectionReference(ref)
|
if not chat or not hasattr(chat, "getUserConnectionFromConnectionReference"):
|
||||||
else:
|
logger.warning("Chat service missing; cannot resolve ClickUp connection")
|
||||||
app = getattr(self.services, "interfaceDbApp", None)
|
return None
|
||||||
if app and hasattr(app, "getUserConnectionById"):
|
user_connection = chat.getUserConnectionFromConnectionReference(ref)
|
||||||
user_connection = app.getUserConnectionById(ref)
|
|
||||||
if not user_connection:
|
if not user_connection:
|
||||||
logger.warning("No user connection for reference/id %s", connection_reference)
|
logger.warning("No user connection for reference/id %s", connection_reference)
|
||||||
return None
|
return None
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ from modules.workflows.methods.methodBase import MethodBase
|
||||||
|
|
||||||
from .helpers.connection import ClickupConnectionHelper
|
from .helpers.connection import ClickupConnectionHelper
|
||||||
from .actions.list_tasks import list_tasks
|
from .actions.list_tasks import list_tasks
|
||||||
|
from .actions.list_fields import list_fields
|
||||||
from .actions.search_tasks import search_tasks
|
from .actions.search_tasks import search_tasks
|
||||||
from .actions.get_task import get_task
|
from .actions.get_task import get_task
|
||||||
from .actions.create_task import create_task
|
from .actions.create_task import create_task
|
||||||
|
|
@ -67,6 +68,35 @@ class MethodClickup(MethodBase):
|
||||||
},
|
},
|
||||||
execute=list_tasks.__get__(self, self.__class__),
|
execute=list_tasks.__get__(self, self.__class__),
|
||||||
),
|
),
|
||||||
|
"listFields": WorkflowActionDefinition(
|
||||||
|
actionId="clickup.listFields",
|
||||||
|
description="List custom and built-in field definitions for a ClickUp list (names, types, ids)",
|
||||||
|
dynamicMode=True,
|
||||||
|
parameters={
|
||||||
|
"connectionReference": WorkflowActionParameter(
|
||||||
|
name="connectionReference",
|
||||||
|
type="str",
|
||||||
|
frontendType=FrontendType.USER_CONNECTION,
|
||||||
|
required=True,
|
||||||
|
description="ClickUp connection",
|
||||||
|
),
|
||||||
|
"listId": WorkflowActionParameter(
|
||||||
|
name="listId",
|
||||||
|
type="str",
|
||||||
|
frontendType=FrontendType.TEXT,
|
||||||
|
required=False,
|
||||||
|
description="ClickUp list ID (if set, pathQuery is optional)",
|
||||||
|
),
|
||||||
|
"pathQuery": WorkflowActionParameter(
|
||||||
|
name="pathQuery",
|
||||||
|
type="str",
|
||||||
|
frontendType=FrontendType.TEXT,
|
||||||
|
required=False,
|
||||||
|
description="Virtual path /team/{teamId}/list/{listId} (same as data source path)",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
execute=list_fields.__get__(self, self.__class__),
|
||||||
|
),
|
||||||
"searchTasks": WorkflowActionDefinition(
|
"searchTasks": WorkflowActionDefinition(
|
||||||
actionId="clickup.searchTasks",
|
actionId="clickup.searchTasks",
|
||||||
description="Search tasks in a ClickUp workspace (team)",
|
description="Search tasks in a ClickUp workspace (team)",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue