Keep IM tool approvals in chat and add WhatsApp default model.

Claim in-flight IM turns before ownership flips active so approvals no longer stick only in DSH Web, auto-recover stale session models once, and let WhatsApp bots pick a shared default model for new sessions.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-09-15 20:02:45 +08:00
parent 2723e94541
commit 74c58bdcde
21 changed files with 1388 additions and 395 deletions

View file

@ -566,7 +566,7 @@ __export(index_exports, {
name: () => name
});
module.exports = __toCommonJS(index_exports);
var React25 = __toESM(require("react"), 1);
var React26 = __toESM(require("react"), 1);
// package.json
var package_default = {
@ -11122,6 +11122,177 @@ function installWeixinStyles() {
return () => style.remove();
}
// plugin-src/client/default-model.js
var React19 = __toESM(require("react"), 1);
// src/channels/shared/default-model.mjs
function normalizeDefaultModel(value) {
if (value == null || value === "") return null;
if (typeof value === "string") {
const trimmed = value.trim();
const slash = trimmed.indexOf("/");
if (slash <= 0 || slash === trimmed.length - 1) return null;
return {
provider: trimmed.slice(0, slash),
model: trimmed.slice(slash + 1)
};
}
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
const provider = typeof value.provider === "string" ? value.provider.trim() : "";
const model = typeof value.model === "string" ? value.model.trim() : "";
if (!provider || !model) return null;
const selection = { provider, model };
if (value.reasoningEffort !== void 0) {
if (typeof value.reasoningEffort !== "string" || !value.reasoningEffort.trim()) return null;
selection.reasoningEffort = value.reasoningEffort.trim();
}
return selection;
}
function defaultModelKey(selection) {
const normalized = normalizeDefaultModel(selection);
return normalized ? `${normalized.provider}/${normalized.model}` : "";
}
function modelCatalogItems(catalog) {
const items = [];
const seen = /* @__PURE__ */ new Set();
for (const group of Array.isArray(catalog?.groups) ? catalog.groups : []) {
const provider = typeof group?.id === "string" ? group.id.trim() : "";
if (!provider || !Array.isArray(group.models)) continue;
for (const model of group.models) {
const modelId = typeof model?.id === "string" ? model.id.trim() : "";
if (!modelId) continue;
const key = `${provider}/${modelId}`;
if (seen.has(key)) continue;
seen.add(key);
const label = typeof model.name === "string" && model.name.trim() ? model.name.trim() : modelId;
items.push({
provider,
model: modelId,
key,
label: label === modelId ? key : `${label}\uFF08${key}\uFF09`
});
}
}
return items;
}
function normalizeModelCatalog(value) {
if (!value || typeof value !== "object" || Array.isArray(value)) {
return { groups: [], failures: [], items: [] };
}
const groups = Array.isArray(value.groups) ? value.groups : [];
const failures = Array.isArray(value.failures) ? value.failures : [];
return {
groups,
failures,
items: modelCatalogItems({ groups, failures })
};
}
// plugin-src/client/default-model.js
var SET_DEFAULT_MODEL_ENDPOINT = "bot.model.set";
var EMPTY_MODEL_CATALOG = Object.freeze({
groups: Object.freeze([]),
failures: Object.freeze([]),
items: Object.freeze([])
});
var ModelCatalogContext = React19.createContext(EMPTY_MODEL_CATALOG);
function normalizeDefaultModelSelection(value) {
return normalizeDefaultModel(value);
}
function normalizeHostModelCatalog(value) {
return normalizeModelCatalog(value);
}
function DefaultModelEditor({ defaultModel = null, disabled = false, onSave }) {
const catalog = React19.useContext(ModelCatalogContext) ?? EMPTY_MODEL_CATALOG;
const helpId = React19.useId();
const current = normalizeDefaultModel(defaultModel);
const currentKey = defaultModelKey(current);
const [saving, setSaving] = React19.useState(false);
const [error, setError] = React19.useState(null);
const items = [];
const seen = /* @__PURE__ */ new Set();
for (const item of Array.isArray(catalog.items) ? catalog.items : []) {
if (!item?.key || seen.has(item.key)) continue;
seen.add(item.key);
items.push(item);
}
const currentUnavailable = Boolean(currentKey && !seen.has(currentKey));
if (currentUnavailable && current) {
items.push({
key: currentKey,
provider: current.provider,
model: current.model,
label: currentKey,
unavailable: true
});
}
const change = async (event) => {
const nextKey = event.target.value;
if (nextKey === currentKey || saving || disabled) return;
setSaving(true);
setError(null);
try {
await onSave?.(nextKey ? normalizeDefaultModel(nextKey) : null);
} catch (cause) {
setError(cause?.message ?? "\u9ED8\u8BA4\u6A21\u578B\u4FEE\u6539\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5\u3002");
} finally {
setSaving(false);
}
};
return h2(
"div",
{ className: "dim-preset" },
h2(
"div",
{ className: "dim-presetHeader" },
h2(
"span",
{ className: "dim-presetTitle" },
h2("span", null, "\u9ED8\u8BA4\u6A21\u578B"),
h2(
"span",
{ className: "dim-presetHelp" },
h2("button", {
type: "button",
className: "dim-presetHelpButton",
"aria-label": "\u67E5\u770B\u9ED8\u8BA4\u6A21\u578B\u8BF4\u660E",
"aria-describedby": helpId
}, h2("span", { "aria-hidden": "true" }, "?")),
h2("span", {
id: helpId,
className: "dim-presetTooltip",
role: "tooltip"
}, "\u4E3A\u8BE5 WhatsApp \u673A\u5668\u4EBA\u7684\u65B0\u5EFA\u4F1A\u8BDD\u6307\u5B9A\u6A21\u578B\uFF0C\u907F\u514D\u6BCF\u4EBA\u5404\u81EA /model\u3002\u5DF2\u6709\u4F1A\u8BDD\u9700\u5148 /new \u518D\u53D1\u6D88\u606F\u624D\u4F1A\u751F\u6548\u3002")
)
),
saving ? h2("span", { className: "dim-presetStatus" }, "\u4FDD\u5B58\u4E2D\u2026") : null
),
React19.createElement(
"select",
{
className: "dim-presetSelect",
value: currentKey,
disabled: disabled || saving,
"aria-label": "\u9ED8\u8BA4\u6A21\u578B",
onChange: (event) => {
void change(event);
}
},
h2("option", { value: "" }, "\u8DDF\u968F Host \u9ED8\u8BA4"),
...items.map((item) => h2(
"option",
{ key: item.key, value: item.key },
item.unavailable ? [item.key, "\uFF08\u5DF2\u4E0D\u53EF\u7528\uFF09"] : item.label
))
),
error || currentUnavailable ? h2(
"p",
{ className: "dim-presetError", role: error ? "alert" : "status" },
error ?? "\u5F53\u524D\u9ED8\u8BA4\u6A21\u578B\u5DF2\u4E0D\u53EF\u7528\uFF0C\u8BF7\u9009\u62E9\u5176\u4ED6\u6A21\u578B\u6216\u8DDF\u968F Host \u9ED8\u8BA4\u3002"
) : null
);
}
// src/channels/shared/access-grant.mjs
var ACCESS_GRANT_VERSION = 1;
var ACCESS_PENDING_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
@ -11359,6 +11530,7 @@ var WHATSAPP_ENDPOINTS = Object.freeze({
setGroupSessionScope: "bot.group-session-scope.set",
setWorkspace: "bot.workspace.set",
setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
setDefaultModel: SET_DEFAULT_MODEL_ENDPOINT,
setContextEnhancement: "bot.context-enhancement.set",
resolveChannelPeer: "bot.session.channel-peer"
});
@ -11426,6 +11598,7 @@ function normalizeBot6(value) {
state: connected ? "connected" : state,
workspace: text5(value.workspace, "", 4096),
agentPreset: normalizeAgentPresetId(value.agentPreset),
defaultModel: normalizeDefaultModelSelection(value.defaultModel),
contextEnhancement: normalizeContextEnhancementConfig(value.contextEnhancement),
...Object.hasOwn(value, "accessPolicy") ? { accessPolicy: normalizeAccessPolicy(value.accessPolicy) } : {},
...Object.hasOwn(value, "accessGrant") ? { accessGrant: normalizeAccessGrant(value.accessGrant) } : {},
@ -11456,7 +11629,8 @@ function normalizeSnapshot8(value) {
bots,
totals: { configured: bots.length, connected: bots.filter((bot) => bot.connected).length },
provisioning: source.provisioning ? normalizeProvisioning6(source.provisioning) : null,
agentPresetCatalog: normalizeAgentPresetCatalog(source.agentPresetCatalog)
agentPresetCatalog: normalizeAgentPresetCatalog(source.agentPresetCatalog),
modelCatalog: normalizeHostModelCatalog(source.modelCatalog)
};
}
function presentError9(error) {
@ -11471,7 +11645,7 @@ function formatRemaining6(milliseconds) {
}
// plugin-src/client/channels/whatsapp/index.js
var React19 = __toESM(require("react"), 1);
var React20 = __toESM(require("react"), 1);
// plugin-src/client/channels/whatsapp/styles.js
var WHATSAPP_STYLE_ID = "xmanrui-dsh-im-whatsapp-settings";
@ -11496,7 +11670,7 @@ function installWhatsappStyles() {
// plugin-src/client/channels/whatsapp/index.js
var ACTIVE_STATES3 = /* @__PURE__ */ new Set(["pending", "connecting"]);
var Button14 = React19.forwardRef(function Button15({ children, kind = "secondary", className = "", ...props }, ref) {
var Button14 = React20.forwardRef(function Button15({ children, kind = "secondary", className = "", ...props }, ref) {
return h2("button", {
...props,
ref,
@ -11719,6 +11893,7 @@ function WhatsappAccountCard({
onReconnect,
onWorkspaceSave,
onAgentPresetSave,
onDefaultModelSave,
onContextEnhancementSave,
onRequestRemove,
onConfirmRemove,
@ -11783,6 +11958,11 @@ function WhatsappAccountCard({
disabled: Boolean(busy),
onSave: onAgentPresetSave
}),
h2(DefaultModelEditor, {
defaultModel: account.defaultModel,
disabled: Boolean(busy),
onSave: onDefaultModelSave
}),
h2(ContextEnhancementEditor, {
config: account.contextEnhancement,
disabled: Boolean(busy),
@ -11830,23 +12010,24 @@ function WhatsappAccountCard({
);
}
function WhatsappSettingsTab({ rpcCall }) {
const [model, setModel] = React19.useState({
const [model, setModel] = React20.useState({
phase: "loading",
bots: [],
totals: { configured: 0, connected: 0 },
error: null,
agentPresetCatalog: EMPTY_AGENT_PRESET_CATALOG
agentPresetCatalog: EMPTY_AGENT_PRESET_CATALOG,
modelCatalog: EMPTY_MODEL_CATALOG
});
const [provision, setProvision] = React19.useState(null);
const [busy, setBusy] = React19.useState(false);
const [busyByBot, setBusyByBot] = React19.useState({});
const [testNoticeByBot, setTestNoticeByBot] = React19.useState({});
const [removeTarget, setRemoveTarget] = React19.useState(null);
const [now, setNow] = React19.useState(Date.now());
const mounted = React19.useRef(true);
const [provision, setProvision] = React20.useState(null);
const [busy, setBusy] = React20.useState(false);
const [busyByBot, setBusyByBot] = React20.useState({});
const [testNoticeByBot, setTestNoticeByBot] = React20.useState({});
const [removeTarget, setRemoveTarget] = React20.useState(null);
const [now, setNow] = React20.useState(Date.now());
const mounted = React20.useRef(true);
const workspaceFence = useWorkspaceSnapshotFence();
const addButtonRef = React19.useRef(null);
React19.useEffect(() => {
const addButtonRef = React20.useRef(null);
React20.useEffect(() => {
const disposeDingtalk = installDingtalkStyles();
const disposeWhatsapp = installWhatsappStyles();
mounted.current = true;
@ -11856,11 +12037,11 @@ function WhatsappSettingsTab({ rpcCall }) {
disposeDingtalk();
};
}, []);
const invoke = React19.useCallback(async (endpoint, payload = {}, signal) => {
const invoke = React20.useCallback(async (endpoint, payload = {}, signal) => {
if (typeof rpcCall !== "function") throw new TypeError("WhatsApp \u8BBE\u7F6E\u9875\u7F3A\u5C11 RPC \u8FDE\u63A5");
return unwrapRpcResult9(await rpcCall(endpoint, payload, signal));
}, [rpcCall]);
const loadStatus = React19.useCallback(async ({ signal, silent = false, restore = false } = {}) => {
const loadStatus = React20.useCallback(async ({ signal, silent = false, restore = false } = {}) => {
const workspaceVersion = workspaceFence.beginStatus();
if (workspaceVersion === null) return void 0;
if (!silent && mounted.current) setModel((current) => ({ ...current, phase: "loading", error: null }));
@ -11872,7 +12053,8 @@ function WhatsappSettingsTab({ rpcCall }) {
bots: snapshot.bots,
totals: snapshot.totals,
error: null,
agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG
agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
modelCatalog: snapshot.modelCatalog ?? EMPTY_MODEL_CATALOG
});
if (restore && snapshot.provisioning) setProvision({
...snapshot.provisioning,
@ -11890,12 +12072,12 @@ function WhatsappSettingsTab({ rpcCall }) {
return void 0;
}
}, [invoke, workspaceFence]);
React19.useEffect(() => {
React20.useEffect(() => {
const controller = new AbortController();
void loadStatus({ signal: controller.signal, restore: true });
return () => controller.abort();
}, [loadStatus]);
React19.useEffect(() => {
React20.useEffect(() => {
if (model.phase !== "ready") return void 0;
const controller = new AbortController();
const timer = window.setInterval(
@ -11907,12 +12089,12 @@ function WhatsappSettingsTab({ rpcCall }) {
window.clearInterval(timer);
};
}, [loadStatus, model.phase]);
React19.useEffect(() => {
React20.useEffect(() => {
if (!provision || !ACTIVE_STATES3.has(provision.status)) return void 0;
const timer = window.setInterval(() => mounted.current && setNow(Date.now()), 1e3);
return () => window.clearInterval(timer);
}, [provision?.attemptId, provision?.status]);
const startProvisioning = React19.useCallback(async (replace = false) => {
const startProvisioning = React20.useCallback(async (replace = false) => {
setBusy(true);
try {
if (replace && provision?.attemptId) {
@ -11930,7 +12112,7 @@ function WhatsappSettingsTab({ rpcCall }) {
if (mounted.current) setBusy(false);
}
}, [invoke, provision?.attemptId]);
const closeProvision = React19.useCallback(async () => {
const closeProvision = React20.useCallback(async () => {
setBusy(true);
try {
if (provision?.attemptId && ACTIVE_STATES3.has(provision.status)) {
@ -11941,7 +12123,7 @@ function WhatsappSettingsTab({ rpcCall }) {
if (mounted.current) setBusy(false);
}
}, [invoke, provision?.attemptId, provision?.status]);
React19.useEffect(() => {
React20.useEffect(() => {
const attemptId = provision?.attemptId;
if (!attemptId || !ACTIVE_STATES3.has(provision.status)) return void 0;
const controller = new AbortController();
@ -11982,7 +12164,7 @@ function WhatsappSettingsTab({ rpcCall }) {
if (timer) window.clearTimeout(timer);
};
}, [invoke, loadStatus, provision?.attemptId, provision?.status]);
const botAction = React19.useCallback(async (account, operation, endpoint, payload) => {
const botAction = React20.useCallback(async (account, operation, endpoint, payload) => {
const snapshotVersion = workspaceFence.beginMutation();
setBusyByBot((current) => ({ ...current, [account.botId]: operation }));
if (operation === "reconnect") {
@ -12001,7 +12183,8 @@ function WhatsappSettingsTab({ rpcCall }) {
bots: snapshot.bots,
totals: snapshot.totals,
error: null,
agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG
agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
modelCatalog: snapshot.modelCatalog ?? EMPTY_MODEL_CATALOG
});
if (operation === "reconnect") {
setTestNoticeByBot((current) => ({
@ -12059,6 +12242,12 @@ function WhatsappSettingsTab({ rpcCall }) {
WHATSAPP_ENDPOINTS.setAgentPreset,
{ botId: account.botId, agentPreset }
),
onDefaultModelSave: (defaultModel) => botAction(
account,
"model",
WHATSAPP_ENDPOINTS.setDefaultModel,
{ botId: account.botId, defaultModel }
),
onContextEnhancementSave: (config) => botAction(
account,
"context-enhancement",
@ -12078,6 +12267,8 @@ function WhatsappSettingsTab({ rpcCall }) {
) : null;
return h2(AgentPresetCatalogContext.Provider, {
value: model.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG
}, h2(ModelCatalogContext.Provider, {
value: model.modelCatalog ?? EMPTY_MODEL_CATALOG
}, h2(
"section",
{
@ -12101,7 +12292,7 @@ function WhatsappSettingsTab({ rpcCall }) {
h2(Button14, { onClick: () => void loadStatus() }, "\u91CD\u65B0\u8BFB\u53D6")
)
) : h2(
React19.Fragment,
React20.Fragment,
null,
provision?.status === "pending" ? h2(QrPanel5, {
provision,
@ -12117,11 +12308,11 @@ function WhatsappSettingsTab({ rpcCall }) {
}) : model.bots.length === 0 ? h2(EmptyView6, { busy, onStart: () => void startProvisioning(false) }) : null,
botList
)
));
)));
}
// plugin-src/client/channel-peer-label.js
var React20 = __toESM(require("react"), 1);
var React21 = __toESM(require("react"), 1);
var CHANNEL_LABELS = Object.freeze({
whatsapp: "WhatsApp",
feishu: "\u98DE\u4E66",
@ -12135,8 +12326,8 @@ var CHANNEL_LABELS = Object.freeze({
office: "AI Office"
});
function ChannelPeerLabel({ sessionId, resolveChannelPeer }) {
const [peer, setPeer] = React20.useState(null);
React20.useEffect(() => {
const [peer, setPeer] = React21.useState(null);
React21.useEffect(() => {
if (typeof resolveChannelPeer !== "function" || typeof sessionId !== "string" || !sessionId) {
setPeer(null);
return void 0;
@ -12193,10 +12384,10 @@ async function resolveWhatsappChannelPeer(whatsappRpcCall, sessionId, signal) {
}
// plugin-src/client/delivery-settings.js
var React23 = __toESM(require("react"), 1);
var React24 = __toESM(require("react"), 1);
// plugin-src/client/access-policy-settings.js
var React21 = __toESM(require("react"), 1);
var React22 = __toESM(require("react"), 1);
var ACCESS_POLICY_ENDPOINT = "bot.access-policy.set";
var GROUP_SESSION_SCOPE_ENDPOINT = "bot.group-session-scope.set";
var ACCESS_CHANNEL_DEFINITIONS = Object.freeze({
@ -12299,8 +12490,8 @@ function ScenePolicyEditor({
unsupported = false,
onChange
}) {
const ownerHelpId = React21.useId();
const emptyAllowlistHelpId = React21.useId();
const ownerHelpId = React22.useId();
const emptyAllowlistHelpId = React22.useId();
const allowlist = policy.mode === "allowlist";
const collectionKey = allowlist ? "users" : "commandPermissionOverrides";
const branchKey = allowlist ? "allowlist" : "open";
@ -12352,7 +12543,7 @@ function ScenePolicyEditor({
h2("strong", null, "\u5F53\u524D\u6E20\u9053\u4E0D\u652F\u6301\u7FA4\u804A"),
h2("p", null, "\u6B64\u533A\u57DF\u65E0\u9700\u914D\u7F6E\uFF0C\u4FDD\u5B58\u79C1\u804A\u8BBE\u7F6E\u65F6\u4F1A\u4FDD\u7559\u73B0\u6709\u7FA4\u804A\u7B56\u7565\u3002")
) : h2(
React21.Fragment,
React22.Fragment,
null,
h2(
"div",
@ -12494,18 +12685,18 @@ function AccessPolicySettingsPage({ channel: channel4, account, rpcCall, onSaved
const initialPolicy = normalizeAccessPolicy(account?.accessPolicy);
const initialKey = JSON.stringify(initialPolicy);
const initialScope = normalizeGroupSessionScope(account?.groupSessionScope);
const [draft, setDraft] = React21.useState(() => clonePolicy(
const [draft, setDraft] = React22.useState(() => clonePolicy(
initialPolicy ?? DEFAULT_ACCESS_POLICY
));
const [groupSessionScope, setGroupSessionScope] = React21.useState(initialScope);
const [saving, setSaving] = React21.useState(false);
const [feedback, setFeedback] = React21.useState(null);
React21.useEffect(() => {
const [groupSessionScope, setGroupSessionScope] = React22.useState(initialScope);
const [saving, setSaving] = React22.useState(false);
const [feedback, setFeedback] = React22.useState(null);
React22.useEffect(() => {
const next = normalizeAccessPolicy(account?.accessPolicy);
setDraft(clonePolicy(next ?? DEFAULT_ACCESS_POLICY));
setGroupSessionScope(normalizeGroupSessionScope(account?.groupSessionScope));
}, [account?.botId, initialKey, account?.groupSessionScope]);
React21.useEffect(() => {
React22.useEffect(() => {
setFeedback(null);
}, [account?.botId]);
if (!definition) {
@ -12658,7 +12849,7 @@ function AccessPolicySettingsPage({ channel: channel4, account, rpcCall, onSaved
}
// plugin-src/client/access-grant-settings.js
var React22 = __toESM(require("react"), 1);
var React23 = __toESM(require("react"), 1);
var ACCESS_GRANT_ENDPOINT = "bot.access-grant.set";
var ACCESS_PENDING_RESOLVE_ENDPOINT = "bot.access-pending.resolve";
var ACCESS_GROUP_TITLES_REFRESH_ENDPOINT = "bot.access-group-titles.refresh";
@ -12750,13 +12941,13 @@ function PhoneTypeahead({
disabled,
requirePhone = true
}) {
const [query, setQuery] = React22.useState(value || "");
const [open, setOpen] = React22.useState(false);
const blurTimer = React22.useRef(null);
React22.useEffect(() => {
const [query, setQuery] = React23.useState(value || "");
const [open, setOpen] = React23.useState(false);
const blurTimer = React23.useRef(null);
React23.useEffect(() => {
setQuery(value || "");
}, [value]);
React22.useEffect(() => () => {
React23.useEffect(() => () => {
if (blurTimer.current) window.clearTimeout(blurTimer.current);
}, []);
const q = query.trim().toLowerCase();
@ -13064,21 +13255,21 @@ function AccessGrantSettingsPage({ channel: channel4, account, rpcCall, onSaved
const initialGrant = normalizeAccessGrant(account?.accessGrant);
const initialKey = JSON.stringify(initialGrant);
const initialScope = normalizeGroupSessionScope(account?.groupSessionScope);
const [draft, setDraft] = React22.useState(() => cloneGrant(initialGrant, ownerPhone));
const [groupSessionScope, setGroupSessionScope] = React22.useState(initialScope);
const [saving, setSaving] = React22.useState(false);
const [feedback, setFeedback] = React22.useState(null);
const [newGroupJid, setNewGroupJid] = React22.useState("");
const [loadingGrant, setLoadingGrant] = React22.useState(!initialGrant);
const contextCatalog = React22.useContext(AgentPresetCatalogContext);
const [presetCatalog, setPresetCatalog] = React22.useState(() => normalizeAgentPresetCatalog(
const [draft, setDraft] = React23.useState(() => cloneGrant(initialGrant, ownerPhone));
const [groupSessionScope, setGroupSessionScope] = React23.useState(initialScope);
const [saving, setSaving] = React23.useState(false);
const [feedback, setFeedback] = React23.useState(null);
const [newGroupJid, setNewGroupJid] = React23.useState("");
const [loadingGrant, setLoadingGrant] = React23.useState(!initialGrant);
const contextCatalog = React23.useContext(AgentPresetCatalogContext);
const [presetCatalog, setPresetCatalog] = React23.useState(() => normalizeAgentPresetCatalog(
account?.agentPresetCatalog ?? contextCatalog ?? EMPTY_AGENT_PRESET_CATALOG
));
React22.useEffect(() => {
React23.useEffect(() => {
setDraft(cloneGrant(normalizeAccessGrant(account?.accessGrant), ownerPhone));
setGroupSessionScope(normalizeGroupSessionScope(account?.groupSessionScope));
}, [account?.botId, initialKey, account?.groupSessionScope, ownerPhone]);
React22.useEffect(() => {
React23.useEffect(() => {
if (typeof rpcCall !== "function" || !account?.botId) return void 0;
let cancelled = false;
setLoadingGrant(true);
@ -13132,7 +13323,7 @@ function AccessGrantSettingsPage({ channel: channel4, account, rpcCall, onSaved
};
}, [account?.botId, rpcCall, ownerPhone]);
const contacts = draft.contacts ?? [];
const knownGroupJids = React22.useMemo(() => {
const knownGroupJids = React23.useMemo(() => {
const set = new Set(Object.keys(draft.groups ?? {}));
for (const contact of contacts) {
for (const jid of contact.groupJids ?? []) set.add(jid);
@ -13142,7 +13333,7 @@ function AccessGrantSettingsPage({ channel: channel4, account, rpcCall, onSaved
}
return [...set].sort();
}, [draft.groups, draft.pending, contacts]);
const actionableContacts = React22.useMemo(
const actionableContacts = React23.useMemo(
() => contacts.filter((contact) => contactNeedsGrantAction(contact, draft, knownGroupJids)),
[contacts, draft, knownGroupJids]
);
@ -13821,13 +14012,13 @@ function TargetForm({
}) {
const editing = mode === "edit";
const initialKind = initialValue?.kind && definition.fields[initialValue.kind] ? initialValue.kind : definition.kinds[0].value;
const [targetId, setTargetId] = React23.useState(initialValue?.targetId ?? "");
const [name2, setName] = React23.useState(initialValue?.name ?? "");
const [kind, setKind] = React23.useState(initialKind);
const [route, setRoute] = React23.useState(initialValue?.route ?? {});
const [error, setError] = React23.useState(null);
const [testing, setTesting] = React23.useState(false);
const [testState, setTestState] = React23.useState(null);
const [targetId, setTargetId] = React24.useState(initialValue?.targetId ?? "");
const [name2, setName] = React24.useState(initialValue?.name ?? "");
const [kind, setKind] = React24.useState(initialKind);
const [route, setRoute] = React24.useState(initialValue?.route ?? {});
const [error, setError] = React24.useState(null);
const [testing, setTesting] = React24.useState(false);
const [testState, setTestState] = React24.useState(null);
const currentTarget = () => {
const normalizedRoute = Object.fromEntries(fieldsFor(definition, kind).map((field) => {
const raw = String(route[field.key] ?? "").trim();
@ -14042,7 +14233,7 @@ function TargetSuggestionPicker({
}
},
// Platform display names must stay raw (bypass h() i18n phrase rewrite).
React23.createElement(
React24.createElement(
"strong",
null,
suggestionDisplayName(suggestion) || suggestionFallbackName(definition, suggestion)
@ -14051,7 +14242,7 @@ function TargetSuggestionPicker({
localizeText(kindLabel(definition, suggestion.kind)),
added ? localizeText("\u5DF2\u6DFB\u52A0") : null
].filter(Boolean).join(" \xB7 ")),
React23.createElement("code", null, fieldsFor(definition, suggestion.kind).map((field) => maskRouteValue(suggestion.route?.[field.key])).filter(Boolean).join(" \xB7 "))
React24.createElement("code", null, fieldsFor(definition, suggestion.kind).map((field) => maskRouteValue(suggestion.route?.[field.key])).filter(Boolean).join(" \xB7 "))
)
);
})
@ -14065,10 +14256,10 @@ function TargetSuggestionPicker({
);
}
function TargetRow({ definition, target, botId, connected, rpcCall, onChanged, onEdit }) {
const [action, setAction] = React23.useState(null);
const [testState, setTestState] = React23.useState(null);
const [copyState, setCopyState] = React23.useState(null);
const [confirmDelete, setConfirmDelete] = React23.useState(false);
const [action, setAction] = React24.useState(null);
const [testState, setTestState] = React24.useState(null);
const [copyState, setCopyState] = React24.useState(null);
const [confirmDelete, setConfirmDelete] = React24.useState(false);
const testTarget = async () => {
setAction("test");
setTestState(null);
@ -14110,7 +14301,7 @@ function TargetRow({ definition, target, botId, connected, rpcCall, onChanged, o
h2(
"div",
{ className: "dim-targetTitle" },
React23.createElement("strong", null, target.name || target.targetId),
React24.createElement("strong", null, target.name || target.targetId),
h2("span", null, kindLabel(definition, target.kind))
),
h2("code", null, `targetId: ${target.targetId}`)
@ -14173,28 +14364,28 @@ function DeliveryTargetSettingsPage({
onBack
}) {
const definition = CHANNEL_DEFINITIONS[channel4];
const [activeTabId, setActiveTabId] = React23.useState(BOT_SETTINGS_TABS[0].id);
const [phase, setPhase] = React23.useState("loading");
const [targets, setTargets] = React23.useState([]);
const [suggestionPhase, setSuggestionPhase] = React23.useState("idle");
const [suggestions, setSuggestions] = React23.useState([]);
const [suggestionError, setSuggestionError] = React23.useState(null);
const [error, setError] = React23.useState(null);
const [editor, setEditor] = React23.useState(null);
const [saving, setSaving] = React23.useState(false);
const [botCopyState, setBotCopyState] = React23.useState(null);
const [accessPolicy, setAccessPolicy] = React23.useState(account.accessPolicy);
const [accessGrant, setAccessGrant] = React23.useState(account.accessGrant);
const mounted = React23.useRef(true);
React23.useEffect(() => {
const [activeTabId, setActiveTabId] = React24.useState(BOT_SETTINGS_TABS[0].id);
const [phase, setPhase] = React24.useState("loading");
const [targets, setTargets] = React24.useState([]);
const [suggestionPhase, setSuggestionPhase] = React24.useState("idle");
const [suggestions, setSuggestions] = React24.useState([]);
const [suggestionError, setSuggestionError] = React24.useState(null);
const [error, setError] = React24.useState(null);
const [editor, setEditor] = React24.useState(null);
const [saving, setSaving] = React24.useState(false);
const [botCopyState, setBotCopyState] = React24.useState(null);
const [accessPolicy, setAccessPolicy] = React24.useState(account.accessPolicy);
const [accessGrant, setAccessGrant] = React24.useState(account.accessGrant);
const mounted = React24.useRef(true);
React24.useEffect(() => {
setAccessPolicy(account.accessPolicy);
setAccessGrant(account.accessGrant);
}, [account.botId, account.accessPolicy, account.accessGrant]);
const invoke = React23.useCallback(async (endpoint, payload = {}, signal) => {
const invoke = React24.useCallback(async (endpoint, payload = {}, signal) => {
if (typeof rpcCall !== "function") throw new Error("\u6295\u9012\u76EE\u6807\u8BBE\u7F6E\u6682\u4E0D\u53EF\u7528\u3002");
return unwrapRpcResult12(await rpcCall(endpoint, payload, signal));
}, [rpcCall]);
const loadTargets = React23.useCallback(async ({ signal, silent = false } = {}) => {
const loadTargets = React24.useCallback(async ({ signal, silent = false } = {}) => {
if (!silent) setPhase("loading");
setError(null);
try {
@ -14208,7 +14399,7 @@ function DeliveryTargetSettingsPage({
setPhase("error");
}
}, [account.botId, invoke]);
const loadSuggestions = React23.useCallback(async () => {
const loadSuggestions = React24.useCallback(async () => {
setSuggestionPhase("loading");
setSuggestionError(null);
try {
@ -14222,7 +14413,7 @@ function DeliveryTargetSettingsPage({
setSuggestionPhase("error");
}
}, [account.botId, definition, invoke]);
React23.useEffect(() => {
React24.useEffect(() => {
mounted.current = true;
const controller = new AbortController();
void loadTargets({ signal: controller.signal });
@ -14349,7 +14540,7 @@ function DeliveryTargetSettingsPage({
rpcCall: accessRpcCall,
onSaved: setAccessPolicy
}) : h2(
React23.Fragment,
React24.Fragment,
null,
h2(
"section",
@ -15170,7 +15361,7 @@ function installImStyles() {
}
// plugin-src/client/update-panel.js
var React24 = __toESM(require("react"), 1);
var React25 = __toESM(require("react"), 1);
var import_react_dom3 = require("react-dom");
var import_valid = __toESM(require_valid(), 1);
var import_rcompare = __toESM(require_rcompare(), 1);
@ -15260,11 +15451,11 @@ function manualUpdateCommand(snapshot) {
return `dsh plugin --profile ${profileArgument} add -w dsh-im-ops@${version}`;
}
function ManualUpdateCommand({ command, disabled, sourceInstall, desktop }) {
const [copyState, setCopyState] = React24.useState("idle");
const commandRef = React24.useRef(null);
const mounted = React24.useRef(false);
const copying = React24.useRef(false);
React24.useEffect(() => {
const [copyState, setCopyState] = React25.useState("idle");
const commandRef = React25.useRef(null);
const mounted = React25.useRef(false);
const copying = React25.useRef(false);
React25.useEffect(() => {
mounted.current = true;
return () => {
mounted.current = false;
@ -15296,7 +15487,7 @@ function ManualUpdateCommand({ command, disabled, sourceInstall, desktop }) {
{ className: "dim-updateManual", "aria-label": "\u624B\u5DE5\u66F4\u65B0" },
h2("h4", { className: "dim-updateManualHeading" }, "\u624B\u5DE5\u66F4\u65B0"),
command ? h2(
React24.Fragment,
React25.Fragment,
null,
h2("p", { className: "dim-updateManualHint" }, "\u81EA\u52A8\u66F4\u65B0\u5931\u8D25\u53EF\u4EE5\u4F7F\u7528\u547D\u4EE4\u66F4\u65B0\uFF1A"),
h2(
@ -15332,7 +15523,7 @@ function ManualUpdateCommand({ command, disabled, sourceInstall, desktop }) {
"aria-hidden": "true",
focusable: "false"
}, copyState === "copied" ? h2("path", { d: "m4 10 4 4 8-8" }) : h2(
React24.Fragment,
React25.Fragment,
null,
h2("rect", { x: 7, y: 7, width: 10, height: 11, rx: 1.5 }),
h2("path", { d: "M5 13H3.5A1.5 1.5 0 0 1 2 11.5v-8A1.5 1.5 0 0 1 3.5 2h8A1.5 1.5 0 0 1 13 3.5V5" })
@ -15357,10 +15548,10 @@ function ManualUpdateCommand({ command, disabled, sourceInstall, desktop }) {
);
}
function UpdateDialog({ children, onClose }) {
const dialogRef = React24.useRef(null);
const titleId = React24.useId();
const descriptionId = React24.useId();
React24.useEffect(() => {
const dialogRef = React25.useRef(null);
const titleId = React25.useId();
const descriptionId = React25.useId();
React25.useEffect(() => {
const previous = globalThis.document?.activeElement;
dialogRef.current?.focus?.();
return () => {
@ -15418,19 +15609,19 @@ function UpdateDialog({ children, onClose }) {
return typeof document !== "undefined" && document.body ? (0, import_react_dom3.createPortal)(content, document.body) : content;
}
function UpdatePanel({ rpcCall, clientVersion, onStatus }) {
const [snapshot, setSnapshot] = React24.useState(null);
const [action, setAction] = React24.useState("status");
const [error, setError] = React24.useState(null);
const [open, setOpen] = React24.useState(false);
const [uncertainInstall, setUncertainInstall] = React24.useState(false);
const mounted = React24.useRef(false);
const busy = React24.useRef(false);
const readController = React24.useRef(null);
const pollReadController = React24.useRef(null);
const installRequest = React24.useRef(null);
const onStatusRef = React24.useRef(onStatus);
const [snapshot, setSnapshot] = React25.useState(null);
const [action, setAction] = React25.useState("status");
const [error, setError] = React25.useState(null);
const [open, setOpen] = React25.useState(false);
const [uncertainInstall, setUncertainInstall] = React25.useState(false);
const mounted = React25.useRef(false);
const busy = React25.useRef(false);
const readController = React25.useRef(null);
const pollReadController = React25.useRef(null);
const installRequest = React25.useRef(null);
const onStatusRef = React25.useRef(onStatus);
onStatusRef.current = onStatus;
const accept = React24.useCallback((next) => {
const accept = React25.useCallback((next) => {
setSnapshot(next);
onStatusRef.current?.(next);
}, []);
@ -15439,7 +15630,7 @@ function UpdatePanel({ rpcCall, clientVersion, onStatus }) {
setUncertainInstall(false);
accept(next);
};
const invoke = React24.useCallback(async (endpoint, payload = {}, signal) => {
const invoke = React25.useCallback(async (endpoint, payload = {}, signal) => {
if (typeof rpcCall !== "function") {
const unavailable = new Error("\u5F53\u524D Host \u4E0D\u652F\u6301\u66F4\u65B0\u63A5\u53E3\uFF0C\u8BF7\u5148\u624B\u52A8\u66F4\u65B0\u63D2\u4EF6\u5E76\u91CD\u542F\u3002");
unavailable.code = "update-unavailable";
@ -15447,7 +15638,7 @@ function UpdatePanel({ rpcCall, clientVersion, onStatus }) {
}
return unwrapSnapshot(await rpcCall(endpoint, payload, signal));
}, [rpcCall]);
React24.useEffect(() => {
React25.useEffect(() => {
mounted.current = true;
busy.current = true;
const controller = new AbortController();
@ -15470,7 +15661,7 @@ function UpdatePanel({ rpcCall, clientVersion, onStatus }) {
const activeJob = ACTIVE_STATES4.has(snapshot?.job?.state);
const restartRequired = snapshot?.job?.state === "restart-required" || snapshot?.blockedReason === "pending-restart";
const shouldPoll = activeJob || uncertainInstall;
React24.useEffect(() => {
React25.useEffect(() => {
if (!shouldPoll) return void 0;
let controller;
const scheduler = createPollScheduler({
@ -15568,7 +15759,7 @@ function UpdatePanel({ rpcCall, clientVersion, onStatus }) {
const manualCommand = manualUpdateCommand(snapshot);
const buttonLabel = action === "checking" ? "\u68C0\u67E5\u4E2D\u2026" : action === "starting" || activeJob ? "\u6B63\u5728\u66F4\u65B0\u2026" : restartRequired ? "\u5F85\u624B\u52A8\u91CD\u542F" : snapshot?.canInstall ? "\u66F4\u65B0\u81F3" : "\u68C0\u67E5\u66F4\u65B0";
return h2(
React24.Fragment,
React25.Fragment,
null,
h2("button", {
type: "button",
@ -15593,13 +15784,13 @@ function UpdatePanel({ rpcCall, clientVersion, onStatus }) {
h2("dt", null, "\u8FD0\u884C\u7248\u672C"),
h2("dd", null, `v${snapshot?.runningVersion ?? clientVersion}`),
snapshot?.installedVersion && snapshot.installedVersion !== snapshot.runningVersion ? h2(
React24.Fragment,
React25.Fragment,
null,
h2("dt", null, "\u5DF2\u5B89\u88C5\u7248\u672C"),
h2("dd", null, `v${snapshot.installedVersion}`)
) : null,
targetVersion ? h2(
React24.Fragment,
React25.Fragment,
null,
h2("dt", null, "\u76EE\u6807\u7248\u672C"),
h2("dd", null, `v${targetVersion}`)
@ -15812,20 +16003,20 @@ function IMSettingsTab({
browserLocation = globalThis.location,
navigateToRecoveryUrl = replacePageLocation
}) {
const [selected, setSelected] = React25.useState("weixin");
const [loopbackRecovery, setLoopbackRecovery] = React25.useState(null);
const [runningVersion, setRunningVersion] = React25.useState(IM_PLUGIN_VERSION);
const [deliverySettings, setDeliverySettings] = React25.useState(null);
const githubTooltipId = React25.useId();
const channelSelectId = React25.useId();
const [selected, setSelected] = React26.useState("weixin");
const [loopbackRecovery, setLoopbackRecovery] = React26.useState(null);
const [runningVersion, setRunningVersion] = React26.useState(IM_PLUGIN_VERSION);
const [deliverySettings, setDeliverySettings] = React26.useState(null);
const githubTooltipId = React26.useId();
const channelSelectId = React26.useId();
const active = CHANNELS.find((channel4) => channel4.id === selected) ?? CHANNELS[0];
const reportLoopbackRecovery = React25.useCallback((recovery) => {
const reportLoopbackRecovery = React26.useCallback((recovery) => {
setLoopbackRecovery((current) => current?.url === recovery.url ? current : recovery);
}, []);
const reportUpdateStatus = React25.useCallback((snapshot) => {
const reportUpdateStatus = React26.useCallback((snapshot) => {
setRunningVersion(snapshot.runningVersion);
}, []);
const rpcCalls = React25.useMemo(() => createLoopbackAwareRpcCalls({
const rpcCalls = React26.useMemo(() => createLoopbackAwareRpcCalls({
dingtalkRpcCall,
discordRpcCall,
feishuRpcCall,
@ -15857,7 +16048,7 @@ function IMSettingsTab({
weixinRpcCall,
whatsappRpcCall
]);
const botSettingsContext = React25.useMemo(() => Object.freeze({
const botSettingsContext = React26.useMemo(() => Object.freeze({
openBotSettings: setDeliverySettings
}), []);
return h2(