mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-14 16:17:25 +00:00
feat(teams): add workspace scope setting UI and deprecate agent links
Add workspace sharing card selector (isolated/shared) to team settings, improving on the API-only configuration. Update workspace_read error message to guide agents toward per-user scope fallback. Mark agent links as deprecated with amber warning banner pointing to agent teams.
This commit is contained in:
1 parent
bc9ebaa2d0
commit
16d726d5a2
9 files changed
+81
-11
No files matched your search
@@ -182,7 +182,7 @@ func resolveWorkspaceScopeFromArgs(ctx context.Context, args map[string]any, ws
|
||||
channel, chatID = resolveWorkspaceScope(ctx)
|
||||
if scope == "team" {
|
||||
if !ws.isShared() {
|
||||
return "", "", "team workspace_scope is 'isolated' — set to 'shared' in team settings to use scope=team"
|
||||
return "", "", "team workspace_scope is 'isolated' — retry with scope='channel' (or omit scope) to use your per-user workspace. To enable shared team workspace, ask an admin to set workspace_scope='shared' in team settings."
|
||||
}
|
||||
chatID = ""
|
||||
}
|
||||
|
||||
@@ -193,7 +193,7 @@
|
||||
"revoke": "Revoke"
|
||||
},
|
||||
"links": {
|
||||
"teamsHint": "For multi-agent collaboration with task tracking and quality control, consider using Agent Teams instead. Agent Links are best for simple 1-to-1 delegation between two agents.",
|
||||
"teamsHint": "Agent Links will be removed soon. Please use Agent Teams for multi-agent collaboration — it provides task tracking, shared workspace, and quality control in a simpler setup.",
|
||||
"createLink": "Create Link",
|
||||
"targetAgent": "Target Agent",
|
||||
"selectAgent": "Select or search agent...",
|
||||
|
||||
@@ -190,6 +190,13 @@
|
||||
"followupIntervalHint": "How long to wait before sending each follow-up reminder to the user.",
|
||||
"followupMaxReminders": "Max reminders",
|
||||
"followupMaxRemindersHint": "Maximum number of reminders per task. 0 = unlimited (remind until user replies).",
|
||||
"workspace": "Workspace",
|
||||
"workspaceScope": "Workspace Sharing",
|
||||
"workspaceScopeHint": "Control how agents access files in the team workspace.",
|
||||
"workspaceScopeIsolated": "Isolated",
|
||||
"workspaceScopeIsolatedDesc": "Each agent has its own private workspace. Agents cannot read or write files from other agents.",
|
||||
"workspaceScopeShared": "Shared",
|
||||
"workspaceScopeSharedDesc": "All agents share a single team workspace. Agents can read and write each other's files.",
|
||||
"teamVersion": "Team Version",
|
||||
"versionBasic": "Basic",
|
||||
"versionAdvanced": "Advanced",
|
||||
|
||||
@@ -193,7 +193,7 @@
|
||||
"revoke": "Thu hồi"
|
||||
},
|
||||
"links": {
|
||||
"teamsHint": "Để cộng tác đa agent với theo dõi nhiệm vụ và kiểm soát chất lượng, hãy cân nhắc sử dụng Nhóm agent thay thế. Liên kết agent phù hợp nhất cho ủy quyền đơn giản 1-1 giữa hai agent.",
|
||||
"teamsHint": "Agent Links sẽ sớm bị gỡ bỏ. Vui lòng sử dụng Agent Teams để cộng tác đa agent — hỗ trợ quản lý nhiệm vụ, workspace chung và kiểm soát chất lượng trong một hệ thống đơn giản hơn.",
|
||||
"createLink": "Tạo liên kết",
|
||||
"targetAgent": "Agent đích",
|
||||
"selectAgent": "Chọn hoặc tìm kiếm agent...",
|
||||
|
||||
@@ -190,6 +190,13 @@
|
||||
"followupIntervalHint": "Thời gian chờ trước khi gửi mỗi lần nhắc nhở cho người dùng.",
|
||||
"followupMaxReminders": "Số lần nhắc tối đa",
|
||||
"followupMaxRemindersHint": "Số lần nhắc tối đa cho mỗi nhiệm vụ. 0 = không giới hạn (nhắc cho đến khi người dùng trả lời).",
|
||||
"workspace": "Không gian làm việc",
|
||||
"workspaceScope": "Chia sẻ không gian làm việc",
|
||||
"workspaceScopeHint": "Kiểm soát cách các agent truy cập tệp trong không gian làm việc của team.",
|
||||
"workspaceScopeIsolated": "Cô lập",
|
||||
"workspaceScopeIsolatedDesc": "Mỗi agent có không gian làm việc riêng. Không thể đọc hoặc ghi tệp của agent khác.",
|
||||
"workspaceScopeShared": "Chia sẻ",
|
||||
"workspaceScopeSharedDesc": "Tất cả agent dùng chung một không gian làm việc. Có thể đọc và ghi tệp của nhau.",
|
||||
"teamVersion": "Phiên bản Team",
|
||||
"versionBasic": "Cơ bản",
|
||||
"versionAdvanced": "Nâng cao",
|
||||
|
||||
@@ -193,7 +193,7 @@
|
||||
"revoke": "撤销"
|
||||
},
|
||||
"links": {
|
||||
"teamsHint": "对于具有任务跟踪和质量控制的多Agent协作,请考虑使用Agent Team。Agent链接最适合两个Agent之间的简单一对一委派。",
|
||||
"teamsHint": "Agent Links 即将移除。请使用 Agent Teams 进行多代理协作——它提供任务跟踪、共享工作区和质量控制,且设置更简单。",
|
||||
"createLink": "创建链接",
|
||||
"targetAgent": "目标Agent",
|
||||
"selectAgent": "选择或搜索Agent...",
|
||||
|
||||
@@ -190,6 +190,13 @@
|
||||
"followupIntervalHint": "在每次向用户发送跟进提醒前等待的时间。",
|
||||
"followupMaxReminders": "最大提醒次数",
|
||||
"followupMaxRemindersHint": "每个任务的最大提醒次数。0 = 无限制(持续提醒直到用户回复)。",
|
||||
"workspace": "工作区",
|
||||
"workspaceScope": "工作区共享",
|
||||
"workspaceScopeHint": "控制代理如何访问团队工作区中的文件。",
|
||||
"workspaceScopeIsolated": "隔离",
|
||||
"workspaceScopeIsolatedDesc": "每个代理拥有独立的工作区。无法读取或写入其他代理的文件。",
|
||||
"workspaceScopeShared": "共享",
|
||||
"workspaceScopeSharedDesc": "所有代理共享同一个工作区。可以读取和写入彼此的文件。",
|
||||
"teamVersion": "团队版本",
|
||||
"versionBasic": "基础版",
|
||||
"versionAdvanced": "高级版",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect, useMemo } from "react";
|
||||
import { Link } from "react-router";
|
||||
import { Info } from "lucide-react";
|
||||
import { TriangleAlert } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ConfirmDialog } from "@/components/shared/confirm-dialog";
|
||||
import { useAgentLinks } from "../hooks/use-agent-links";
|
||||
@@ -47,11 +47,11 @@ export function AgentLinksTab({ agentId }: AgentLinksTabProps) {
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl space-y-6">
|
||||
<div className="flex items-start gap-3 rounded-lg border border-blue-200 bg-blue-50 px-4 py-3 dark:border-blue-900 dark:bg-blue-950/30">
|
||||
<Info className="mt-0.5 h-4 w-4 shrink-0 text-blue-600 dark:text-blue-400" />
|
||||
<p className="text-sm text-blue-800 dark:text-blue-300">
|
||||
<div className="flex items-start gap-3 rounded-lg border border-amber-200 bg-amber-50 px-4 py-3 dark:border-amber-900 dark:bg-amber-950/30">
|
||||
<TriangleAlert className="mt-0.5 h-4 w-4 shrink-0 text-amber-600 dark:text-amber-400" />
|
||||
<p className="text-sm text-amber-800 dark:text-amber-300">
|
||||
{t("links.teamsHint").split("Agent Teams")[0]}
|
||||
<Link to={ROUTES.TEAMS} className="font-medium underline underline-offset-2 hover:text-blue-900 dark:hover:text-blue-200">
|
||||
<Link to={ROUTES.TEAMS} className="font-medium underline underline-offset-2 hover:text-amber-900 dark:hover:text-amber-200">
|
||||
Agent Teams
|
||||
</Link>
|
||||
{t("links.teamsHint").split("Agent Teams")[1]}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { Combobox } from "@/components/ui/combobox";
|
||||
import { X, Save, Check, Bell, ShieldAlert, Clock, Info } from "lucide-react";
|
||||
import { X, Save, Check, Bell, ShieldAlert, Clock, Info, FolderLock, FolderSync } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { CHANNEL_TYPES } from "@/constants/channels";
|
||||
import type { TeamData, TeamAccessSettings, EscalationMode, EscalationAction } from "@/types/team";
|
||||
@@ -77,6 +77,7 @@ export function TeamSettingsTab({ teamId, team, onSaved }: TeamSettingsTabProps)
|
||||
const [escalationActions, setEscalationActions] = useState<EscalationAction[]>(initial.escalation_actions ?? []);
|
||||
const [followupInterval, setFollowupInterval] = useState<number>(initial.followup_interval_minutes ?? 30);
|
||||
const [followupMaxReminders, setFollowupMaxReminders] = useState<number>(initial.followup_max_reminders ?? 0);
|
||||
const [workspaceScope, setWorkspaceScope] = useState<string>(initial.workspace_scope ?? "isolated");
|
||||
const isTeamV2 = version >= 2;
|
||||
|
||||
const [saving, setSaving] = useState(false);
|
||||
@@ -102,6 +103,7 @@ export function TeamSettingsTab({ teamId, team, onSaved }: TeamSettingsTabProps)
|
||||
setEscalationActions(s.escalation_actions ?? []);
|
||||
setFollowupInterval(s.followup_interval_minutes ?? 30);
|
||||
setFollowupMaxReminders(s.followup_max_reminders ?? 0);
|
||||
setWorkspaceScope(s.workspace_scope ?? "isolated");
|
||||
setSaved(false);
|
||||
setError(null);
|
||||
}, [team]);
|
||||
@@ -123,6 +125,7 @@ export function TeamSettingsTab({ teamId, team, onSaved }: TeamSettingsTabProps)
|
||||
}
|
||||
if (followupInterval !== 30) settings.followup_interval_minutes = followupInterval;
|
||||
if (followupMaxReminders !== 0) settings.followup_max_reminders = followupMaxReminders;
|
||||
if (workspaceScope === "shared") settings.workspace_scope = "shared";
|
||||
if (version >= 2) settings.version = version;
|
||||
await updateTeamSettings(teamId, settings);
|
||||
setSaved(true);
|
||||
@@ -133,7 +136,7 @@ export function TeamSettingsTab({ teamId, team, onSaved }: TeamSettingsTabProps)
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
}, [teamId, version, allowUserIds, denyUserIds, allowChannels, denyChannels, progressNotifications, escalationMode, escalationActions, followupInterval, followupMaxReminders, updateTeamSettings, onSaved, t]);
|
||||
}, [teamId, version, allowUserIds, denyUserIds, allowChannels, denyChannels, progressNotifications, escalationMode, escalationActions, followupInterval, followupMaxReminders, workspaceScope, updateTeamSettings, onSaved, t]);
|
||||
|
||||
const userOptions = knownUsers.map((u) => ({ value: u, label: u }));
|
||||
const channelOptions = CHANNEL_TYPES.map((c) => ({ value: c.value, label: c.label }));
|
||||
@@ -276,6 +279,52 @@ export function TeamSettingsTab({ teamId, team, onSaved }: TeamSettingsTabProps)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Workspace Scope */}
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-sm font-medium">{t("settings.workspace")}</h3>
|
||||
<div className="rounded-lg border bg-gradient-to-r from-emerald-500/5 to-teal-500/5 p-4">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="rounded-lg bg-emerald-500/10 p-2.5 text-emerald-600 dark:text-emerald-400">
|
||||
<FolderSync className="h-5 w-5" />
|
||||
</div>
|
||||
<div className="flex-1 space-y-3">
|
||||
<div className="space-y-1">
|
||||
<span className="text-sm font-semibold">{t("settings.workspaceScope")}</span>
|
||||
<p className="text-xs text-muted-foreground leading-relaxed">
|
||||
{t("settings.workspaceScopeHint")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2">
|
||||
{([
|
||||
{ value: "isolated", Icon: FolderLock, labelKey: "workspaceScopeIsolated", descKey: "workspaceScopeIsolatedDesc" },
|
||||
{ value: "shared", Icon: FolderSync, labelKey: "workspaceScopeShared", descKey: "workspaceScopeSharedDesc" },
|
||||
] as const).map((opt) => (
|
||||
<button
|
||||
key={opt.value}
|
||||
type="button"
|
||||
onClick={() => setWorkspaceScope(opt.value)}
|
||||
className={
|
||||
"flex items-start gap-3 rounded-lg border p-3 text-left transition-colors " +
|
||||
(workspaceScope === opt.value
|
||||
? "border-primary bg-primary/5"
|
||||
: "border-border hover:border-primary/50")
|
||||
}
|
||||
>
|
||||
<opt.Icon className="mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" />
|
||||
<div>
|
||||
<div className="text-sm font-medium">{t(`settings.${opt.labelKey}`)}</div>
|
||||
<div className="mt-0.5 text-xs text-muted-foreground">
|
||||
{t(`settings.${opt.descKey}`)}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Escalation Policy (V2 only) */}
|
||||
{isTeamV2 && <div className="space-y-4">
|
||||
<h3 className="text-sm font-medium">{t("settings.escalationPolicy")}</h3>
|
||||
|
||||
Reference in new issue
Block a user