Add per-DM and per-group Agent Preset overrides (4.9.1-ops.19).

Access grants can pin an agentPreset on direct members and groups; new sessions resolve override over the bot-global preset.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-09-05 18:53:43 +08:00
parent 7993499273
commit 2718586fda
12 changed files with 590 additions and 232 deletions

View file

@ -571,7 +571,7 @@ var React24 = __toESM(require("react"), 1);
// package.json
var package_default = {
name: "dsh-im-ops",
version: "4.9.1-ops.18",
version: "4.9.1-ops.19",
description: "Ops fork of dsh-im (all channels kept). Based on @xmanrui/dsh-im@4.9.1 \u2014 access/session policies owned here.",
keywords: [
"deepseek-harness",
@ -852,6 +852,9 @@ var EN = Object.freeze({
"\u673A\u5668\u4EBA\u8BBE\u7F6E\u9875\u7B7E": "Bot settings tabs",
"\u6295\u9012\u8BBE\u7F6E": "Delivery settings",
"\u8BBF\u95EE\u8BBE\u7F6E": "Access settings",
"\uFF08\u4E0D\u53EF\u7528\uFF09": " (unavailable)",
"\u7FA4 Agent Preset": "Group Agent Preset",
"\u8DDF\u968F\u5168\u5C40": "Follow global",
"\u2014": "\u2014",
"\u6765\u6E90": "Source",
"\u6682\u65E0\u7BA1\u7406\u5458\uFF0C\u53EF\u70B9\u4E0B\u65B9\u65B0\u589E\u3002": "No admins yet. Add one below.",
@ -11119,7 +11122,13 @@ var ACCESS_GRANT_VERSION = 1;
var ACCESS_PENDING_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
var ACCESS_GRANT_PHONE_MAX_LENGTH = 32;
var ACCESS_GRANT_GROUP_JID_PATTERN = /^\d{5,32}@g\.us$/;
var AGENT_PRESET_ID = /^[a-z0-9][a-z0-9-]*$/;
var CONTROL_CHARACTERS2 = /[\u0000-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/;
function normalizeOptionalAgentPresetId(value) {
if (typeof value !== "string") return null;
const id5 = value.trim();
return AGENT_PRESET_ID.test(id5) ? id5 : null;
}
function invalidGrant(message) {
const error = new TypeError(message);
error.code = "access-grant-invalid";
@ -11159,7 +11168,18 @@ function validateMember(input) {
}
const phone = validateAccessPhone(input.phone ?? input.id);
const canExecuteCommands = input.canExecuteCommands === void 0 ? true : Boolean(input.canExecuteCommands);
return Object.freeze({ phone, canExecuteCommands });
const agentPreset = optionalAgentPreset(input.agentPreset);
return Object.freeze({
phone,
canExecuteCommands,
...agentPreset ? { agentPreset } : {}
});
}
function optionalAgentPreset(value) {
if (value == null || value === "") return void 0;
const id5 = normalizeOptionalAgentPresetId(value);
if (!id5) invalidGrant("Agent Preset \u65E0\u6548\u3002");
return id5;
}
function validatePhoneList(input, { emptyMessage, label }) {
if (!Array.isArray(input)) invalidGrant(`${label}\u5FC5\u987B\u662F\u6570\u7EC4\u3002`);
@ -11178,8 +11198,10 @@ function validateGroupGrant(input) {
invalidGrant("\u7FA4\u6210\u5458\u4E0D\u80FD\u5305\u542B\u91CD\u590D\u7535\u8BDD\u3002");
}
const title = typeof input.title === "string" ? input.title.trim().slice(0, 128) : "";
const agentPreset = optionalAgentPreset(input.agentPreset);
return Object.freeze({
...title ? { title } : {},
...agentPreset ? { agentPreset } : {},
admins,
members
});
@ -12597,6 +12619,7 @@ function cloneGrant(grant, ownerPhone) {
directMembers: (base.directMembers ?? []).map((m) => ({ ...m })),
groups: Object.fromEntries(Object.entries(base.groups ?? {}).map(([jid, group]) => [jid, {
title: group.title ?? "",
...group.agentPreset ? { agentPreset: group.agentPreset } : {},
admins: [...group.admins ?? []],
members: (group.members ?? []).map((m) => ({ ...m }))
}])),
@ -12715,14 +12738,52 @@ function PhoneTypeahead({
) : null
);
}
function AgentPresetSelect({
value,
catalog,
disabled,
onChange,
ariaLabel = "Agent Preset"
}) {
const current = typeof value === "string" ? value : "";
const items = [];
const seen = /* @__PURE__ */ new Set();
for (const item of Array.isArray(catalog?.items) ? catalog.items : []) {
if (!item?.id || seen.has(item.id)) continue;
seen.add(item.id);
items.push(item);
}
if (current && !seen.has(current)) {
items.push({ id: current, label: current, unavailable: true });
}
return h2(
"select",
{
className: "dim-accessCompactSelect",
value: current,
disabled,
title: ariaLabel,
"aria-label": ariaLabel,
onChange: (event) => onChange(event.target.value || "")
},
h2("option", { value: "" }, "\u8DDF\u968F\u5168\u5C40"),
...items.map((item) => h2("option", {
key: item.id,
value: item.id
}, item.unavailable ? item.label + localizeText("\uFF08\u4E0D\u53EF\u7528\uFF09") : item.label))
);
}
function MemberRows({
title,
members,
contacts,
disabled,
onChange,
nested = false
nested = false,
showAgentPreset = false,
presetCatalog = EMPTY_AGENT_PRESET_CATALOG
}) {
const columns = showAgentPreset ? 5 : 4;
return h2(
nested ? "div" : "fieldset",
{
@ -12735,7 +12796,10 @@ function MemberRows({
{ className: "dim-accessTableWrap" },
h2(
"table",
{ className: "dim-accessTable", "data-kind": "member" },
{
className: "dim-accessTable",
"data-kind": showAgentPreset ? "member-preset" : "member"
},
h2(
"thead",
null,
@ -12745,10 +12809,14 @@ function MemberRows({
h2("th", { scope: "col" }, "\u6635\u79F0"),
h2("th", { scope: "col" }, "\u7535\u8BDD"),
h2("th", { scope: "col" }, "\u547D\u4EE4\u6743\u9650"),
showAgentPreset ? h2("th", { scope: "col" }, "Agent Preset") : null,
h2("th", { scope: "col", className: "dim-accessTableActions" }, "\u64CD\u4F5C")
)
),
h2("tbody", null, members.length === 0 ? h2("tr", null, h2("td", { colSpan: 4, className: "dim-accessTableEmpty" }, "\u6682\u65E0\u7528\u6237\uFF0C\u53EF\u70B9\u4E0B\u65B9\u65B0\u589E\u3002")) : members.map((member, index) => h2(
h2("tbody", null, members.length === 0 ? h2("tr", null, h2("td", {
colSpan: columns,
className: "dim-accessTableEmpty"
}, "\u6682\u65E0\u7528\u6237\uFF0C\u53EF\u70B9\u4E0B\u65B9\u65B0\u589E\u3002")) : members.map((member, index) => h2(
"tr",
{ key: `member-${index}` },
h2(
@ -12784,6 +12852,23 @@ function MemberRows({
h2("option", { value: "deny" }, "\u4E0D\u53EF\u6267\u884C\u547D\u4EE4")
)
),
showAgentPreset ? h2(
"td",
{ className: "dim-accessTablePreset" },
h2(AgentPresetSelect, {
value: member.agentPreset ?? "",
catalog: presetCatalog,
disabled,
ariaLabel: "Agent Preset",
onChange: (preset) => onChange(members.map((entry, i) => {
if (i !== index) return entry;
const next = { ...entry };
if (preset) next.agentPreset = preset;
else delete next.agentPreset;
return next;
}))
})
) : null,
h2(
"td",
{ className: "dim-accessTableActions" },
@ -12906,6 +12991,10 @@ function AccessGrantSettingsPage({ channel: channel4, account, rpcCall, onSaved
const [feedback, setFeedback] = React21.useState(null);
const [newGroupJid, setNewGroupJid] = React21.useState("");
const [loadingGrant, setLoadingGrant] = React21.useState(!initialGrant);
const contextCatalog = React21.useContext(AgentPresetCatalogContext);
const [presetCatalog, setPresetCatalog] = React21.useState(() => normalizeAgentPresetCatalog(
account?.agentPresetCatalog ?? contextCatalog ?? EMPTY_AGENT_PRESET_CATALOG
));
React21.useEffect(() => {
setDraft(cloneGrant(normalizeAccessGrant(account?.accessGrant), ownerPhone));
setGroupSessionScope(normalizeGroupSessionScope(account?.groupSessionScope));
@ -12918,6 +13007,9 @@ function AccessGrantSettingsPage({ channel: channel4, account, rpcCall, onSaved
try {
const value = unwrapRpcResult11(await rpcCall("connection.status", {}));
if (cancelled) return;
if (value?.agentPresetCatalog) {
setPresetCatalog(normalizeAgentPresetCatalog(value.agentPresetCatalog));
}
const bot = Array.isArray(value?.bots) ? value.bots.find((entry) => entry?.botId === account.botId) : null;
let grant = normalizeAccessGrant(bot?.accessGrant);
if (grant) {
@ -13072,6 +13164,8 @@ function AccessGrantSettingsPage({ channel: channel4, account, rpcCall, onSaved
members: draft.directMembers,
contacts,
disabled: saving,
showAgentPreset: true,
presetCatalog,
onChange: (directMembers) => {
setDraft((current) => ({ ...current, directMembers }));
setFeedback(null);
@ -13183,6 +13277,30 @@ function AccessGrantSettingsPage({ channel: channel4, account, rpcCall, onSaved
})
}, "\u540C\u6B65\u7FA4\u540D")
),
h2(
"label",
{ className: "dim-accessField" },
h2("span", null, "\u7FA4 Agent Preset"),
h2(AgentPresetSelect, {
value: group.agentPreset ?? "",
catalog: presetCatalog,
disabled: saving,
ariaLabel: "\u7FA4 Agent Preset",
onChange: (preset) => setDraft((current) => {
const existing = current.groups[groupJid] ?? { title: "", admins: [], members: [] };
const nextGroup = { ...existing };
if (preset) nextGroup.agentPreset = preset;
else delete nextGroup.agentPreset;
return {
...current,
groups: {
...current.groups,
[groupJid]: nextGroup
}
};
})
})
),
h2(AdminPhones, {
title: "\u672C\u7FA4\u7BA1\u7406\u5458",
help: "\u672A\u914D\u7F6E\u65F6\uFF0C\u672C\u7FA4\u7533\u8BF7\u56DE\u843D\u7ED9\u5168\u5C40\u7BA1\u7406\u5458\u5BA1\u6279\uFF08\u4ECD\u53EA\u6388\u4E88\u672C\u7FA4\u6743\uFF09\u3002",
@ -14799,6 +14917,12 @@ body[data-ds-dark-theme] .dim-page select option, body[data-ds-dark-theme] .dim-
.dim-accessTable[data-kind="member"] th:nth-child(2), .dim-accessTable[data-kind="member"] td:nth-child(2) { width: 36%; }
.dim-accessTable[data-kind="member"] th:nth-child(3), .dim-accessTable[data-kind="member"] td:nth-child(3) { width: 28%; }
.dim-accessTable[data-kind="member"] th:nth-child(4), .dim-accessTable[data-kind="member"] td:nth-child(4) { width: 16%; }
.dim-accessTable[data-kind="member-preset"] th:nth-child(1), .dim-accessTable[data-kind="member-preset"] td:nth-child(1) { width: 16%; }
.dim-accessTable[data-kind="member-preset"] th:nth-child(2), .dim-accessTable[data-kind="member-preset"] td:nth-child(2) { width: 26%; }
.dim-accessTable[data-kind="member-preset"] th:nth-child(3), .dim-accessTable[data-kind="member-preset"] td:nth-child(3) { width: 18%; }
.dim-accessTable[data-kind="member-preset"] th:nth-child(4), .dim-accessTable[data-kind="member-preset"] td:nth-child(4) { width: 26%; }
.dim-accessTable[data-kind="member-preset"] th:nth-child(5), .dim-accessTable[data-kind="member-preset"] td:nth-child(5) { width: 14%; }
.dim-accessTablePreset { min-width: 0; }
.dim-accessTable[data-kind="contact"] th:nth-child(1), .dim-accessTable[data-kind="contact"] td:nth-child(1) { width: 18%; }
.dim-accessTable[data-kind="contact"] th:nth-child(2), .dim-accessTable[data-kind="contact"] td:nth-child(2) { width: 26%; }
.dim-accessTable[data-kind="contact"] th:nth-child(3), .dim-accessTable[data-kind="contact"] td:nth-child(3) { width: 16%; }

File diff suppressed because one or more lines are too long