FEAT - TASKER: показ Codex Agent API по Launcher entitlement
This commit is contained in:
parent
2ae353c8d5
commit
97566faba3
|
|
@ -25,6 +25,7 @@ def get_nodedc_workspace_creation_policy(user, workspace_slug=None):
|
|||
"default_managed_by": "tasker",
|
||||
"invite_approval": "tasker",
|
||||
"default_invite_approval": "tasker",
|
||||
"service_modules": {},
|
||||
"workspaces": [],
|
||||
"reason": "NODE.DC workspace policy is not configured.",
|
||||
}
|
||||
|
|
@ -45,6 +46,7 @@ def get_nodedc_workspace_creation_policy(user, workspace_slug=None):
|
|||
"default_managed_by": "tasker",
|
||||
"invite_approval": "tasker",
|
||||
"default_invite_approval": "tasker",
|
||||
"service_modules": {},
|
||||
"workspaces": [],
|
||||
"reason": "NODE.DC identity is not linked." if enforce_unlinked else "Standalone user without NODE.DC identity.",
|
||||
}
|
||||
|
|
@ -75,11 +77,18 @@ def get_nodedc_workspace_creation_policy(user, workspace_slug=None):
|
|||
"default_managed_by": "tasker",
|
||||
"invite_approval": "disabled",
|
||||
"default_invite_approval": "tasker",
|
||||
"service_modules": {},
|
||||
"workspaces": [],
|
||||
"reason": "NODE.DC workspace policy is unavailable.",
|
||||
}
|
||||
|
||||
workspace_policy = payload.get("workspacePolicy") if isinstance(payload.get("workspacePolicy"), dict) else {}
|
||||
service_modules = normalize_service_modules(
|
||||
workspace_policy.get("serviceModules")
|
||||
or workspace_policy.get("service_modules")
|
||||
or payload.get("serviceModules")
|
||||
or payload.get("service_modules")
|
||||
)
|
||||
access_allowed = bool(payload.get("allowed"))
|
||||
if not workspace_policy:
|
||||
return {
|
||||
|
|
@ -90,6 +99,7 @@ def get_nodedc_workspace_creation_policy(user, workspace_slug=None):
|
|||
"default_managed_by": "tasker",
|
||||
"invite_approval": "tasker",
|
||||
"default_invite_approval": "tasker",
|
||||
"service_modules": service_modules,
|
||||
"workspaces": [],
|
||||
"reason": payload.get("reason") or "NODE.DC access check does not expose workspace policy.",
|
||||
}
|
||||
|
|
@ -117,6 +127,7 @@ def get_nodedc_workspace_creation_policy(user, workspace_slug=None):
|
|||
"default_managed_by": normalize_managed_by(workspace_policy.get("defaultManagedBy") or workspace_policy.get("managedBy")),
|
||||
"invite_approval": invite_approval,
|
||||
"default_invite_approval": default_invite_approval,
|
||||
"service_modules": service_modules,
|
||||
"workspaces": workspaces,
|
||||
"reason": workspace_policy.get("reason") or payload.get("reason") or "NODE.DC workspace policy decision.",
|
||||
}
|
||||
|
|
@ -159,6 +170,18 @@ def normalize_workspace_management_list(value):
|
|||
return workspaces
|
||||
|
||||
|
||||
def normalize_service_modules(value):
|
||||
if not isinstance(value, dict):
|
||||
return {}
|
||||
|
||||
service_modules = {}
|
||||
for module_key in ("codex_agents",):
|
||||
if value.get(module_key) is True:
|
||||
service_modules[module_key] = True
|
||||
|
||||
return service_modules
|
||||
|
||||
|
||||
def resolve_workspace_managed_by(workspace_slug, workspaces, fallback):
|
||||
if isinstance(workspace_slug, str) and workspace_slug.strip():
|
||||
normalized_slug = workspace_slug.strip()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
import { redirect } from "react-router";
|
||||
// local imports
|
||||
import type { Route } from "./+types/page";
|
||||
|
||||
export function clientLoader({ params }: Route.ClientLoaderArgs) {
|
||||
const { workspaceSlug } = params;
|
||||
throw redirect(`/${workspaceSlug}/?workspaceSettings=codex-agent-api`);
|
||||
}
|
||||
|
||||
export default function CodexAgentApiSettingsPage() {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -297,6 +297,10 @@ export const coreRoutes: RouteConfigEntry[] = [
|
|||
":workspaceSlug/settings/ai-voice-tasker",
|
||||
"./(all)/[workspaceSlug]/(settings)/settings/(workspace)/ai-voice-tasker/page.tsx"
|
||||
),
|
||||
route(
|
||||
":workspaceSlug/settings/codex-agent-api",
|
||||
"./(all)/[workspaceSlug]/(settings)/settings/(workspace)/codex-agent-api/page.tsx"
|
||||
),
|
||||
]),
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -9,7 +9,12 @@ import { usePathname } from "next/navigation";
|
|||
import { useParams } from "react-router";
|
||||
import useSWR from "swr";
|
||||
// plane imports
|
||||
import { EUserPermissionsLevel, GROUPED_WORKSPACE_SETTINGS, WORKSPACE_SETTINGS, WORKSPACE_SETTINGS_CATEGORIES } from "@plane/constants";
|
||||
import {
|
||||
EUserPermissionsLevel,
|
||||
GROUPED_WORKSPACE_SETTINGS,
|
||||
WORKSPACE_SETTINGS,
|
||||
WORKSPACE_SETTINGS_CATEGORIES,
|
||||
} from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import type { TWorkspaceSettingsTabs } from "@plane/types";
|
||||
import { joinUrlPath } from "@plane/utils";
|
||||
|
|
@ -19,12 +24,14 @@ import { SettingsSidebarItem } from "@/components/settings/sidebar/item";
|
|||
import { useUserPermissions } from "@/hooks/store/user";
|
||||
// services
|
||||
import { WorkspaceAIService } from "@/services/workspace-ai.service";
|
||||
import { WorkspaceService } from "@/services/workspace.service";
|
||||
// local imports
|
||||
import { WORKSPACE_SETTINGS_ICONS } from "./item-icon";
|
||||
|
||||
const HIDDEN_WORKSPACE_SETTINGS_KEYS = new Set(["billing-and-plans"]);
|
||||
const WORKSPACE_FEATURE_GATED_SETTINGS_KEYS = new Set<TWorkspaceSettingsTabs>(["ai-voice-tasker"]);
|
||||
const WORKSPACE_FEATURE_GATED_SETTINGS_KEYS = new Set<TWorkspaceSettingsTabs>(["ai-voice-tasker", "codex-agent-api"]);
|
||||
const workspaceAIService = new WorkspaceAIService();
|
||||
const workspaceService = new WorkspaceService();
|
||||
|
||||
export const WorkspaceSettingsSidebarItemCategories = observer(function WorkspaceSettingsSidebarItemCategories() {
|
||||
// params
|
||||
|
|
@ -42,7 +49,12 @@ export const WorkspaceSettingsSidebarItemCategories = observer(function Workspac
|
|||
canLoadVoiceTaskerEntitlement ? `WORKSPACE_AI_SETTINGS_${workspaceSlug}` : null,
|
||||
() => workspaceAIService.retrieveSettings(workspaceSlug as string)
|
||||
);
|
||||
const { data: nodedcWorkspacePolicy } = useSWR(
|
||||
workspaceSlug ? `NODEDC_WORKSPACE_POLICY_${workspaceSlug}` : null,
|
||||
() => workspaceService.getNodeDCWorkspacePolicy(workspaceSlug as string)
|
||||
);
|
||||
const isVoiceTaskerEntitled = aiSettings?.feature_entitlement_enabled === true;
|
||||
const isCodexAgentEntitled = nodedcWorkspacePolicy?.service_modules?.codex_agents === true;
|
||||
|
||||
return (
|
||||
<div className="mt-4 flex flex-col divide-y divide-white/6">
|
||||
|
|
@ -51,7 +63,11 @@ export const WorkspaceSettingsSidebarItemCategories = observer(function Workspac
|
|||
const accessibleItems = categoryItems.filter(
|
||||
(item) =>
|
||||
!HIDDEN_WORKSPACE_SETTINGS_KEYS.has(item.key) &&
|
||||
(!WORKSPACE_FEATURE_GATED_SETTINGS_KEYS.has(item.key) || isVoiceTaskerEntitled) &&
|
||||
(!WORKSPACE_FEATURE_GATED_SETTINGS_KEYS.has(item.key) ||
|
||||
isWorkspaceFeatureSettingsEntitled(item.key, {
|
||||
isCodexAgentEntitled,
|
||||
isVoiceTaskerEntitled,
|
||||
})) &&
|
||||
allowPermissions(item.access, EUserPermissionsLevel.WORKSPACE, workspaceSlug)
|
||||
);
|
||||
|
||||
|
|
@ -59,7 +75,7 @@ export const WorkspaceSettingsSidebarItemCategories = observer(function Workspac
|
|||
|
||||
return (
|
||||
<div key={category} className="shrink-0 py-3.5 first:pt-0 last:pb-0">
|
||||
<div className="px-3 py-1.5 text-[11px] font-semibold uppercase tracking-[0.18em] text-tertiary">
|
||||
<div className="px-3 py-1.5 text-[11px] font-semibold tracking-[0.18em] text-tertiary uppercase">
|
||||
{t(category)}
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
|
|
@ -87,3 +103,16 @@ export const WorkspaceSettingsSidebarItemCategories = observer(function Workspac
|
|||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
function isWorkspaceFeatureSettingsEntitled(
|
||||
itemKey: TWorkspaceSettingsTabs,
|
||||
entitlements: {
|
||||
isCodexAgentEntitled: boolean;
|
||||
isVoiceTaskerEntitled: boolean;
|
||||
}
|
||||
) {
|
||||
if (itemKey === "ai-voice-tasker") return entitlements.isVoiceTaskerEntitled;
|
||||
if (itemKey === "codex-agent-api") return entitlements.isCodexAgentEntitled;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import { ArrowUpToLine, Building, CreditCard, Database, Mic, Users, Webhook } from "lucide-react";
|
||||
import { ArrowUpToLine, Bot, Building, CreditCard, Database, Mic, Users, Webhook } from "lucide-react";
|
||||
// plane imports
|
||||
import type { ISvgIcons } from "@plane/propel/icons";
|
||||
import type { TWorkspaceSettingsTabs } from "@plane/types";
|
||||
|
|
@ -18,4 +18,5 @@ export const WORKSPACE_SETTINGS_ICONS: Record<TWorkspaceSettingsTabs, LucideIcon
|
|||
storage: Database,
|
||||
webhooks: Webhook,
|
||||
"ai-voice-tasker": Mic,
|
||||
"codex-agent-api": Bot,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,108 @@
|
|||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import { Bot, Check, KeyRound, Route, ShieldCheck } from "lucide-react";
|
||||
import useSWR from "swr";
|
||||
// components
|
||||
import { SettingsHeading } from "@/components/settings/heading";
|
||||
// hooks
|
||||
import { useWorkspace } from "@/hooks/store/use-workspace";
|
||||
// services
|
||||
import { WorkspaceService } from "@/services/workspace.service";
|
||||
|
||||
const workspaceService = new WorkspaceService();
|
||||
|
||||
type TProps = {
|
||||
showHeading?: boolean;
|
||||
workspaceSlug: string;
|
||||
};
|
||||
|
||||
export const CodexAgentApiSettingsContent = observer(function CodexAgentApiSettingsContent(props: TProps) {
|
||||
const { showHeading = true, workspaceSlug } = props;
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { data: nodedcWorkspacePolicy, isLoading } = useSWR(
|
||||
workspaceSlug ? `NODEDC_WORKSPACE_POLICY_${workspaceSlug}` : null,
|
||||
() => workspaceService.getNodeDCWorkspacePolicy(workspaceSlug)
|
||||
);
|
||||
const isCodexAgentEntitled = nodedcWorkspacePolicy?.service_modules?.codex_agents === true;
|
||||
|
||||
return (
|
||||
<div className="flex w-full flex-col gap-7">
|
||||
{showHeading && (
|
||||
<SettingsHeading
|
||||
title="Codex Agent API"
|
||||
description="Workspace-level вход в отдельный NODE.DC Agent Gateway. Внешний Codex получает только ограниченные agent grants, а не Plane session cookies или прямой Tasker API."
|
||||
/>
|
||||
)}
|
||||
|
||||
{isLoading ? (
|
||||
<div className="nodedc-settings-card text-sm px-5 py-5 text-secondary">Загрузка статуса модуля...</div>
|
||||
) : (
|
||||
<>
|
||||
<section className="nodedc-settings-card overflow-hidden">
|
||||
<div className="flex flex-col gap-4 px-5 py-5 md:flex-row md:items-start md:justify-between">
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2 text-16 font-semibold text-primary">
|
||||
<Bot className="size-5 text-tertiary" />
|
||||
<span>Agent Gateway для {currentWorkspace?.name ?? workspaceSlug}</span>
|
||||
</div>
|
||||
<p className="mt-2 max-w-3xl text-13 leading-5 text-secondary">
|
||||
Доступ к модулю приходит из Launcher entitlement Operational Core → Codex Agent API. Если entitlement
|
||||
снят, этот раздел исчезает из настроек workspace и backend policy больше не возвращает активный модуль.
|
||||
</p>
|
||||
</div>
|
||||
<div className="nodedc-external-readonly-value shrink-0">
|
||||
<span className="grid size-5 place-items-center rounded-full bg-[rgb(var(--nodedc-accent-rgb))] text-[rgb(var(--nodedc-on-accent-rgb))]">
|
||||
<Check className="size-3.5" />
|
||||
</span>
|
||||
<span>{isCodexAgentEntitled ? "Доступ выдан" : "Доступ не выдан"}</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="grid gap-4 md:grid-cols-3">
|
||||
<CapabilityCard
|
||||
icon={ShieldCheck}
|
||||
title="Граница прав"
|
||||
description="Агент работает только в workspace/project grants и не получает права на удаление карточек, проектов, участников или состояний."
|
||||
/>
|
||||
<CapabilityCard
|
||||
icon={Route}
|
||||
title="Маршрутизация"
|
||||
description="Все write-действия идут через отдельный Gateway и Tasker internal adapter, с audit trail и idempotency key."
|
||||
/>
|
||||
<CapabilityCard
|
||||
icon={KeyRound}
|
||||
title="Локальный Codex"
|
||||
description="Пользовательский Codex подключается по MCP endpoint с agent token; token хранится только на стороне Gateway."
|
||||
/>
|
||||
</section>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
type TCapabilityCardProps = {
|
||||
description: string;
|
||||
icon: typeof ShieldCheck;
|
||||
title: string;
|
||||
};
|
||||
|
||||
function CapabilityCard(props: TCapabilityCardProps) {
|
||||
const Icon = props.icon;
|
||||
|
||||
return (
|
||||
<div className="nodedc-settings-card px-5 py-5">
|
||||
<div className="flex items-center gap-2 text-14 font-semibold text-primary">
|
||||
<Icon className="size-4 text-tertiary" />
|
||||
<span>{props.title}</span>
|
||||
</div>
|
||||
<p className="mt-3 text-13 leading-5 text-secondary">{props.description}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -25,6 +25,7 @@ import { SettingsSidebarItem } from "@/components/settings/sidebar/item";
|
|||
import { WORKSPACE_SETTINGS_ICONS } from "@/components/settings/workspace/sidebar/item-icon";
|
||||
import { WorkspaceSettingsSidebarHeader } from "@/components/settings/workspace/sidebar/header";
|
||||
import { AIVoiceTaskerSettingsContent } from "@/components/workspace/settings/ai-voice-tasker-settings";
|
||||
import { CodexAgentApiSettingsContent } from "@/components/workspace/settings/codex-agent-api-settings";
|
||||
import { WorkspaceExportsSettingsContent } from "@/components/workspace/settings/exports-settings";
|
||||
import { WorkspaceMembersSettingsContent } from "@/components/workspace/settings/members-settings";
|
||||
import { StorageSettingsContent } from "@/components/workspace/settings/storage-settings";
|
||||
|
|
@ -48,7 +49,7 @@ import {
|
|||
|
||||
const HIDDEN_WORKSPACE_SETTINGS_KEYS = new Set<TWorkspaceSettingsTabs>(["billing-and-plans"]);
|
||||
const LAUNCHER_MANAGED_HIDDEN_WORKSPACE_SETTINGS_KEYS = new Set<TWorkspaceSettingsTabs>(["members"]);
|
||||
const WORKSPACE_FEATURE_GATED_SETTINGS_KEYS = new Set<TWorkspaceSettingsTabs>(["ai-voice-tasker"]);
|
||||
const WORKSPACE_FEATURE_GATED_SETTINGS_KEYS = new Set<TWorkspaceSettingsTabs>(["ai-voice-tasker", "codex-agent-api"]);
|
||||
const MODAL_TABS = new Set<TWorkspaceSettingsTabs>([
|
||||
"general",
|
||||
"members",
|
||||
|
|
@ -56,6 +57,7 @@ const MODAL_TABS = new Set<TWorkspaceSettingsTabs>([
|
|||
"storage",
|
||||
"webhooks",
|
||||
"ai-voice-tasker",
|
||||
"codex-agent-api",
|
||||
]);
|
||||
const workspaceAIService = new WorkspaceAIService();
|
||||
const workspaceService = new WorkspaceService();
|
||||
|
|
@ -99,6 +101,7 @@ export const WorkspaceSettingsModal = observer(function WorkspaceSettingsModal()
|
|||
);
|
||||
const isVoiceTaskerEntitled = aiSettings?.feature_entitlement_enabled === true;
|
||||
const isLauncherManagedWorkspace = nodedcWorkspacePolicy?.managed_by === "launcher";
|
||||
const isCodexAgentEntitled = nodedcWorkspacePolicy?.service_modules?.codex_agents === true;
|
||||
|
||||
useEffect(() => {
|
||||
const syncFromLocation = () => {
|
||||
|
|
@ -136,6 +139,11 @@ export const WorkspaceSettingsModal = observer(function WorkspaceSettingsModal()
|
|||
if (!isVoiceTaskerEntitled) openWorkspaceSettingsModal("general", true);
|
||||
}, [activeTab, isOpen, isVoiceTaskerEntitlementLoading, isVoiceTaskerEntitled]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen || activeTab !== "codex-agent-api" || !nodedcWorkspacePolicy) return;
|
||||
if (!isCodexAgentEntitled) openWorkspaceSettingsModal("general", true);
|
||||
}, [activeTab, isCodexAgentEntitled, isOpen, nodedcWorkspacePolicy]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen || activeTab !== "members" || !isLauncherManagedWorkspace) return;
|
||||
openWorkspaceSettingsModal("general", true);
|
||||
|
|
@ -162,6 +170,11 @@ export const WorkspaceSettingsModal = observer(function WorkspaceSettingsModal()
|
|||
return <AIVoiceTaskerSettingsContent workspaceSlug={currentWorkspace.slug} />;
|
||||
}
|
||||
|
||||
if (activeTab === "codex-agent-api" && currentWorkspace?.slug) {
|
||||
if (!isCodexAgentEntitled) return <WorkspaceDetails />;
|
||||
return <CodexAgentApiSettingsContent workspaceSlug={currentWorkspace.slug} />;
|
||||
}
|
||||
|
||||
if (activeTab === "members" && currentWorkspace?.slug) {
|
||||
return <WorkspaceMembersSettingsContent workspaceSlug={currentWorkspace.slug} />;
|
||||
}
|
||||
|
|
@ -204,6 +217,7 @@ export const WorkspaceSettingsModal = observer(function WorkspaceSettingsModal()
|
|||
allowPermissions={allowPermissions}
|
||||
isVoiceTaskerEntitled={isVoiceTaskerEntitled}
|
||||
isLauncherManagedWorkspace={isLauncherManagedWorkspace}
|
||||
isCodexAgentEntitled={isCodexAgentEntitled}
|
||||
workspaceSlug={currentWorkspace?.slug}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -238,6 +252,7 @@ export const WorkspaceSettingsModal = observer(function WorkspaceSettingsModal()
|
|||
type TWorkspaceModalSidebarProps = {
|
||||
activeTab: TWorkspaceSettingsModalTab;
|
||||
allowPermissions: ReturnType<typeof useUserPermissions>["allowPermissions"];
|
||||
isCodexAgentEntitled: boolean;
|
||||
isLauncherManagedWorkspace: boolean;
|
||||
isVoiceTaskerEntitled: boolean;
|
||||
onSelectItem: (itemKey: TWorkspaceSettingsTabs, itemHref: string) => void;
|
||||
|
|
@ -247,6 +262,7 @@ type TWorkspaceModalSidebarProps = {
|
|||
function WorkspaceModalSidebar({
|
||||
activeTab,
|
||||
allowPermissions,
|
||||
isCodexAgentEntitled,
|
||||
isLauncherManagedWorkspace,
|
||||
isVoiceTaskerEntitled,
|
||||
onSelectItem,
|
||||
|
|
@ -267,7 +283,11 @@ function WorkspaceModalSidebar({
|
|||
(item) =>
|
||||
!HIDDEN_WORKSPACE_SETTINGS_KEYS.has(item.key) &&
|
||||
(!isLauncherManagedWorkspace || !LAUNCHER_MANAGED_HIDDEN_WORKSPACE_SETTINGS_KEYS.has(item.key)) &&
|
||||
(!WORKSPACE_FEATURE_GATED_SETTINGS_KEYS.has(item.key) || isVoiceTaskerEntitled) &&
|
||||
(!WORKSPACE_FEATURE_GATED_SETTINGS_KEYS.has(item.key) ||
|
||||
isWorkspaceFeatureSettingsEntitled(item.key, {
|
||||
isCodexAgentEntitled,
|
||||
isVoiceTaskerEntitled,
|
||||
})) &&
|
||||
allowPermissions(item.access, EUserPermissionsLevel.WORKSPACE, workspaceSlug)
|
||||
);
|
||||
|
||||
|
|
@ -302,3 +322,16 @@ function WorkspaceModalSidebar({
|
|||
</ScrollArea>
|
||||
);
|
||||
}
|
||||
|
||||
function isWorkspaceFeatureSettingsEntitled(
|
||||
itemKey: TWorkspaceSettingsTabs,
|
||||
entitlements: {
|
||||
isCodexAgentEntitled: boolean;
|
||||
isVoiceTaskerEntitled: boolean;
|
||||
}
|
||||
) {
|
||||
if (itemKey === "ai-voice-tasker") return entitlements.isVoiceTaskerEntitled;
|
||||
if (itemKey === "codex-agent-api") return entitlements.isCodexAgentEntitled;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,14 @@ export const WORKSPACE_SETTINGS_MODAL_EVENT = "nodedc:workspace-settings-modal";
|
|||
|
||||
export const WORKSPACE_SETTINGS_WEBHOOK_QUERY_KEY = "webhookId";
|
||||
|
||||
export type TWorkspaceSettingsModalTab = "general" | "members" | "export" | "storage" | "webhooks" | "ai-voice-tasker";
|
||||
export type TWorkspaceSettingsModalTab =
|
||||
| "general"
|
||||
| "members"
|
||||
| "export"
|
||||
| "storage"
|
||||
| "webhooks"
|
||||
| "ai-voice-tasker"
|
||||
| "codex-agent-api";
|
||||
|
||||
type TWorkspaceSettingsModalEventDetail = {
|
||||
isOpen: boolean;
|
||||
|
|
@ -23,7 +30,8 @@ export const getWorkspaceSettingsModalTabFromSearch = (search: string): TWorkspa
|
|||
value === "export" ||
|
||||
value === "storage" ||
|
||||
value === "webhooks" ||
|
||||
value === "ai-voice-tasker"
|
||||
value === "ai-voice-tasker" ||
|
||||
value === "codex-agent-api"
|
||||
)
|
||||
return value;
|
||||
|
||||
|
|
|
|||
|
|
@ -43,6 +43,9 @@ export interface NodeDCWorkspacePolicy {
|
|||
default_managed_by: "launcher" | "tasker";
|
||||
invite_approval: "tasker" | "nodedc" | "launcher" | "disabled";
|
||||
default_invite_approval: "tasker" | "nodedc" | "launcher" | "disabled";
|
||||
service_modules?: {
|
||||
codex_agents?: boolean;
|
||||
};
|
||||
workspaces: Array<{
|
||||
slug: string;
|
||||
name: string | null;
|
||||
|
|
|
|||
|
|
@ -70,6 +70,13 @@ export const WORKSPACE_SETTINGS: Record<TWorkspaceSettingsTabs, TWorkspaceSettin
|
|||
access: [EUserWorkspaceRoles.ADMIN],
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/ai-voice-tasker/`,
|
||||
},
|
||||
"codex-agent-api": {
|
||||
key: "codex-agent-api",
|
||||
i18n_label: "workspace_settings.settings.codex_agent_api.title",
|
||||
href: `/settings/codex-agent-api`,
|
||||
access: [EUserWorkspaceRoles.ADMIN],
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/codex-agent-api/`,
|
||||
},
|
||||
};
|
||||
|
||||
export const WORKSPACE_SETTINGS_ACCESS = Object.fromEntries(
|
||||
|
|
@ -84,6 +91,9 @@ export const GROUPED_WORKSPACE_SETTINGS: Record<WORKSPACE_SETTINGS_CATEGORY, TWo
|
|||
WORKSPACE_SETTINGS["export"],
|
||||
WORKSPACE_SETTINGS["storage"],
|
||||
],
|
||||
[WORKSPACE_SETTINGS_CATEGORY.FEATURES]: [WORKSPACE_SETTINGS["ai-voice-tasker"]],
|
||||
[WORKSPACE_SETTINGS_CATEGORY.FEATURES]: [
|
||||
WORKSPACE_SETTINGS["ai-voice-tasker"],
|
||||
WORKSPACE_SETTINGS["codex-agent-api"],
|
||||
],
|
||||
[WORKSPACE_SETTINGS_CATEGORY.DEVELOPER]: [WORKSPACE_SETTINGS["webhooks"]],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -325,11 +325,11 @@ export default {
|
|||
incoming_description: "Requests routed into this contour from other projects will appear here.",
|
||||
},
|
||||
},
|
||||
list: {
|
||||
last_updated: "Last updated",
|
||||
unassigned: "Unassigned",
|
||||
unread_updates: "New updates available",
|
||||
},
|
||||
list: {
|
||||
last_updated: "Last updated",
|
||||
unassigned: "Unassigned",
|
||||
unread_updates: "New updates available",
|
||||
},
|
||||
empty_state: {
|
||||
title: "External contours module is ready for the next stage",
|
||||
description:
|
||||
|
|
@ -424,7 +424,8 @@ export default {
|
|||
},
|
||||
decline_modal: {
|
||||
title: "Return the request for rework",
|
||||
description: "Provide the reason for returning the request. This comment will be sent to the external contour and added to the target issue.",
|
||||
description:
|
||||
"Provide the reason for returning the request. This comment will be sent to the external contour and added to the target issue.",
|
||||
placeholder: "Describe what needs to be revised or clarified",
|
||||
submit: "Decline and return",
|
||||
},
|
||||
|
|
@ -1794,6 +1795,9 @@ export default {
|
|||
ai_voice_tasker: {
|
||||
title: "AI / Voice Tasker",
|
||||
},
|
||||
codex_agent_api: {
|
||||
title: "Codex Agent API",
|
||||
},
|
||||
api_tokens: {
|
||||
title: "Personal Access Tokens",
|
||||
add_token: "Add personal access token",
|
||||
|
|
@ -1991,7 +1995,8 @@ export default {
|
|||
},
|
||||
list_heading: "Estimate list",
|
||||
archived_heading: "Archived estimates",
|
||||
archived_description: "These are estimates from earlier project versions that are not currently in use. Read more",
|
||||
archived_description:
|
||||
"These are estimates from earlier project versions that are not currently in use. Read more",
|
||||
no_estimate: "No estimate",
|
||||
new: "New estimate system",
|
||||
create: {
|
||||
|
|
@ -2395,7 +2400,8 @@ export default {
|
|||
project_page: {
|
||||
delete_modal: {
|
||||
title: "Delete page",
|
||||
content: 'Are you sure you want to delete page "{value}"? The page will be permanently removed and this action cannot be undone.',
|
||||
content:
|
||||
'Are you sure you want to delete page "{value}"? The page will be permanently removed and this action cannot be undone.',
|
||||
success_title: "Page deleted",
|
||||
success_message: "Page deleted successfully.",
|
||||
error_title: "Page delete failed",
|
||||
|
|
@ -3108,24 +3114,24 @@ export default {
|
|||
project_settings_label: "Project settings",
|
||||
project_join_modal: {
|
||||
title: "Join project?",
|
||||
description: "Are you sure you want to join the project {project}? Click \"Join project\" to continue.",
|
||||
description: 'Are you sure you want to join the project {project}? Click "Join project" to continue.',
|
||||
submit: "Join project",
|
||||
loading: "Joining...",
|
||||
},
|
||||
project_leave_modal: {
|
||||
title: "Leave project",
|
||||
description:
|
||||
"Are you sure you want to leave the project \"{project}\"? All work items associated with you will become inaccessible.",
|
||||
'Are you sure you want to leave the project "{project}"? All work items associated with you will become inaccessible.',
|
||||
enter_project_name: "Enter the project name {project} to continue:",
|
||||
project_name_placeholder: "Enter project name",
|
||||
confirm_instruction: "To confirm, type {keyword} below:",
|
||||
confirm_placeholder: "Enter \"leave project\"",
|
||||
confirm_placeholder: 'Enter "leave project"',
|
||||
confirm_keyword: "leave project",
|
||||
loading: "Leaving...",
|
||||
submit: "Leave project",
|
||||
error_title: "Error!",
|
||||
error_default: "Something went wrong. Please try again later.",
|
||||
error_confirm: "Please confirm leaving the project by typing \"leave project\".",
|
||||
error_confirm: 'Please confirm leaving the project by typing "leave project".',
|
||||
error_name: "Please enter the project name exactly as shown in the description.",
|
||||
error_fields: "Please fill all fields.",
|
||||
},
|
||||
|
|
@ -3136,7 +3142,7 @@ export default {
|
|||
enter_project_name: "Enter the project name {project} to continue:",
|
||||
project_name_placeholder: "Project name",
|
||||
confirm_instruction: "To confirm, type {keyword} below:",
|
||||
confirm_placeholder: "Enter \"delete my project\"",
|
||||
confirm_placeholder: 'Enter "delete my project"',
|
||||
confirm_keyword: "delete my project",
|
||||
loading: "Deleting",
|
||||
submit: "Delete project",
|
||||
|
|
@ -3151,7 +3157,7 @@ export default {
|
|||
type_workspace_name: "Type this workspace name to continue.",
|
||||
final_confirmation: "For final confirmation, type {keyword} below.",
|
||||
confirm_keyword: "delete my workspace",
|
||||
input_placeholder: "Enter \"delete my workspace\"",
|
||||
input_placeholder: 'Enter "delete my workspace"',
|
||||
},
|
||||
project_invitation_modal: {
|
||||
success_title: "Success!",
|
||||
|
|
|
|||
|
|
@ -482,11 +482,11 @@ export default {
|
|||
incoming_description: "Здесь будут видны запросы, которые пришли в этот контур из других проектов.",
|
||||
},
|
||||
},
|
||||
list: {
|
||||
last_updated: "Последнее изменение",
|
||||
unassigned: "Не назначено",
|
||||
unread_updates: "Есть новые изменения",
|
||||
},
|
||||
list: {
|
||||
last_updated: "Последнее изменение",
|
||||
unassigned: "Не назначено",
|
||||
unread_updates: "Есть новые изменения",
|
||||
},
|
||||
empty_state: {
|
||||
title: "Модуль внешних контуров подготовлен",
|
||||
description:
|
||||
|
|
@ -552,7 +552,8 @@ export default {
|
|||
},
|
||||
traceability: {
|
||||
title: "Маршрутизация",
|
||||
description: "Здесь отображается, из какого контура ушёл запрос, куда он направлен и в каком состоянии находится работа по нему.",
|
||||
description:
|
||||
"Здесь отображается, из какого контура ушёл запрос, куда он направлен и в каком состоянии находится работа по нему.",
|
||||
source_contour: "Исходный внутренний контур",
|
||||
source_decision: "Решение источника",
|
||||
source_decision_pending: "Ожидает решения",
|
||||
|
|
@ -1956,6 +1957,9 @@ export default {
|
|||
ai_voice_tasker: {
|
||||
title: "AI / Voice Tasker",
|
||||
},
|
||||
codex_agent_api: {
|
||||
title: "Codex Agent API",
|
||||
},
|
||||
api_tokens: {
|
||||
title: "API-токены",
|
||||
add_token: "Добавить токен",
|
||||
|
|
@ -2552,7 +2556,8 @@ export default {
|
|||
project_page: {
|
||||
delete_modal: {
|
||||
title: "Удалить страницу",
|
||||
content: 'Вы уверены, что хотите удалить страницу "{value}"? Страница будет удалена без возможности восстановления.',
|
||||
content:
|
||||
'Вы уверены, что хотите удалить страницу "{value}"? Страница будет удалена без возможности восстановления.',
|
||||
success_title: "Страница удалена",
|
||||
success_message: "Страница успешно удалена.",
|
||||
error_title: "Не удалось удалить страницу",
|
||||
|
|
@ -3379,8 +3384,7 @@ export default {
|
|||
},
|
||||
cycles: {
|
||||
title: "Двигайтесь циклами",
|
||||
description:
|
||||
"Циклы помогают команде двигаться быстрее и ближе всего соответствуют спринтам в agile-подходе.",
|
||||
description: "Циклы помогают команде двигаться быстрее и ближе всего соответствуют спринтам в agile-подходе.",
|
||||
},
|
||||
modules: {
|
||||
title: "Делите работу на модули",
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ export type TWorkspaceSettingsTabs =
|
|||
| "export"
|
||||
| "storage"
|
||||
| "webhooks"
|
||||
| "ai-voice-tasker";
|
||||
| "ai-voice-tasker"
|
||||
| "codex-agent-api";
|
||||
export type TWorkspaceSettingsItem = {
|
||||
key: TWorkspaceSettingsTabs;
|
||||
i18n_label: string;
|
||||
|
|
|
|||
Loading…
Reference in New Issue