Compare commits

...

4 Commits

Author SHA1 Message Date
john 7c6427e123 fix(release): bundle wechat follow QR png into memind-lib
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-10 10:03:41 +08:00
john 8651a50cbe fix(wechat): improve news draft push button feedback
Show loading/progress notice during push, auto-save config before push,
and remove confirm dialog that blocked clicks in some browsers.
2026-09-10 09:30:29 +08:00
john b414b732c8 feat(wechat): add news morning draft admin UI and API routes
Expose preview/push controls for daily-news WeChat draft publishing with
inline HTML body mode in the Wechat admin page.
2026-09-10 09:25:25 +08:00
john d4127bd24b Merge branch 'feature/user-subscription-token-display'
Show user subscription plan and token quota in admin user list and detail pages.
2026-09-07 16:22:47 +08:00
7 changed files with 531 additions and 0 deletions
+1
View File
@@ -168,6 +168,7 @@ rsync -a \
--include '*.mjs' \
--include '*.json' \
--include '*.js' \
--include 'wechat/assets/*.png' \
--exclude '*' \
"${MEMIND_SRC}/" "${MEMIND_LIB}/"
+63
View File
@@ -108,6 +108,7 @@ export function createAdminApp(services) {
skillRuntimeConfigService,
billingConfigService,
wechatScheduleLlmConfigService,
wechatNewsMorningDraftService,
wechatIntentRouterConfigService,
wechatCursorExecutorPolicyService,
adminSystemTestService,
@@ -420,6 +421,68 @@ export function createAdminApp(services) {
res.json(result);
});
adminApi.get('/wechat/news-morning-draft/config', requireAdmin, async (_req, res) => {
if (!wechatNewsMorningDraftService) {
return res.status(503).json({ message: '新闻早报草稿推送未启用' });
}
const [config, template] = await Promise.all([
wechatNewsMorningDraftService.getConfig(),
Promise.resolve(wechatNewsMorningDraftService.getTemplate()),
]);
return res.json({ config, template });
});
adminApi.patch('/wechat/news-morning-draft/config', requireAdmin, async (req, res) => {
if (!wechatNewsMorningDraftService) {
return res.status(503).json({ message: '新闻早报草稿推送未启用' });
}
const config = await wechatNewsMorningDraftService.updateConfig(req.body ?? {}, {
updatedBy: req.currentUser.id,
});
return res.json({ config });
});
adminApi.get('/wechat/news-morning-draft/preview', requireAdmin, async (_req, res) => {
if (!wechatNewsMorningDraftService) {
return res.status(503).json({ message: '新闻早报草稿推送未启用' });
}
try {
return res.json(await wechatNewsMorningDraftService.preview());
} catch (error) {
return res.status(400).json({
message: error instanceof Error ? error.message : '预览失败',
});
}
});
adminApi.post('/wechat/news-morning-draft/push', requireAdmin, async (req, res) => {
if (!wechatNewsMorningDraftService) {
return res.status(503).json({ message: '新闻早报草稿推送未启用' });
}
try {
const result = await wechatNewsMorningDraftService.pushDraft({
triggeredBy: req.currentUser.id,
dryRun: req.body?.dryRun === true,
});
return res.json(result);
} catch (error) {
return res.status(400).json({
message: error instanceof Error ? error.message : '推送失败',
run: error?.run ?? null,
});
}
});
adminApi.get('/wechat/news-morning-draft/runs', requireAdmin, async (req, res) => {
if (!wechatNewsMorningDraftService) {
return res.status(503).json({ message: '新闻早报草稿推送未启用' });
}
const runs = await wechatNewsMorningDraftService.listRuns({
limit: req.query.limit,
});
return res.json({ runs });
});
adminApi.get('/wechat/intent-router/config', requireAdmin, async (_req, res) => {
if (!wechatIntentRouterConfigService?.getConfig) {
return res.status(503).json({ message: '微信意图路由配置未启用' });
+8
View File
@@ -43,6 +43,7 @@ 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 { createWechatNewsMorningDraftService } = await importMemind('wechat-news-morning-draft.mjs');
const { createWechatIntentRouterConfigService } = await importMemind('wechat-intent-router-config.mjs');
const { createWechatCursorExecutorAdminConfigService } = await importMemind(
'wechat-cursor-executor-admin-config.mjs',
@@ -138,6 +139,12 @@ export async function bootstrapAdminServices() {
portalBaseUrl: `http://127.0.0.1:${process.env.H5_PORT ?? 8081}`,
});
const wechatMpConfig = loadWechatMpConfig();
const wechatNewsMorningDraftService = createWechatNewsMorningDraftService(pool, {
mpConfig: wechatMpConfig,
h5Root,
memindLibRoot: resolveMemindLib(),
env: process.env,
});
const wechatMpService = createWechatMpService({
config: wechatMpConfig,
userAuth,
@@ -204,6 +211,7 @@ export async function bootstrapAdminServices() {
skillRuntimeConfigService,
billingConfigService,
wechatScheduleLlmConfigService,
wechatNewsMorningDraftService,
wechatIntentRouterConfigService,
wechatCursorExecutorPolicyService,
adminSystemTestService,
+2
View File
@@ -112,6 +112,7 @@ ready
skillRuntimeConfigService,
billingConfigService,
wechatScheduleLlmConfigService,
wechatNewsMorningDraftService,
wechatIntentRouterConfigService,
wechatCursorExecutorPolicyService,
adminSystemTestService,
@@ -144,6 +145,7 @@ ready
skillRuntimeConfigService,
billingConfigService,
wechatScheduleLlmConfigService,
wechatNewsMorningDraftService,
wechatIntentRouterConfigService,
wechatCursorExecutorPolicyService,
adminSystemTestService,
+355
View File
@@ -7,14 +7,19 @@ import {
getWechatAdminSummary,
getWechatIntentRouterConfig,
getWechatIntentRouterRuntime,
getWechatNewsMorningDraftConfig,
listAdminUsers,
listLlmProviderKeys,
listWechatBindings,
listWechatDeliveries,
listWechatDigests,
listWechatMessages,
listWechatNewsMorningDraftRuns,
listWechatWebNotifications,
patchWechatIntentRouterConfig,
patchWechatNewsMorningDraftConfig,
previewWechatNewsMorningDraft,
pushWechatNewsMorningDraft,
resumeWechatDigest,
updateWechatScheduleLlmConfig,
} from '../../api/client';
@@ -28,6 +33,9 @@ import type {
WechatIntentRouterAdminConfig,
WechatIntentRouterRuntimeState,
WechatMessage,
WechatNewsMorningDraftPreview,
WechatNewsMorningDraftRun,
WechatNewsMorningDraftTemplate,
WechatWebNotification,
} from '../../types';
import { formatTime } from '../utils/format';
@@ -88,6 +96,35 @@ type IntentRouterForm = {
canaryOpenids: string;
};
type NewsMorningDraftForm = {
enabled: boolean;
autoPushEnabled: boolean;
pushHour: string;
pushMinute: string;
timezone: string;
sourceUserId: string;
pageSlugPattern: string;
author: string;
templateVersion: string;
};
function newsMorningDraftToForm(
config: Partial<NewsMorningDraftForm> | undefined,
template: WechatNewsMorningDraftTemplate | undefined,
): NewsMorningDraftForm {
return {
enabled: Boolean(config?.enabled),
autoPushEnabled: Boolean(config?.autoPushEnabled),
pushHour: String(config?.pushHour ?? 6),
pushMinute: String(config?.pushMinute ?? 0),
timezone: String(config?.timezone ?? 'Asia/Shanghai'),
sourceUserId: String(config?.sourceUserId ?? ''),
pageSlugPattern: String(config?.pageSlugPattern ?? 'daily-news-*'),
author: String(config?.author ?? 'TKMind'),
templateVersion: String(config?.templateVersion ?? template?.version ?? '2026-09-10'),
};
}
function intentRouterToForm(
config: WechatIntentRouterAdminConfig | undefined,
keys: LlmProviderKeyRow[],
@@ -147,6 +184,13 @@ export function WechatPage() {
const [savedIntentForm, setSavedIntentForm] = useState<IntentRouterForm | null>(null);
const [intentRuntime, setIntentRuntime] = useState<WechatIntentRouterRuntimeState | null>(null);
const [intentSaving, setIntentSaving] = useState(false);
const [newsDraftForm, setNewsDraftForm] = useState<NewsMorningDraftForm | null>(null);
const [savedNewsDraftForm, setSavedNewsDraftForm] = useState<NewsMorningDraftForm | null>(null);
const [newsDraftTemplate, setNewsDraftTemplate] = useState<WechatNewsMorningDraftTemplate | null>(null);
const [newsDraftPreview, setNewsDraftPreview] = useState<WechatNewsMorningDraftPreview | null>(null);
const [newsDraftRuns, setNewsDraftRuns] = useState<WechatNewsMorningDraftRun[]>([]);
const [newsDraftSaving, setNewsDraftSaving] = useState(false);
const [newsDraftWorking, setNewsDraftWorking] = useState(false);
const [loading, setLoading] = useState(true);
const [busy, setBusy] = useState(false);
const [error, setError] = useState<string | null>(null);
@@ -158,6 +202,11 @@ export function WechatPage() {
return JSON.stringify(intentForm) !== JSON.stringify(savedIntentForm);
}, [intentForm, savedIntentForm]);
const newsDraftDirty = useMemo(() => {
if (!newsDraftForm || !savedNewsDraftForm) return false;
return JSON.stringify(newsDraftForm) !== JSON.stringify(savedNewsDraftForm);
}, [newsDraftForm, savedNewsDraftForm]);
const selectedIntentKey = useMemo(
() => llmKeys.find((item) => item.id === intentForm?.modelProviderKeyId) ?? null,
[llmKeys, intentForm?.modelProviderKeyId],
@@ -178,6 +227,8 @@ export function WechatPage() {
nextLlmKeys,
nextIntentConfig,
nextIntentRuntime,
nextNewsDraftConfig,
nextNewsDraftRuns,
] =
await Promise.all([
getWechatAdminSummary(),
@@ -190,6 +241,8 @@ export function WechatPage() {
listLlmProviderKeys().catch(() => [] as LlmProviderKeyRow[]),
getWechatIntentRouterConfig().catch(() => null),
getWechatIntentRouterRuntime().catch(() => null),
getWechatNewsMorningDraftConfig().catch(() => null),
listWechatNewsMorningDraftRuns(10).catch(() => [] as WechatNewsMorningDraftRun[]),
]);
setSummary(nextSummary);
setScheduleLlmEnabled(nextSummary.config.scheduleLlmEnabled ?? false);
@@ -207,6 +260,13 @@ export function WechatPage() {
setSavedIntentForm(nextForm);
}
setIntentRuntime(nextIntentRuntime);
if (nextNewsDraftConfig) {
const nextForm = newsMorningDraftToForm(nextNewsDraftConfig.config, nextNewsDraftConfig.template);
setNewsDraftForm(nextForm);
setSavedNewsDraftForm(nextForm);
setNewsDraftTemplate(nextNewsDraftConfig.template);
}
setNewsDraftRuns(nextNewsDraftRuns);
} catch (err) {
setError(err instanceof Error ? err.message : '加载服务号管理失败');
} finally {
@@ -314,6 +374,82 @@ export function WechatPage() {
});
};
const handleSaveNewsMorningDraft = async () => {
if (!newsDraftForm) return;
setNewsDraftSaving(true);
setError(null);
setNotice(null);
try {
const result = await patchWechatNewsMorningDraftConfig({
enabled: newsDraftForm.enabled,
autoPushEnabled: newsDraftForm.autoPushEnabled,
pushHour: Number(newsDraftForm.pushHour) || 6,
pushMinute: Number(newsDraftForm.pushMinute) || 0,
timezone: newsDraftForm.timezone.trim() || 'Asia/Shanghai',
sourceUserId: newsDraftForm.sourceUserId.trim() || null,
pageSlugPattern: newsDraftForm.pageSlugPattern.trim() || 'daily-news-*',
author: newsDraftForm.author.trim() || 'TKMind',
});
const nextForm = newsMorningDraftToForm(result, newsDraftTemplate ?? undefined);
setNewsDraftForm(nextForm);
setSavedNewsDraftForm(nextForm);
setNotice('新闻早报草稿推送配置已保存。');
} catch (err) {
setError(err instanceof Error ? err.message : '保存失败');
} finally {
setNewsDraftSaving(false);
}
};
const handlePreviewNewsMorningDraft = async () => {
setNewsDraftWorking(true);
setError(null);
try {
setNewsDraftPreview(await previewWechatNewsMorningDraft());
} catch (err) {
setError(err instanceof Error ? err.message : '预览失败');
} finally {
setNewsDraftWorking(false);
}
};
const handlePushNewsMorningDraft = async () => {
if (!newsDraftForm) return;
if (!newsDraftForm.sourceUserId.trim()) {
setError('请先填写来源用户 ID');
return;
}
setNewsDraftWorking(true);
setError(null);
setNotice('正在推送到微信草稿箱,请稍候(约 5–15 秒)…');
try {
if (newsDraftDirty || !newsDraftForm.enabled) {
const saved = await patchWechatNewsMorningDraftConfig({
enabled: true,
autoPushEnabled: newsDraftForm.autoPushEnabled,
pushHour: Number(newsDraftForm.pushHour) || 6,
pushMinute: Number(newsDraftForm.pushMinute) || 0,
timezone: newsDraftForm.timezone.trim() || 'Asia/Shanghai',
sourceUserId: newsDraftForm.sourceUserId.trim() || null,
pageSlugPattern: newsDraftForm.pageSlugPattern.trim() || 'daily-news-*',
author: newsDraftForm.author.trim() || 'TKMind',
});
const nextForm = newsMorningDraftToForm(saved, newsDraftTemplate ?? undefined);
setNewsDraftForm(nextForm);
setSavedNewsDraftForm(nextForm);
}
const result = await pushWechatNewsMorningDraft();
setNotice(`草稿已写入微信后台,media_id${result.draftMediaId ?? '—'}`);
setNewsDraftPreview(result.preview ?? null);
setNewsDraftRuns(await listWechatNewsMorningDraftRuns(10));
} catch (err) {
setError(err instanceof Error ? err.message : '推送失败');
setNotice(null);
} finally {
setNewsDraftWorking(false);
}
};
const handleSaveIntentRouter = async () => {
if (!intentForm) return;
setIntentSaving(true);
@@ -425,6 +561,225 @@ export function WechatPage() {
</section>
)}
{newsDraftForm ? (
<section className="admin-card">
<div className="admin-card-head">
<div>
<h2> · 稿</h2>
<p className="muted" style={{ marginTop: 6 }}>
MindSpace HTML 稿 {newsDraftForm.templateVersion}0910
</p>
</div>
<button
type="button"
className="ghost-btn"
onClick={() => void handleSaveNewsMorningDraft()}
disabled={busy || newsDraftSaving || newsDraftWorking || !newsDraftDirty}
>
{newsDraftSaving ? '保存中…' : newsDraftDirty ? '保存配置' : '已保存'}
</button>
</div>
<div className="admin-form" style={{ marginTop: 16 }}>
<label>
<input
type="checkbox"
checked={newsDraftForm.enabled}
disabled={busy || newsDraftSaving || newsDraftWorking}
onChange={(event) =>
setNewsDraftForm((current) => current && { ...current, enabled: event.target.checked })
}
/>{' '}
稿
</label>
<label>
<input
type="checkbox"
checked={newsDraftForm.autoPushEnabled}
disabled={busy || newsDraftSaving || newsDraftWorking || !newsDraftForm.enabled}
onChange={(event) =>
setNewsDraftForm((current) => current && { ...current, autoPushEnabled: event.target.checked })
}
/>{' '}
Portal
</label>
<label>
<div className="wechat-toolbar" style={{ marginTop: 8 }}>
<input
type="number"
min={0}
max={23}
value={newsDraftForm.pushHour}
disabled={busy || newsDraftSaving || newsDraftWorking}
onChange={(event) =>
setNewsDraftForm((current) => current && { ...current, pushHour: event.target.value })
}
style={{ width: 80 }}
/>
<span>:</span>
<input
type="number"
min={0}
max={59}
value={newsDraftForm.pushMinute}
disabled={busy || newsDraftSaving || newsDraftWorking}
onChange={(event) =>
setNewsDraftForm((current) => current && { ...current, pushMinute: event.target.value })
}
style={{ width: 80 }}
/>
</div>
</label>
<label>
ID
<input
value={newsDraftForm.sourceUserId}
disabled={busy || newsDraftSaving || newsDraftWorking}
placeholder="a70ff537-8908-486e-9b6c-042e07cc25db"
onChange={(event) =>
setNewsDraftForm((current) => current && { ...current, sourceUserId: event.target.value })
}
/>
</label>
<label>
<input
value={newsDraftForm.pageSlugPattern}
disabled={busy || newsDraftSaving || newsDraftWorking}
placeholder="daily-news-*"
onChange={(event) =>
setNewsDraftForm((current) => current && { ...current, pageSlugPattern: event.target.value })
}
/>
</label>
<label>
稿 8
<input
value={newsDraftForm.author}
maxLength={8}
disabled={busy || newsDraftSaving || newsDraftWorking}
onChange={(event) =>
setNewsDraftForm((current) => current && { ...current, author: event.target.value })
}
/>
</label>
</div>
{newsDraftTemplate ? (
<details style={{ marginTop: 12 }}>
<summary className="muted">0910 taskSpec </summary>
<pre className="admin-code-block" style={{ whiteSpace: 'pre-wrap', marginTop: 8 }}>
{newsDraftTemplate.spec}
</pre>
</details>
) : null}
<div className="wechat-toolbar" style={{ marginTop: 16 }}>
<button
type="button"
className="ghost-btn"
onClick={() => void handlePreviewNewsMorningDraft()}
disabled={busy || newsDraftWorking || !newsDraftForm.sourceUserId.trim()}
>
{newsDraftWorking ? '处理中…' : '预览今日草稿'}
</button>
<button
type="button"
className="primary-btn"
onClick={() => void handlePushNewsMorningDraft()}
disabled={busy || newsDraftWorking || !newsDraftForm.sourceUserId.trim()}
title={
!newsDraftForm.sourceUserId.trim()
? '请先填写来源用户 ID'
: !newsDraftForm.enabled
? '建议勾选「启用新闻早报草稿推送」后再推送'
: undefined
}
>
{newsDraftWorking ? '推送中…' : '立即推送到草稿箱'}
</button>
</div>
{!newsDraftForm.enabled ? (
<p className="muted" style={{ marginTop: 8 }}>
</p>
) : null}
{newsDraftDirty ? (
<p className="muted" style={{ marginTop: 8 }}>
</p>
) : null}
{newsDraftPreview ? (
<div className="admin-card" style={{ marginTop: 16, background: 'var(--panel-2, rgba(255,255,255,.03))' }}>
<h3 style={{ marginBottom: 8 }}></h3>
<dl className="admin-dl">
<div>
<dt></dt>
<dd>
<a href={newsDraftPreview.page.publicUrl} target="_blank" rel="noreferrer">
{newsDraftPreview.page.slug}
</a>
</dd>
</div>
<div>
<dt></dt>
<dd>{newsDraftPreview.article.title}</dd>
</div>
<div>
<dt></dt>
<dd>{newsDraftPreview.article.digest}</dd>
</div>
<div>
<dt></dt>
<dd>
{'contentMode' in newsDraftPreview.article && newsDraftPreview.article.contentMode === 'inline_html'
? `内联 HTML(公众号排版,非长图${newsDraftPreview.article.trimmed ? ',已自动裁剪' : ''}`
: 'HTML 正文'}
</dd>
</div>
{'contentLength' in newsDraftPreview.article && newsDraftPreview.article.contentLength ? (
<div>
<dt></dt>
<dd>{newsDraftPreview.article.contentLength.toLocaleString()} / 20,000</dd>
</div>
) : null}
{'cardCount' in newsDraftPreview.article ? (
<div>
<dt></dt>
<dd>{newsDraftPreview.article.cardCount}</dd>
</div>
) : null}
</dl>
</div>
) : null}
{newsDraftRuns.length > 0 ? (
<div style={{ marginTop: 16 }}>
<h3></h3>
<table className="admin-table">
<thead>
<tr>
<th></th>
<th></th>
<th></th>
<th>draft media_id</th>
<th></th>
</tr>
</thead>
<tbody>
{newsDraftRuns.map((run) => (
<tr key={run.id}>
<td>{dateLabel(run.createdAt)}</td>
<td className={statusClass(run.status)}>{statusLabel(run.status)}</td>
<td>{run.pageSlug ?? '—'}</td>
<td>{run.draftMediaId ?? '—'}</td>
<td className={run.errorMessage ? 'text-error' : 'muted'}>{run.errorMessage ?? '—'}</td>
</tr>
))}
</tbody>
</table>
</div>
) : null}
</section>
) : null}
{intentForm ? (
<section className="admin-card">
<div className="admin-card-head">
+48
View File
@@ -55,6 +55,10 @@ import type {
WechatDeliveryLog,
WechatDigestSubscription,
WechatScheduleLlmConfig,
WechatNewsMorningDraftConfig,
WechatNewsMorningDraftPreview,
WechatNewsMorningDraftRun,
WechatNewsMorningDraftTemplate,
WechatIntentRouterAdminConfig,
WechatIntentRouterConfigState,
WechatIntentRouterRuntimeState,
@@ -458,6 +462,50 @@ export async function updateWechatScheduleLlmConfig(
});
}
export async function getWechatNewsMorningDraftConfig(): Promise<{
config: WechatNewsMorningDraftConfig;
template: WechatNewsMorningDraftTemplate;
}> {
return portalFetch('/admin-api/wechat/news-morning-draft/config');
}
export async function patchWechatNewsMorningDraftConfig(
patch: Partial<Omit<WechatNewsMorningDraftConfig, 'updatedAt' | 'updatedBy' | 'templateVersion'>>,
): Promise<WechatNewsMorningDraftConfig> {
const result = await portalFetch<{ config: WechatNewsMorningDraftConfig }>(
'/admin-api/wechat/news-morning-draft/config',
{
method: 'PATCH',
body: JSON.stringify(patch),
},
);
return result.config;
}
export async function previewWechatNewsMorningDraft(): Promise<WechatNewsMorningDraftPreview> {
return portalFetch<WechatNewsMorningDraftPreview>('/admin-api/wechat/news-morning-draft/preview');
}
export async function pushWechatNewsMorningDraft(options?: { dryRun?: boolean }): Promise<{
ok?: boolean;
dryRun?: boolean;
draftMediaId?: string;
preview?: WechatNewsMorningDraftPreview;
run?: WechatNewsMorningDraftRun;
}> {
return portalFetch('/admin-api/wechat/news-morning-draft/push', {
method: 'POST',
body: JSON.stringify(options ?? {}),
});
}
export async function listWechatNewsMorningDraftRuns(limit = 20): Promise<WechatNewsMorningDraftRun[]> {
const result = await portalFetch<{ runs: WechatNewsMorningDraftRun[] }>(
`/admin-api/wechat/news-morning-draft/runs?limit=${encodeURIComponent(String(limit))}`,
);
return result.runs ?? [];
}
export async function getWechatIntentRouterConfig(): Promise<WechatIntentRouterAdminConfig> {
const result = await portalFetch<WechatIntentRouterConfigState>('/admin-api/wechat/intent-router/config');
return result.config;
+54
View File
@@ -330,6 +330,60 @@ export type WechatScheduleLlmConfig = {
updatedBy: string | null;
};
export type WechatNewsMorningDraftConfig = {
enabled: boolean;
autoPushEnabled: boolean;
pushHour: number;
pushMinute: number;
timezone: string;
sourceUserId: string | null;
pageSlugPattern: string;
author: string;
templateVersion: string;
updatedAt: number | null;
updatedBy: string | null;
};
export type WechatNewsMorningDraftTemplate = {
version: string;
spec: string;
};
export type WechatNewsMorningDraftPreview = {
config: WechatNewsMorningDraftConfig;
page: {
slug: string;
relativePath: string;
publicUrl: string;
modifiedAt: number;
hasThumb: boolean;
};
article: {
title: string;
digest: string;
content: string;
contentSourceUrl?: string;
contentMode?: 'inline_html' | 'html' | string;
contentLength?: number;
cardCount?: number;
statsCount?: number;
trimmed?: boolean;
trimLevel?: number;
};
template: WechatNewsMorningDraftTemplate;
};
export type WechatNewsMorningDraftRun = {
id: string;
status: string;
pageSlug: string | null;
pageUrl: string | null;
draftMediaId: string | null;
errorMessage: string | null;
triggeredBy: string | null;
createdAt: number;
};
export type WechatIntentRouterAdminConfig = {
enabled: boolean;
shadowMode: boolean;