feat(wechat): add intent router and Cursor executor admin controls

Expose WeChat routing policy and Cursor experience channel settings in md.tkmind.cn with matching admin-api routes.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-08-26 21:56:15 +08:00
parent 80c2091baf
commit 70433a3dcd
7 changed files with 626 additions and 0 deletions
+58
View File
@@ -108,6 +108,8 @@ export function createAdminApp(services) {
skillRuntimeConfigService,
billingConfigService,
wechatScheduleLlmConfigService,
wechatIntentRouterConfigService,
wechatCursorExecutorPolicyService,
adminSystemTestService,
systemTestAccountService,
wordFilterService,
@@ -395,6 +397,62 @@ export function createAdminApp(services) {
res.json(result);
});
adminApi.get('/wechat/intent-router/config', requireAdmin, async (_req, res) => {
if (!wechatIntentRouterConfigService?.getConfig) {
return res.status(503).json({ message: '微信意图路由配置未启用' });
}
const config = await wechatIntentRouterConfigService.getConfig();
return res.json({ config });
});
adminApi.get('/wechat/intent-router/runtime', requireAdmin, async (_req, res) => {
if (!wechatIntentRouterConfigService?.getRuntimeState) {
return res.status(503).json({ message: '微信意图路由配置未启用' });
}
return res.json(await wechatIntentRouterConfigService.getRuntimeState());
});
const updateWechatIntentRouterConfig = async (req, res) => {
if (!wechatIntentRouterConfigService?.updateConfig) {
return res.status(503).json({ message: '微信意图路由配置未启用' });
}
const config = await wechatIntentRouterConfigService.updateConfig(req.body ?? {}, {
updatedBy: req.currentUser.id,
});
return res.json({ config });
};
adminApi.put('/wechat/intent-router/config', requireAdmin, updateWechatIntentRouterConfig);
adminApi.patch('/wechat/intent-router/config', requireAdmin, updateWechatIntentRouterConfig);
adminApi.get('/wechat/cursor-executor/config', requireAdmin, async (_req, res) => {
if (!wechatCursorExecutorPolicyService?.getAdminConfig) {
return res.status(503).json({ message: '微信 Cursor 体验通道未启用' });
}
return res.json(await wechatCursorExecutorPolicyService.getAdminConfig());
});
adminApi.get('/wechat/cursor-executor/runtime', requireAdmin, async (_req, res) => {
if (!wechatCursorExecutorPolicyService?.getRuntimeState) {
return res.status(503).json({ message: '微信 Cursor 体验通道未启用' });
}
return res.json(await wechatCursorExecutorPolicyService.getRuntimeState());
});
const updateWechatCursorExecutorConfig = async (req, res) => {
if (!wechatCursorExecutorPolicyService?.updateAdminConfig) {
return res.status(503).json({ message: '微信 Cursor 体验通道未启用' });
}
const result = await wechatCursorExecutorPolicyService.updateAdminConfig(
req.body?.config ?? req.body ?? {},
{ updatedBy: req.currentUser.id },
);
return res.json(result);
};
adminApi.put('/wechat/cursor-executor/config', requireAdmin, updateWechatCursorExecutorConfig);
adminApi.patch('/wechat/cursor-executor/config', requireAdmin, updateWechatCursorExecutorConfig);
adminApi.get('/mindspace/config', requireAdmin, async (_req, res) => {
if (!loadMindSpaceConfig) return res.status(503).json({ message: 'MindSpace 配置未启用' });
const config = await loadMindSpaceConfig(pool);
+8
View File
@@ -43,6 +43,10 @@ export async function bootstrapAdminServices() {
const { createAssetGatewayConfigService } = await importMemind('asset-gateway.mjs');
const { ensureAssetGatewaySchema } = await importMemind('db.mjs');
const { createWechatScheduleLlmConfigService } = await importMemind('wechat-schedule-llm-config.mjs');
const { createWechatIntentRouterConfigService } = await importMemind('wechat-intent-router-config.mjs');
const { createWechatCursorExecutorAdminConfigService } = await importMemind(
'wechat-cursor-executor-admin-config.mjs',
);
const { createAdminSystemTestService } = await importMemind('admin-system-tests.mjs');
const { createOpsApi } = await importMemind('admin-routes.mjs');
const { createWordFilterService, ensureWordFilterSchema } = await importMemind('word-filter.mjs');
@@ -126,6 +130,8 @@ export async function bootstrapAdminServices() {
const wechatScheduleLlmConfigService = createWechatScheduleLlmConfigService(pool, {
env: process.env,
});
const wechatIntentRouterConfigService = createWechatIntentRouterConfigService(pool);
const wechatCursorExecutorPolicyService = createWechatCursorExecutorAdminConfigService(pool);
const adminSystemTestService = createAdminSystemTestService({
pool,
userAuth,
@@ -198,6 +204,8 @@ export async function bootstrapAdminServices() {
skillRuntimeConfigService,
billingConfigService,
wechatScheduleLlmConfigService,
wechatIntentRouterConfigService,
wechatCursorExecutorPolicyService,
adminSystemTestService,
systemTestAccountService,
wordFilterService,
+4
View File
@@ -112,6 +112,8 @@ ready
skillRuntimeConfigService,
billingConfigService,
wechatScheduleLlmConfigService,
wechatIntentRouterConfigService,
wechatCursorExecutorPolicyService,
adminSystemTestService,
systemTestAccountService,
wordFilterService,
@@ -142,6 +144,8 @@ ready
skillRuntimeConfigService,
billingConfigService,
wechatScheduleLlmConfigService,
wechatIntentRouterConfigService,
wechatCursorExecutorPolicyService,
adminSystemTestService,
systemTestAccountService,
wordFilterService,
+474
View File
@@ -6,6 +6,8 @@ import {
getWechatAdminSummary,
getWechatIntentRouterConfig,
getWechatIntentRouterRuntime,
getWechatCursorExecutorConfig,
getWechatCursorExecutorRuntime,
listAdminUsers,
listLlmProviderKeys,
listWechatBindings,
@@ -14,6 +16,7 @@ import {
listWechatMessages,
listWechatWebNotifications,
patchWechatIntentRouterConfig,
patchWechatCursorExecutorConfig,
resumeWechatDigest,
updateWechatScheduleLlmConfig,
} from '../../api/client';
@@ -26,6 +29,8 @@ import type {
WechatDigestSubscription,
WechatIntentRouterAdminConfig,
WechatIntentRouterRuntimeState,
WechatCursorExecutorAdminConfig,
WechatCursorExecutorRuntimeState,
WechatMessage,
WechatWebNotification,
} from '../../types';
@@ -120,6 +125,268 @@ function intentRouterRuntimeMode(overrides: Record<string, string>) {
return '已激活(chat.general 可升级为 page.generate';
}
type CursorExecutorForm = {
enabled: boolean;
selectedUserIds: string[];
manualAllowlistEntries: string[];
intentAllowlist: string;
fallbackToDeepseek: boolean;
};
const USER_ID_PATTERN =
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
function userMatchesAllowlistToken(user: AdminUserRow, token: string) {
const normalized = token.trim().toLowerCase();
if (!normalized) return false;
const identities = [
user.id,
user.username,
user.slug,
user.publishSlug,
user.displayName,
]
.map((value) => String(value ?? '').trim().toLowerCase())
.filter(Boolean);
return identities.includes(normalized);
}
function resolveAllowlistTokens(tokens: string[], users: AdminUserRow[]) {
const selectedUserIds: string[] = [];
const manualAllowlistEntries: string[] = [];
const seenIds = new Set<string>();
for (const rawToken of tokens) {
const token = String(rawToken ?? '').trim();
if (!token) continue;
const matchedUser = users.find((user) => userMatchesAllowlistToken(user, token));
if (matchedUser) {
if (!seenIds.has(matchedUser.id)) {
seenIds.add(matchedUser.id);
selectedUserIds.push(matchedUser.id);
}
continue;
}
if (USER_ID_PATTERN.test(token)) {
if (!seenIds.has(token)) {
seenIds.add(token);
selectedUserIds.push(token);
}
continue;
}
manualAllowlistEntries.push(token);
}
return { selectedUserIds, manualAllowlistEntries };
}
function cursorExecutorToForm(
config: WechatCursorExecutorAdminConfig | undefined,
users: AdminUserRow[] = [],
): CursorExecutorForm {
const tokens = Array.isArray(config?.userAllowlist) ? config.userAllowlist : [];
const { selectedUserIds, manualAllowlistEntries } = resolveAllowlistTokens(tokens, users);
return {
enabled: Boolean(config?.enabled),
selectedUserIds,
manualAllowlistEntries,
intentAllowlist: Array.isArray(config?.intentAllowlist)
? config.intentAllowlist.join('\n')
: 'page.generate',
fallbackToDeepseek: config?.fallbackToDeepseek !== false,
};
}
function cursorFormToAllowlist(form: CursorExecutorForm) {
const manual = form.manualAllowlistEntries
.map((item) => item.trim())
.filter(Boolean);
return [...new Set([...form.selectedUserIds, ...manual])];
}
function CursorAllowlistPickerModal({
open,
users,
selectedUserIds,
busy,
onClose,
onConfirm,
}: {
open: boolean;
users: AdminUserRow[];
selectedUserIds: string[];
busy?: boolean;
onClose: () => void;
onConfirm: (nextIds: string[]) => void;
}) {
const [search, setSearch] = useState('');
const [draftIds, setDraftIds] = useState<string[]>(selectedUserIds);
const [pickerUsers, setPickerUsers] = useState<AdminUserRow[]>(users);
const [loadingUsers, setLoadingUsers] = useState(false);
useEffect(() => {
if (!open) return;
setDraftIds(selectedUserIds);
setSearch('');
setPickerUsers(users);
}, [open, selectedUserIds, users]);
useEffect(() => {
if (!open) return;
const timer = window.setTimeout(() => {
void (async () => {
setLoadingUsers(true);
try {
const result = await listAdminUsers({
page: 1,
pageSize: 500,
status: 'active',
search: search.trim() || undefined,
});
setPickerUsers(result.items);
} catch {
setPickerUsers(users);
} finally {
setLoadingUsers(false);
}
})();
}, search.trim() ? 250 : 0);
return () => window.clearTimeout(timer);
}, [open, search, users]);
if (!open) return null;
const visibleIds = pickerUsers.map((user) => user.id);
const allVisibleSelected =
visibleIds.length > 0 && visibleIds.every((id) => draftIds.includes(id));
const toggleUser = (userId: string) => {
setDraftIds((current) =>
current.includes(userId)
? current.filter((id) => id !== userId)
: [...current, userId],
);
};
const toggleAllVisible = () => {
setDraftIds((current) => {
if (allVisibleSelected) {
return current.filter((id) => !visibleIds.includes(id));
}
return [...new Set([...current, ...visibleIds])];
});
};
return (
<div
className="modal-backdrop"
role="presentation"
onMouseDown={(event) => {
if (event.target === event.currentTarget) onClose();
}}
>
<div
className="modal-box"
role="dialog"
aria-modal="true"
aria-labelledby="cursor-allowlist-picker-title"
style={{ maxWidth: 720, width: 'min(720px, calc(100vw - 32px))' }}
onMouseDown={(event) => event.stopPropagation()}
>
<div className="modal-head">
<div>
<h3 id="cursor-allowlist-picker-title"></h3>
<p className="muted" style={{ margin: '6px 0 0', fontSize: 13 }}>
TKMind DeepSeek
</p>
</div>
<button type="button" className="modal-close" onClick={onClose} aria-label="关闭">
×
</button>
</div>
<div className="wechat-toolbar" style={{ marginBottom: 12 }}>
<input
value={search}
onChange={(event) => setSearch(event.target.value)}
placeholder="搜索用户名、昵称或 userId"
style={{ flex: 1, minWidth: 220 }}
autoFocus
/>
<button type="button" className="ghost-btn" onClick={toggleAllVisible} disabled={loadingUsers || pickerUsers.length === 0}>
{allVisibleSelected ? '取消全选当前列表' : '全选当前列表'}
</button>
</div>
<p className="muted" style={{ margin: '0 0 8px', fontSize: 12 }}>
{draftIds.length} {loadingUsers ? ' · 加载中…' : ''}
</p>
<div
className="admin-table-wrap"
style={{ maxHeight: 360, overflow: 'auto', border: '1px solid var(--color-border, #e7dfd1)', borderRadius: 8 }}
>
<table className="admin-table">
<thead>
<tr>
<th style={{ width: 44 }} />
<th></th>
<th></th>
<th>userId</th>
</tr>
</thead>
<tbody>
{pickerUsers.length === 0 ? (
<tr>
<td colSpan={4} className="muted">
{loadingUsers ? '加载用户列表…' : '没有匹配的用户'}
</td>
</tr>
) : (
pickerUsers.map((user) => {
const checked = draftIds.includes(user.id);
return (
<tr
key={user.id}
style={{ cursor: 'pointer' }}
onClick={() => toggleUser(user.id)}
>
<td onClick={(event) => event.stopPropagation()}>
<input
type="checkbox"
checked={checked}
onChange={() => toggleUser(user.id)}
/>
</td>
<td>{user.displayName || user.username}</td>
<td className="muted">@{user.username}</td>
<td className="mono">{user.id}</td>
</tr>
);
})
)}
</tbody>
</table>
</div>
<div className="modal-actions" style={{ marginTop: 16 }}>
<button type="button" className="ghost-btn" onClick={onClose} disabled={busy}>
</button>
<button
type="button"
className="send-btn"
disabled={busy}
onClick={() => onConfirm(draftIds)}
>
</button>
</div>
</div>
</div>
);
}
export function WechatPage() {
const [summary, setSummary] = useState<WechatAdminSummary | null>(null);
const [bindings, setBindings] = useState<WechatBinding[]>([]);
@@ -146,6 +413,11 @@ export function WechatPage() {
const [savedIntentForm, setSavedIntentForm] = useState<IntentRouterForm | null>(null);
const [intentRuntime, setIntentRuntime] = useState<WechatIntentRouterRuntimeState | null>(null);
const [intentSaving, setIntentSaving] = useState(false);
const [cursorForm, setCursorForm] = useState<CursorExecutorForm | null>(null);
const [savedCursorForm, setSavedCursorForm] = useState<CursorExecutorForm | null>(null);
const [cursorRuntime, setCursorRuntime] = useState<WechatCursorExecutorRuntimeState | null>(null);
const [cursorSaving, setCursorSaving] = useState(false);
const [cursorAllowlistPickerOpen, setCursorAllowlistPickerOpen] = useState(false);
const [loading, setLoading] = useState(true);
const [busy, setBusy] = useState(false);
const [error, setError] = useState<string | null>(null);
@@ -162,6 +434,11 @@ export function WechatPage() {
[llmKeys, intentForm?.modelProviderKeyId],
);
const cursorDirty = useMemo(() => {
if (!cursorForm || !savedCursorForm) return false;
return JSON.stringify(cursorForm) !== JSON.stringify(savedCursorForm);
}, [cursorForm, savedCursorForm]);
const load = useCallback(async () => {
setLoading(true);
setError(null);
@@ -177,6 +454,8 @@ export function WechatPage() {
nextLlmKeys,
nextIntentConfig,
nextIntentRuntime,
nextCursorConfig,
nextCursorRuntime,
] =
await Promise.all([
getWechatAdminSummary(),
@@ -189,6 +468,8 @@ export function WechatPage() {
listLlmProviderKeys().catch(() => [] as LlmProviderKeyRow[]),
getWechatIntentRouterConfig().catch(() => null),
getWechatIntentRouterRuntime().catch(() => null),
getWechatCursorExecutorConfig().catch(() => null),
getWechatCursorExecutorRuntime().catch(() => null),
]);
setSummary(nextSummary);
setScheduleLlmEnabled(nextSummary.config.scheduleLlmEnabled ?? false);
@@ -206,6 +487,12 @@ export function WechatPage() {
setSavedIntentForm(nextForm);
}
setIntentRuntime(nextIntentRuntime);
if (nextCursorConfig?.config) {
const nextCursorForm = cursorExecutorToForm(nextCursorConfig.config, nextUsers.items);
setCursorForm(nextCursorForm);
setSavedCursorForm(nextCursorForm);
}
setCursorRuntime(nextCursorRuntime);
} catch (err) {
setError(err instanceof Error ? err.message : '加载服务号管理失败');
} finally {
@@ -343,6 +630,42 @@ export function WechatPage() {
}
};
const handleSaveCursorExecutor = async () => {
if (!cursorForm) return;
setCursorSaving(true);
setError(null);
setNotice(null);
try {
const result = await patchWechatCursorExecutorConfig({
enabled: cursorForm.enabled,
userAllowlist: cursorFormToAllowlist(cursorForm),
intentAllowlist: cursorForm.intentAllowlist
.split(/[\s,]+/u)
.map((item) => item.trim())
.filter(Boolean),
fallbackToDeepseek: cursorForm.fallbackToDeepseek,
});
const nextForm = cursorExecutorToForm(result.config, users);
setCursorForm(nextForm);
setSavedCursorForm(nextForm);
setCursorRuntime(await getWechatCursorExecutorRuntime().catch(() => null));
setNotice('微信 TKMind 智趣体验通道已保存。未在白名单内的用户仍走原有 DeepSeek 链路。');
} catch (err) {
setError(err instanceof Error ? err.message : '保存失败');
} finally {
setCursorSaving(false);
}
};
const selectedCursorUsers = useMemo(() => {
if (!cursorForm) return [];
const byId = new Map(users.map((user) => [user.id, user]));
return cursorForm.selectedUserIds.map((userId) => ({
userId,
user: byId.get(userId) ?? null,
}));
}, [cursorForm, users]);
return (
<div className="admin-page">
<div className="admin-page-head">
@@ -559,6 +882,157 @@ export function WechatPage() {
</section>
) : null}
{cursorForm ? (
<section className="admin-card">
<div className="admin-card-head">
<div>
<h2> TKMind </h2>
<p className="muted" style={{ marginTop: 6 }}>
DeepSeek/Goose TKMind 退 DeepSeek
</p>
</div>
<button
type="button"
className="ghost-btn"
onClick={() => void handleSaveCursorExecutor()}
disabled={busy || cursorSaving || !cursorDirty}
>
{cursorSaving ? '保存中…' : cursorDirty ? '保存配置' : '已保存'}
</button>
</div>
<div className="admin-form" style={{ marginTop: 16 }}>
<label>
<input
type="checkbox"
checked={cursorForm.enabled}
disabled={busy || cursorSaving}
onChange={(event) =>
setCursorForm((current) => current && { ...current, enabled: event.target.checked })
}
/>{' '}
</label>
<label>
<input
type="checkbox"
checked={cursorForm.fallbackToDeepseek}
disabled={busy || cursorSaving}
onChange={(event) =>
setCursorForm((current) =>
current && { ...current, fallbackToDeepseek: event.target.checked },
)
}
/>{' '}
Cursor 退 DeepSeek
</label>
<label>
<div className="wechat-toolbar" style={{ marginTop: 8, marginBottom: 8 }}>
<button
type="button"
className="ghost-btn"
disabled={busy || cursorSaving}
onClick={() => setCursorAllowlistPickerOpen(true)}
>
</button>
{cursorForm.selectedUserIds.length > 0 ? (
<button
type="button"
className="ghost-btn"
disabled={busy || cursorSaving}
onClick={() =>
setCursorForm((current) =>
current && { ...current, selectedUserIds: [] },
)
}
>
</button>
) : null}
</div>
{selectedCursorUsers.length > 0 ? (
<div className="cursor-allowlist-chips">
{selectedCursorUsers.map(({ userId, user }) => (
<span key={userId} className="cursor-allowlist-chip">
<span>
{user ? (user.displayName || user.username) : userId}
{user ? <span className="muted"> @{user.username}</span> : null}
</span>
<button
type="button"
className="cursor-allowlist-chip-remove"
aria-label="移出白名单"
disabled={busy || cursorSaving}
onClick={() =>
setCursorForm((current) =>
current
? {
...current,
selectedUserIds: current.selectedUserIds.filter((id) => id !== userId),
}
: current,
)
}
>
×
</button>
</span>
))}
</div>
) : (
<p className="muted" style={{ margin: 0 }}>
</p>
)}
{cursorForm.manualAllowlistEntries.length > 0 ? (
<p className="muted" style={{ marginTop: 8, fontSize: 12 }}>
{cursorForm.manualAllowlistEntries.length}
{' '}
{cursorForm.manualAllowlistEntries.join('、')}
</p>
) : null}
</label>
<label>
page.generate
<textarea
value={cursorForm.intentAllowlist}
disabled={busy || cursorSaving}
rows={2}
placeholder="page.generate"
onChange={(event) =>
setCursorForm((current) => current && { ...current, intentAllowlist: event.target.value })
}
/>
</label>
</div>
{cursorRuntime ? (
<p className="muted" style={{ marginTop: 12 }}>
{cursorRuntime.policy.enabled ? '已开启' : '已关闭'}
{' · '}
{cursorRuntime.policy.userAllowlist.length}
{' · '}
{cursorRuntime.source}
{cursorRuntime.updatedAt ? ` · 更新 ${dateLabel(cursorRuntime.updatedAt)}` : ''}
</p>
) : null}
</section>
) : null}
{cursorForm ? (
<CursorAllowlistPickerModal
open={cursorAllowlistPickerOpen}
users={users}
selectedUserIds={cursorForm.selectedUserIds}
busy={busy || cursorSaving}
onClose={() => setCursorAllowlistPickerOpen(false)}
onConfirm={(nextIds) => {
setCursorForm((current) => current && { ...current, selectedUserIds: nextIds });
setCursorAllowlistPickerOpen(false);
}}
/>
) : null}
<section className="admin-card">
<div className="admin-card-head">
<h2></h2>
+20
View File
@@ -58,6 +58,9 @@ import type {
WechatIntentRouterAdminConfig,
WechatIntentRouterConfigState,
WechatIntentRouterRuntimeState,
WechatCursorExecutorAdminConfig,
WechatCursorExecutorAdminConfigState,
WechatCursorExecutorRuntimeState,
WechatMessage,
WechatWebNotification,
MindSearchConfig,
@@ -474,6 +477,23 @@ export async function getWechatIntentRouterRuntime(): Promise<WechatIntentRouter
return portalFetch<WechatIntentRouterRuntimeState>('/admin-api/wechat/intent-router/runtime');
}
export async function getWechatCursorExecutorConfig(): Promise<WechatCursorExecutorAdminConfigState> {
return portalFetch<WechatCursorExecutorAdminConfigState>('/admin-api/wechat/cursor-executor/config');
}
export async function patchWechatCursorExecutorConfig(
patch: Partial<WechatCursorExecutorAdminConfig>,
): Promise<WechatCursorExecutorAdminConfigState> {
return portalFetch<WechatCursorExecutorAdminConfigState>('/admin-api/wechat/cursor-executor/config', {
method: 'PATCH',
body: JSON.stringify({ config: patch }),
});
}
export async function getWechatCursorExecutorRuntime(): Promise<WechatCursorExecutorRuntimeState> {
return portalFetch<WechatCursorExecutorRuntimeState>('/admin-api/wechat/cursor-executor/runtime');
}
export async function getAssetGatewayConfig(): Promise<AssetGatewayConfig> {
return portalFetch('/admin-api/asset-gateway/config');
}
+31
View File
@@ -879,6 +879,37 @@ body,
padding-top: 4px;
}
.cursor-allowlist-chips {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.cursor-allowlist-chip {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 6px 10px;
border: 1px solid var(--color-border-input, #e7dfd1);
border-radius: 999px;
background: var(--color-bg-subtle, rgba(0, 0, 0, 0.02));
font-size: 13px;
}
.cursor-allowlist-chip-remove {
border: 0;
background: transparent;
color: var(--color-text-muted, #68716c);
cursor: pointer;
font-size: 16px;
line-height: 1;
padding: 0 2px;
}
.cursor-allowlist-chip-remove:hover {
color: var(--color-text, #1f2421);
}
.global-model-card {
margin-bottom: 16px;
padding: 14px;
+31
View File
@@ -346,6 +346,37 @@ export type WechatIntentRouterRuntimeState = {
config: WechatIntentRouterAdminConfig;
};
export type WechatCursorExecutorAdminConfig = {
enabled: boolean;
userAllowlist: string[];
intentAllowlist: string[];
fallbackToDeepseek: boolean;
meta?: {
notes?: string;
};
};
export type WechatCursorExecutorAdminConfigState = {
config: WechatCursorExecutorAdminConfig;
updatedAt?: number | null;
updatedBy?: string | null;
source?: string;
};
export type WechatCursorExecutorRuntimeState = {
source: string;
updatedAt: number | null;
updatedBy: string | null;
config: WechatCursorExecutorAdminConfig;
policy: {
enabled: boolean;
userAllowlist: string[];
intentAllowlist: string[];
fallbackToDeepseek: boolean;
source?: string;
};
};
export type MindSpaceSeoGeoConfig = {
enabled: boolean;
seo: {