fix(config): remove ignored session scoping controls (#1405)

Co-authored-by: Collective Developer <man@collective.dev>
This commit is contained in:
ナムandCollective Developer authored and GitHub committed 2026-07-09 16:24:58 +07:00
1 parent ba621391c2
commit fb28b72b27
7 files changed
+116 -125

No files matched your search

-6
View File
@@ -205,8 +205,6 @@
"behavior.pendingCompactionInfo": "When a group chat exceeds the threshold, the LLM automatically summarizes older messages into a single compact entry, keeping the most recent ones intact for context.",
"behavior.rateLimitTitle": "Rate Limiting",
"behavior.rateLimitDescription": "Inbound message size limits and per-user request throttling",
"behavior.sessionsTitle": "Session Scoping",
"behavior.sessionsDescription": "Controls how conversation context is partitioned across users and channels",
"behavior.securityTitle": "Input Security",
"behavior.securityDescription": "Prompt injection detection and credential scrubbing to protect the gateway",
"behavior.injectionActionHint": "What to do when a prompt injection attempt is detected in an inbound message.",
@@ -215,10 +213,6 @@
"behavior.scrubCredentialsHint": "Automatically redact API keys, tokens, and passwords found in tool output before returning to the agent.",
"behavior.scrubCredentialsInfo": "Credential patterns in tool responses are masked before the agent sees them.",
"behavior.scrubCredentialsOffInfo": "Credential scrubbing is disabled. Secrets in tool output may be exposed to the agent.",
"sessions.scope": "Session Scope",
"sessions.scopeTip": "Determines how sessions are partitioned. 'Per Sender' gives each user a separate session; 'Global' shares one session across all users.",
"sessions.dmScope": "DM Scope",
"sessions.dmScopeTip": "How direct-message sessions are keyed. 'Per Channel Peer' is the recommended default.",
"quota.title": "Usage Quota",
"quota.description": "Per-user message quota limits with optional per-provider, channel, and group overrides",
"quota.enabled": "Enable Quotas",
-6
View File
@@ -171,8 +171,6 @@
"behavior.pendingCompactionInfo": "그룹 채팅이 임계값을 초과하면 LLM이 오래된 메시지를 단일 압축 항목으로 자동 요약하여 컨텍스트를 위해 가장 최근 메시지를 유지합니다.",
"behavior.rateLimitTitle": "속도 제한",
"behavior.rateLimitDescription": "인바운드 메시지 크기 제한 및 사용자당 요청 쓰로틀링",
"behavior.sessionsTitle": "세션 범위",
"behavior.sessionsDescription": "사용자와 채널 간에 대화 컨텍스트가 분할되는 방식을 제어합니다",
"behavior.securityTitle": "입력 보안",
"behavior.securityDescription": "게이트웨이를 보호하기 위한 프롬프트 주입 감지 및 자격증명 스크러빙",
"behavior.injectionActionHint": "인바운드 메시지에서 프롬프트 주입 시도가 감지될 때 수행할 작업입니다.",
@@ -181,10 +179,6 @@
"behavior.scrubCredentialsHint": "에이전트에 반환하기 전에 도구 출력에서 발견된 API 키, 토큰 및 비밀번호를 자동으로 수정합니다.",
"behavior.scrubCredentialsInfo": "도구 응답의 자격증명 패턴은 에이전트가 보기 전에 마스킹됩니다.",
"behavior.scrubCredentialsOffInfo": "자격증명 스크러빙이 비활성화되어 있습니다. 도구 출력의 시크릿이 에이전트에 노출될 수 있습니다.",
"sessions.scope": "세션 범위",
"sessions.scopeTip": "세션 분할 방법을 결정합니다. '발신자별'은 각 사용자에게 별도 세션을 제공하고, '전역'은 모든 사용자가 하나의 세션을 공유합니다.",
"sessions.dmScope": "DM 범위",
"sessions.dmScopeTip": "DM 세션이 키로 구분되는 방법입니다. '채널 피어별'이 권장 기본값입니다.",
"quota.title": "사용 할당량",
"quota.description": "선택적 공급자, 채널 및 그룹별 재정의가 있는 사용자별 메시지 할당량 제한",
"quota.enabled": "할당량 활성화",
-6
View File
@@ -204,8 +204,6 @@
"behavior.pendingCompactionInfo": "Khi nhóm chat vượt ngưỡng, LLM tự động tóm tắt các tin nhắn cũ thành một mục rút gọn, giữ nguyên các tin gần đây nhất làm ngữ cảnh.",
"behavior.rateLimitTitle": "Giới hạn tốc độ",
"behavior.rateLimitDescription": "Giới hạn kích thước tin nhắn đến và kiểm soát tốc độ yêu cầu mỗi người dùng",
"behavior.sessionsTitle": "Phạm vi phiên",
"behavior.sessionsDescription": "Kiểm soát cách phân vùng ngữ cảnh cuộc trò chuyện theo người dùng và kênh",
"behavior.securityTitle": "Bảo mật đầu vào",
"behavior.securityDescription": "Phát hiện injection prompt và ẩn thông tin xác thực để bảo vệ gateway",
"behavior.injectionActionHint": "Hành động thực hiện khi phát hiện nỗ lực injection prompt trong tin nhắn đến.",
@@ -214,10 +212,6 @@
"behavior.scrubCredentialsHint": "Tự động ẩn khóa API, token và mật khẩu tìm thấy trong đầu ra công cụ trước khi trả về agent.",
"behavior.scrubCredentialsInfo": "Các mẫu thông tin xác thực trong phản hồi công cụ được che đi trước khi agent nhìn thấy.",
"behavior.scrubCredentialsOffInfo": "Tính năng ẩn thông tin xác thực bị tắt. Bí mật trong đầu ra công cụ có thể bị lộ với agent.",
"sessions.scope": "Phạm vi phiên",
"sessions.scopeTip": "Xác định cách phân vùng phiên. 'Theo người gửi' cho mỗi người dùng một phiên riêng; 'Toàn cục' chia sẻ một phiên cho tất cả người dùng.",
"sessions.dmScope": "Phạm vi tin nhắn riêng",
"sessions.dmScopeTip": "Cách đặt khóa phiên tin nhắn riêng. 'Theo kênh và đối tác' là mặc định được khuyến nghị.",
"quota.title": "Hạn mức sử dụng",
"quota.description": "Giới hạn hạn mức tin nhắn mỗi người dùng với tùy chọn ghi đè theo provider, kênh và nhóm",
"quota.enabled": "Bật hạn mức",
-6
View File
@@ -204,8 +204,6 @@
"behavior.pendingCompactionInfo": "当群聊超过阈值时,LLM 自动将旧消息总结为一条精简条目,保留最近的消息作为上下文。",
"behavior.rateLimitTitle": "速率限制",
"behavior.rateLimitDescription": "入站消息大小限制和每用户请求限流",
"behavior.sessionsTitle": "会话范围",
"behavior.sessionsDescription": "控制跨用户和频道的对话上下文分区方式",
"behavior.securityTitle": "输入安全",
"behavior.securityDescription": "提示词注入检测和凭证擦除,以保护网关",
"behavior.injectionActionHint": "当入站消息中检测到提示词注入尝试时的处理动作。",
@@ -214,10 +212,6 @@
"behavior.scrubCredentialsHint": "在返回给 Agent 之前,自动从工具输出中删除 API 密钥、令牌和密码。",
"behavior.scrubCredentialsInfo": "工具响应中的凭证模式在 Agent 看到之前被屏蔽。",
"behavior.scrubCredentialsOffInfo": "凭证擦除已禁用,工具输出中的密钥可能暴露给 Agent。",
"sessions.scope": "会话范围",
"sessions.scopeTip": "确定会话分区方式。「按发送者」为每个用户提供单独的会话;「全局」在所有用户之间共享一个会话。",
"sessions.dmScope": "私信范围",
"sessions.dmScopeTip": "私信会话的键控方式。推荐默认使用「按频道对等方」。",
"quota.title": "使用配额",
"quota.description": "每用户消息配额限制,可选按提供商、频道和群组覆盖",
"quota.enabled": "启用配额",
@@ -0,0 +1,76 @@
import { describe, expect, it } from "vitest";
import { buildBehaviorPatch } from "./behavior-section";
describe("buildBehaviorPatch", () => {
it("does not include deprecated session scoping config", () => {
const patch = buildBehaviorPatch({
rate: {
max_message_chars: 12000,
rate_limit_rpm: 30,
inbound_debounce_ms: 250,
},
security: {
injection_action: "warn",
scrub_credentials: true,
},
ux: {
intent_classify: true,
team_work_classify: false,
},
pendingCompaction: {
threshold: 200,
keep_recent: 40,
},
chatBehavior: {
enabled: true,
quick_ack: {
enabled: true,
mode: "sidecar_generated",
min_delay_ms: 1000,
provider: "",
model: "",
timeout_ms: 2500,
max_tokens: 40,
max_chars: 120,
templates: ["Got it. Working on it..."],
},
intermediate_replies: {
enabled: false,
mode: "sidecar_generated",
provider: "",
model: "",
timeout_ms: 2500,
max_tokens: 60,
max_chars: 180,
},
final_split: {
enabled: false,
min_chars: 1200,
max_messages: 3,
delay_ms: 500,
},
},
});
expect(Object.prototype.hasOwnProperty.call(patch, "sessions")).toBe(false);
expect(patch).toMatchObject({
gateway: {
max_message_chars: 12000,
rate_limit_rpm: 30,
inbound_debounce_ms: 250,
injection_action: "warn",
team_work_classify: false,
},
agents: {
defaults: { intent_classify: true },
},
tools: { scrub_credentials: true },
channels: {
pending_compaction: {
threshold: 200,
keep_recent: 40,
},
},
});
});
});
@@ -4,27 +4,23 @@ import { useTranslation } from "react-i18next";
import { Button } from "@/components/ui/button";
import { BehaviorUxCard } from "./behavior-ux-card";
import { BehaviorRateCard } from "./behavior-rate-card";
import { BehaviorSessionsCard } from "./behavior-sessions-card";
import { BehaviorSecurityCard } from "./behavior-security-card";
import { BehaviorPendingCompactionCard, type PendingCompactionValues } from "./behavior-pending-compaction-card";
import { BehaviorChatCard, type ChatBehaviorValues } from "./behavior-chat-card";
import { BehaviorPassiveMemoryCard } from "./behavior-passive-memory-card";
interface Props {
config: Record<string, any>;
onPatch: (updates: Record<string, unknown>) => Promise<void>;
saving: boolean;
}
/** State container for Behavior tab — composes 4 sub-cards, patches multiple config keys. */
/** State container for Behavior tab cards and their patch payload. */
export function BehaviorSection({ config, onPatch, saving }: Props) {
const { t } = useTranslation("config");
const gw = config.gateway ?? {};
const ag = config.agents?.defaults ?? {};
const tl = config.tools ?? {};
const ss = config.sessions ?? {};
const ch = config.channels ?? {};
// UX toggles (from gateway + agents.defaults)
@@ -40,12 +36,6 @@ export function BehaviorSection({ config, onPatch, saving }: Props) {
inbound_debounce_ms: gw.inbound_debounce_ms,
});
// Sessions
const [sessions, setSessions] = useState<{ scope?: string; dm_scope?: string }>({
scope: ss.scope,
dm_scope: ss.dm_scope,
});
// Security (from gateway + tools)
const [security, setSecurity] = useState<{ injection_action?: string; scrub_credentials?: boolean }>({
injection_action: gw.injection_action,
@@ -71,7 +61,6 @@ export function BehaviorSection({ config, onPatch, saving }: Props) {
rate_limit_rpm: gw.rate_limit_rpm,
inbound_debounce_ms: gw.inbound_debounce_ms,
});
setSessions({ scope: ss.scope, dm_scope: ss.dm_scope });
setSecurity({
injection_action: gw.injection_action,
scrub_credentials: tl.scrub_credentials,
@@ -85,22 +74,13 @@ export function BehaviorSection({ config, onPatch, saving }: Props) {
(v: T) => { setter(v); setDirty(true); };
const handleSave = () => {
onPatch({
gateway: {
max_message_chars: rate.max_message_chars,
rate_limit_rpm: rate.rate_limit_rpm,
inbound_debounce_ms: rate.inbound_debounce_ms,
injection_action: security.injection_action,
chat_behavior: chatBehavior,
team_work_classify: ux.team_work_classify,
},
agents: {
defaults: { intent_classify: ux.intent_classify },
},
tools: { scrub_credentials: security.scrub_credentials },
sessions,
channels: { pending_compaction: pendingCompaction },
}).catch(() => {});
onPatch(buildBehaviorPatch({
rate,
security,
chatBehavior,
ux,
pendingCompaction,
})).catch(() => {});
};
return (
@@ -108,7 +88,6 @@ export function BehaviorSection({ config, onPatch, saving }: Props) {
<BehaviorUxCard value={ux} onChange={markDirty(setUx)} />
<BehaviorChatCard value={chatBehavior} onChange={markDirty(setChatBehavior)} />
<BehaviorRateCard value={rate} onChange={markDirty(setRate)} />
<BehaviorSessionsCard value={sessions} onChange={markDirty(setSessions)} />
<BehaviorSecurityCard value={security} onChange={markDirty(setSecurity)} />
<BehaviorPendingCompactionCard value={pendingCompaction} onChange={markDirty(setPendingCompaction)} />
<BehaviorPassiveMemoryCard />
@@ -124,6 +103,38 @@ export function BehaviorSection({ config, onPatch, saving }: Props) {
);
}
export interface BehaviorPatchInput {
rate: { max_message_chars?: number; rate_limit_rpm?: number; inbound_debounce_ms?: number };
security: { injection_action?: string; scrub_credentials?: boolean };
chatBehavior: ChatBehaviorValues;
ux: { intent_classify: boolean; team_work_classify: boolean };
pendingCompaction: PendingCompactionValues;
}
export function buildBehaviorPatch({
rate,
security,
chatBehavior,
ux,
pendingCompaction,
}: BehaviorPatchInput): Record<string, unknown> {
return {
gateway: {
max_message_chars: rate.max_message_chars,
rate_limit_rpm: rate.rate_limit_rpm,
inbound_debounce_ms: rate.inbound_debounce_ms,
injection_action: security.injection_action,
chat_behavior: chatBehavior,
team_work_classify: ux.team_work_classify,
},
agents: {
defaults: { intent_classify: ux.intent_classify },
},
tools: { scrub_credentials: security.scrub_credentials },
channels: { pending_compaction: pendingCompaction },
};
}
function normalizeChatBehavior(value: any): ChatBehaviorValues {
return {
enabled: value?.enabled ?? false,
@@ -1,72 +0,0 @@
import { useTranslation } from "react-i18next";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { InfoLabel } from "@/components/shared/info-label";
interface SessionsValues {
scope?: string;
dm_scope?: string;
}
interface Props {
value: SessionsValues;
onChange: (v: SessionsValues) => void;
}
/** Session scoping settings (scope + DM scope). */
export function BehaviorSessionsCard({ value, onChange }: Props) {
const { t } = useTranslation("config");
return (
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-base">{t("behavior.sessionsTitle")}</CardTitle>
<CardDescription>{t("behavior.sessionsDescription")}</CardDescription>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div className="grid gap-1.5">
<InfoLabel tip={t("sessions.scopeTip")}>{t("sessions.scope")}</InfoLabel>
<Select
value={value.scope ?? "per-sender"}
onValueChange={(v) => onChange({ ...value, scope: v })}
>
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="per-sender">Per Sender</SelectItem>
<SelectItem value="global">Global</SelectItem>
</SelectContent>
</Select>
</div>
<div className="grid gap-1.5">
<InfoLabel tip={t("sessions.dmScopeTip")}>{t("sessions.dmScope")}</InfoLabel>
<Select
value={value.dm_scope ?? "per-channel-peer"}
onValueChange={(v) => onChange({ ...value, dm_scope: v })}
>
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="main">Main</SelectItem>
<SelectItem value="per-peer">Per Peer</SelectItem>
<SelectItem value="per-channel-peer">Per Channel Peer</SelectItem>
<SelectItem value="per-account-channel-peer">Per Account Channel Peer</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</CardContent>
</Card>
);
}