feat: add Ops AI Workspace npm setup command

This commit is contained in:
DCCONSTRUCTIONS
2026-06-24 20:40:12 +03:00
parent 763b90f3ba
commit 43e5f570e5
7 changed files with 160 additions and 31 deletions
@@ -10,6 +10,7 @@ from plane.app.views import (
AIWorkspaceExecutorEventsEndpoint,
AIWorkspaceExecutorListEndpoint,
AIWorkspaceExecutorSelectEndpoint,
AIWorkspaceExecutorSetupCommandEndpoint,
AIWorkspaceExecutorWindowsAgentEndpoint,
AIWorkspaceSettingsEndpoint,
AIWorkspaceThreadDispatchEndpoint,
@@ -55,6 +56,11 @@ urlpatterns = [
AIWorkspaceExecutorWindowsAgentEndpoint.as_view(),
name="ai-workspace-executor-windows-agent",
),
path(
"workspaces/<str:slug>/ai-workspace/executors/<uuid:executor_id>/agent/setup-command/",
AIWorkspaceExecutorSetupCommandEndpoint.as_view(),
name="ai-workspace-executor-setup-command",
),
path(
"workspaces/<str:slug>/ai-workspace/threads/",
AIWorkspaceThreadListEndpoint.as_view(),
@@ -180,6 +180,7 @@ from .ai_workspace import (
AIWorkspaceExecutorEventsEndpoint,
AIWorkspaceExecutorListEndpoint,
AIWorkspaceExecutorSelectEndpoint,
AIWorkspaceExecutorSetupCommandEndpoint,
AIWorkspaceExecutorWindowsAgentEndpoint,
AIWorkspaceSettingsEndpoint,
AIWorkspaceThreadDispatchEndpoint,
@@ -505,6 +505,51 @@ def ops_settings_payload(raw_payload, slug):
return payload
def sync_ops_installer_context(request, slug, source):
request_data = request.data if isinstance(request.data, dict) else {}
ops_project_id = str(
request_data.get("opsProjectId")
or request_data.get("ops_project_id")
or request.query_params.get("ops_project_id")
or ""
).strip()
if not ops_project_id:
return None
ops_workspace_slug = str(
request_data.get("opsWorkspaceSlug")
or request_data.get("ops_workspace_slug")
or request.query_params.get("ops_workspace_slug")
or slug
or ""
).strip()
active_context = {
"surface": "ops",
"workspaceSlug": ops_workspace_slug,
"opsWorkspaceSlug": ops_workspace_slug,
"opsRouteWorkspaceSlug": slug,
"opsProjectId": ops_project_id,
}
sync_payload = ops_settings_payload(
{
"activeContext": active_context,
"enabledToolPacks": ["ops", "engine"],
"metadata": {
"source": source,
"updatedAt": timezone.now().isoformat(),
},
},
slug,
)
sync_payload, ops_grant_error = attach_ops_grant_for_active_context(request, slug, sync_payload)
if ops_grant_error is not None:
return ops_grant_error
sync_response = ai_workspace_request(request, "PATCH", "/settings", sync_payload)
if sync_response.status_code >= 400:
return sync_response
return None
class AIWorkspaceSettingsEndpoint(BaseAPIView):
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE")
def get(self, request, slug):
@@ -584,33 +629,9 @@ class AIWorkspaceExecutorWindowsAgentEndpoint(BaseAPIView):
params = {}
if request.query_params.get("port"):
params["port"] = request.query_params.get("port")
ops_project_id = (request.query_params.get("ops_project_id") or "").strip()
if ops_project_id:
ops_workspace_slug = (request.query_params.get("ops_workspace_slug") or slug or "").strip()
active_context = {
"surface": "ops",
"workspaceSlug": ops_workspace_slug,
"opsWorkspaceSlug": ops_workspace_slug,
"opsRouteWorkspaceSlug": slug,
"opsProjectId": ops_project_id,
}
sync_payload = ops_settings_payload(
{
"activeContext": active_context,
"enabledToolPacks": ["ops", "engine"],
"metadata": {
"source": "ops-ai-workspace-installer",
"updatedAt": timezone.now().isoformat(),
},
},
slug,
)
sync_payload, ops_grant_error = attach_ops_grant_for_active_context(request, slug, sync_payload)
if ops_grant_error is not None:
return ops_grant_error
sync_response = ai_workspace_request(request, "PATCH", "/settings", sync_payload)
if sync_response.status_code >= 400:
return sync_response
sync_error = sync_ops_installer_context(request, slug, "ops-ai-workspace-installer")
if sync_error is not None:
return sync_error
try:
response = requests.get(
@@ -643,6 +664,27 @@ class AIWorkspaceExecutorWindowsAgentEndpoint(BaseAPIView):
return output
class AIWorkspaceExecutorSetupCommandEndpoint(BaseAPIView):
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE")
def post(self, request, slug, executor_id):
sync_error = sync_ops_installer_context(request, slug, "ops-ai-workspace-npm-setup")
if sync_error is not None:
return sync_error
request_data = request.data if isinstance(request.data, dict) else {}
payload = {}
port = request_data.get("port") or request.query_params.get("port")
if port:
payload["port"] = port
return ai_workspace_request(
request,
"POST",
f"/executors/{executor_id}/agent/setup-command",
payload,
timeout_override=15,
)
class AIWorkspaceThreadListEndpoint(BaseAPIView):
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE")
def get(self, request, slug):