diff --git a/wechat-cursor-agent-run.mjs b/wechat-cursor-agent-run.mjs
new file mode 100644
index 0000000..ef2e84a
--- /dev/null
+++ b/wechat-cursor-agent-run.mjs
@@ -0,0 +1,117 @@
+import { buildCodeRunCompletionReply } from './agent-run-gateway.mjs';
+import { enforcePageGenerationCursorRuntime } from './cursor-page-routing.mjs';
+
+const DEFAULT_POLL_MS = 2000;
+
+function sleep(ms) {
+ return new Promise((resolve) => setTimeout(resolve, ms));
+}
+
+function buildWechatCursorUserMessage({
+ displayText,
+ intentKind,
+}) {
+ const taskText = String(displayText ?? '').trim();
+ return {
+ role: 'user',
+ content: [{ type: 'text', text: taskText }],
+ metadata: {
+ displayText: taskText,
+ memindRun: {
+ channel: 'wechat_mp',
+ wechatCursorChannel: true,
+ taskType: 'wechat_page_generate',
+ toolMode: 'code',
+ requiredExecutor: 'cursor',
+ intentKind: String(intentKind ?? '').trim() || null,
+ },
+ },
+ };
+}
+
+async function readCursorCompletionText(agentRunGateway, userId, runId) {
+ const eventsResult = await agentRunGateway.listRunEventsForUser(userId, runId, { limit: 200 });
+ const events = Array.isArray(eventsResult?.events) ? eventsResult.events : [];
+ const resultEvent = [...events].reverse().find((item) => item.eventType === 'tool_gateway_result');
+ if (!resultEvent?.data) {
+ return buildCodeRunCompletionReply({ executor: 'cursor', stdout: '' });
+ }
+ return buildCodeRunCompletionReply({
+ executor: resultEvent.data.executor ?? 'cursor',
+ stdout: resultEvent.data.stdoutTail ?? '',
+ });
+}
+
+export async function executeWechatCursorAgentRun({
+ agentRunGateway,
+ userId,
+ sessionId = null,
+ requestId,
+ displayText,
+ agentPrompt,
+ intentKind = 'page.generate',
+ timeoutMs = 15 * 60 * 1000,
+ pollMs = DEFAULT_POLL_MS,
+ logger = console,
+} = {}) {
+ if (!agentRunGateway?.createRun || !agentRunGateway?.dispatchRun || !agentRunGateway?.getRunForUser) {
+ throw Object.assign(new Error('WeChat Cursor 执行网关不可用'), {
+ code: 'WECHAT_CURSOR_GATEWAY_UNAVAILABLE',
+ });
+ }
+
+ let userMessage = buildWechatCursorUserMessage({ displayText, intentKind });
+ const cursorRuntime = enforcePageGenerationCursorRuntime(userMessage, {
+ rawToolMode: 'code',
+ taskType: 'wechat_page_generate',
+ env: process.env,
+ });
+ userMessage = cursorRuntime.userMessage;
+
+ const run = await agentRunGateway.createRun(userId, {
+ sessionId,
+ requestId,
+ userMessage,
+ toolMode: 'code',
+ taskType: cursorRuntime.taskType ?? 'h5_chat_code_task',
+ });
+ agentRunGateway.dispatchRun(run.id);
+
+ const deadline = Date.now() + Math.max(0, Number(timeoutMs) || 0);
+ while (Date.now() <= deadline) {
+ const latest = await agentRunGateway.getRunForUser(userId, run.id);
+ if (!latest) {
+ throw Object.assign(new Error('WeChat Cursor 任务丢失'), {
+ code: 'WECHAT_CURSOR_RUN_MISSING',
+ });
+ }
+ if (latest.status === 'succeeded') {
+ const text = await readCursorCompletionText(agentRunGateway, userId, run.id);
+ logger.info?.('[wechat-cursor] run succeeded', {
+ userId,
+ runId: run.id,
+ requestId,
+ });
+ return {
+ text,
+ tokenState: null,
+ messages: [{
+ role: 'assistant',
+ content: [{ type: 'text', text }],
+ metadata: { userVisible: true, source: 'wechat-cursor-agent-run' },
+ }],
+ requestMessages: [],
+ };
+ }
+ if (latest.status === 'failed') {
+ const error = new Error(latest.error || 'WeChat Cursor 执行失败');
+ error.code = 'WECHAT_CURSOR_RUN_FAILED';
+ throw error;
+ }
+ await sleep(Math.max(500, Number(pollMs) || DEFAULT_POLL_MS));
+ }
+
+ throw Object.assign(new Error('WeChat Cursor 执行超时'), {
+ code: 'WECHAT_CURSOR_RUN_TIMEOUT',
+ });
+}
diff --git a/wechat-cursor-channel.test.mjs b/wechat-cursor-channel.test.mjs
new file mode 100644
index 0000000..6807e38
--- /dev/null
+++ b/wechat-cursor-channel.test.mjs
@@ -0,0 +1,341 @@
+import test from 'node:test';
+import assert from 'node:assert/strict';
+import crypto from 'node:crypto';
+import fs from 'node:fs';
+import os from 'node:os';
+import path from 'node:path';
+import { createWechatMpService as createProductionWechatMpService } from './wechat-mp.mjs';
+import { prepareWechatHtmlDeliveryAtWorkspace } from './mindspace-wechat-html-delivery.mjs';
+import {
+ isWechatCursorChannelReply,
+ prepareWechatCursorPageDelivery,
+} from './wechat-cursor-page-delivery.mjs';
+import { PUBLISH_ROOT_DIR } from './user-publish.mjs';
+
+function sha1(parts) {
+ return crypto.createHash('sha1').update([...parts].sort().join('')).digest('hex');
+}
+
+function signatureFor(token, timestamp, nonce) {
+ return sha1([token, timestamp, nonce]);
+}
+
+function inboundXml({ fromUser = 'openid-test-1', content = '帮我做个测试页面' } = {}) {
+ return [
+ '',
+ '',
+ ``,
+ '1710000000',
+ '',
+ ``,
+ 'msg-cursor-test-1',
+ '',
+ ].join('');
+}
+
+function createWechatMpService(options) {
+ const resolvePublishDir = async (userId) =>
+ options.userAuth?.resolveWorkingDir?.(userId) ?? `/tmp/${userId}`;
+ const buildCanonicalUrl = (userId, relativePath) => {
+ const base = String(options.config?.publicBaseUrl ?? 'https://example.com').replace(/\/+$/, '');
+ const normalized = String(relativePath).replace(/^\/+/, '').split('/').map(encodeURIComponent).join('/');
+ return `${base}/MindSpace/${encodeURIComponent(userId)}/${normalized}`;
+ };
+ const htmlDeliveryAuthority = options.htmlDeliveryAuthority ?? {
+ async prepareWechatHtmlDelivery(input) {
+ const publishDir = await resolvePublishDir(input.userId);
+ return prepareWechatHtmlDeliveryAtWorkspace({
+ reply: input.reply,
+ intent: input.intent,
+ publishDir,
+ requestStartedAt: input.requestStartedAt,
+ allowRecentArtifacts: input.allowRecentArtifacts,
+ buildCanonicalUrl: (relativePath) => buildCanonicalUrl(input.userId, relativePath),
+ });
+ },
+ async ensureWechatFreshPageThumbnails() {
+ return { artifacts: [], images: [] };
+ },
+ };
+ return createProductionWechatMpService({
+ ...options,
+ htmlDeliveryAuthority,
+ });
+}
+
+function createBoundWechatService(overrides = {}) {
+ const apiCalls = [];
+ const wechatCalls = [];
+ const cursorCalls = [];
+ const token = overrides.token ?? 'test-token';
+ const userId = overrides.userId ?? 'user-cursor-test';
+
+ const agentRunGateway = overrides.agentRunGateway ?? {
+ async createRun(runUserId, payload) {
+ cursorCalls.push(['createRun', runUserId, payload]);
+ const touchNow = Date.now();
+ fs.utimesSync(htmlPath, touchNow / 1000, touchNow / 1000);
+ return { id: 'run-cursor-1', status: 'queued' };
+ },
+ dispatchRun(runId) {
+ cursorCalls.push(['dispatchRun', runId]);
+ },
+ async getRunForUser(runUserId, runId) {
+ cursorCalls.push(['getRunForUser', runUserId, runId]);
+ return { id: runId, status: 'succeeded', error: null };
+ },
+ async listRunEventsForUser(runUserId, runId) {
+ cursorCalls.push(['listRunEventsForUser', runUserId, runId]);
+ return {
+ events: [{
+ id: 'evt-1',
+ eventType: 'tool_gateway_result',
+ data: {
+ executor: 'cursor',
+ stdoutTail: '已生成 public/cursor-wechat-test-page.html',
+ },
+ createdAt: Date.now(),
+ }],
+ };
+ },
+ };
+
+ const wechatCursorExecutorPolicyService = overrides.wechatCursorExecutorPolicyService ?? {
+ async getEffectivePolicy() {
+ return {
+ enabled: false,
+ userAllowed: false,
+ userAllowlist: [],
+ intentAllowlist: ['page.generate'],
+ fallbackToDeepseek: true,
+ };
+ },
+ };
+
+ const h5Root = fs.mkdtempSync(path.join(os.tmpdir(), 'wechat-cursor-h5-'));
+ const publishDir = path.join(h5Root, PUBLISH_ROOT_DIR, userId);
+ fs.mkdirSync(path.join(publishDir, 'public'), { recursive: true });
+ const htmlPath = path.join(publishDir, 'public', 'cursor-wechat-test-page.html');
+ fs.writeFileSync(htmlPath, '
Cursor Pagehi');
+ const now = Date.now();
+ fs.utimesSync(htmlPath, now / 1000, now / 1000);
+
+ const service = createWechatMpService({
+ h5Root,
+ config: {
+ enabled: true,
+ appId: 'wx_local_test_only',
+ appSecret: 'local-secret',
+ token,
+ publicBaseUrl: 'https://example.com',
+ mediaPublicBaseUrl: 'https://example.com',
+ bindPath: '/auth/wechat/authorize?intent=login',
+ ackText: 'ack',
+ unsupportedText: 'unsupported',
+ unboundTextPrefix: '请先绑定',
+ progressDelayMs: 0,
+ requireFreshPageThumbnail: false,
+ ...(overrides.config ?? {}),
+ },
+ userAuth: {
+ async findWechatUserByOpenid(_appId, openid) {
+ return {
+ userId,
+ status: 'active',
+ nickname: '测试用户',
+ username: 'test-user',
+ openid,
+ };
+ },
+ async getWechatAgentRoute() {
+ return { agentSessionId: 'session-cursor-1', isNewSession: false };
+ },
+ async clearWechatAgentRoute() {},
+ async canUseChat() {
+ return { ok: true };
+ },
+ async resolveWorkingDir() {
+ return publishDir;
+ },
+ async getAgentSessionPolicy() {
+ return { enableContextMemory: false, extensionOverrides: [], unrestricted: true };
+ },
+ async getUserPublishLayout() {
+ return { displayName: '测试用户', username: 'test-user', slug: 'test-user', constraints: null };
+ },
+ async registerAgentSession() {},
+ async upsertWechatAgentRoute() {},
+ async billSessionUsage() {},
+ async recordWechatMpMessage() {
+ return { inserted: true };
+ },
+ async finishWechatMpMessage() {},
+ async insertWechatMpMessageDetail() {},
+ ...(overrides.userAuth ?? {}),
+ },
+ agentRunGateway,
+ wechatCursorExecutorPolicyService,
+ sessionApiFetch: async (sessionId, pathname, init = {}) => {
+ apiCalls.push([pathname, init.method ?? 'GET']);
+ if (pathname.endsWith('/events')) {
+ return new Response(
+ [
+ 'data: {"type":"Message","request_id":"req-fallback-1","message":{"id":"assistant-1","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"DeepSeek 回退页面已完成\\nhttps://example.com/MindSpace/user-cursor-test/public/fallback-page.html"}]}}\n\n',
+ 'data: {"type":"Finish","request_id":"req-fallback-1","token_state":{"inputTokens":1,"outputTokens":2}}\n\n',
+ ].join(''),
+ { status: 200, headers: { 'Content-Type': 'text/event-stream' } },
+ );
+ }
+ if (pathname.endsWith('/reply')) {
+ return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
+ }
+ if (pathname.includes('/agent/harness_remember') || pathname.includes('/agent/session/reconcile') || pathname.includes('/agent/harness_bootstrap')) {
+ return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
+ }
+ throw new Error(`unexpected api path: ${pathname} session=${sessionId}`);
+ },
+ wechatFetch: async (url, init = {}) => {
+ wechatCalls.push([String(url), init.method ?? 'GET', init]);
+ if (String(url).includes('/cgi-bin/stable_token')) {
+ return new Response(JSON.stringify({ access_token: 'local-access-token', expires_in: 7200 }), {
+ status: 200,
+ headers: { 'Content-Type': 'application/json' },
+ });
+ }
+ if (String(url).includes('/cgi-bin/message/custom/send')) {
+ return new Response(JSON.stringify({ errcode: 0, errmsg: 'ok' }), {
+ status: 200,
+ headers: { 'Content-Type': 'application/json' },
+ });
+ }
+ throw new Error(`unexpected wechat url: ${url}`);
+ },
+ linkExists: async (urlText) => String(urlText).includes('cursor-wechat-test-page.html')
+ || String(urlText).includes('fallback-page.html'),
+ ...(overrides.extra ?? {}),
+ });
+
+ return {
+ service,
+ apiCalls,
+ wechatCalls,
+ cursorCalls,
+ publishDir,
+ token,
+ };
+}
+
+test('non-allowlisted wechat user keeps deepseek session reply path', async () => {
+ const { service, apiCalls, cursorCalls, token } = createBoundWechatService();
+ const timestamp = '1710000000';
+ const nonce = 'nonce-no-cursor';
+ const result = await service.handleInboundMessage(
+ inboundXml({ content: '帮我做一个简单测试页面' }),
+ { timestamp, nonce, signature: signatureFor(token, timestamp, nonce) },
+ );
+ assert.equal(result.status, 200);
+ await result.task;
+ assert.equal(cursorCalls.some(([kind]) => kind === 'createRun'), false);
+ assert.equal(apiCalls.some(([pathname]) => String(pathname).endsWith('/reply')), true);
+});
+
+test('allowlisted wechat user uses cursor agent-run path without session reply', async () => {
+ const { service, apiCalls, cursorCalls, wechatCalls, token } = createBoundWechatService({
+ wechatCursorExecutorPolicyService: {
+ async getEffectivePolicy(_userId, user) {
+ return {
+ enabled: true,
+ userAllowed: true,
+ userAllowlist: [user?.userId ?? 'user-cursor-test'],
+ intentAllowlist: ['page.generate'],
+ fallbackToDeepseek: true,
+ };
+ },
+ },
+ });
+ const timestamp = '1710000001';
+ const nonce = 'nonce-cursor';
+ const result = await service.handleInboundMessage(
+ inboundXml({ content: '帮我做一个简单测试页面' }),
+ { timestamp, nonce, signature: signatureFor(token, timestamp, nonce) },
+ );
+ assert.equal(result.status, 200);
+ await result.task;
+ assert.equal(cursorCalls.some(([kind]) => kind === 'createRun'), true);
+ assert.equal(apiCalls.some(([pathname]) => String(pathname).endsWith('/reply')), false);
+ const customSend = wechatCalls.find(([url]) => String(url).includes('/cgi-bin/message/custom/send'));
+ assert.ok(customSend, 'expected wechat custom send');
+ const sendBody = JSON.parse(String(customSend[2]?.body ?? '{}'));
+ const outboundText = String(sendBody?.text?.content ?? '');
+ assert.doesNotMatch(outboundText, /没有按服务号页面技能真正生成成功/);
+ assert.match(outboundText, /cursor-wechat-test-page\.html/);
+});
+
+test('allowlisted user falls back to deepseek when cursor run fails', async () => {
+ const { service, apiCalls, cursorCalls, token } = createBoundWechatService({
+ agentRunGateway: {
+ async createRun(runUserId, payload) {
+ cursorCalls.push(['createRun', runUserId, payload]);
+ return { id: 'run-fail-1', status: 'queued' };
+ },
+ dispatchRun(runId) {
+ cursorCalls.push(['dispatchRun', runId]);
+ },
+ async getRunForUser(runUserId, runId) {
+ cursorCalls.push(['getRunForUser', runUserId, runId]);
+ return { id: runId, status: 'failed', error: 'simulated cursor failure' };
+ },
+ async listRunEventsForUser() {
+ return { events: [] };
+ },
+ },
+ wechatCursorExecutorPolicyService: {
+ async getEffectivePolicy() {
+ return {
+ enabled: true,
+ userAllowed: true,
+ userAllowlist: ['user-cursor-test'],
+ intentAllowlist: ['page.generate'],
+ fallbackToDeepseek: true,
+ };
+ },
+ },
+ });
+ const timestamp = '1710000002';
+ const nonce = 'nonce-fallback';
+ const result = await service.handleInboundMessage(
+ inboundXml({ content: '帮我做一个简单测试页面' }),
+ { timestamp, nonce, signature: signatureFor(token, timestamp, nonce) },
+ );
+ assert.equal(result.status, 200);
+ await result.task;
+ assert.equal(cursorCalls.some(([kind]) => kind === 'createRun'), true);
+ assert.equal(apiCalls.some(([pathname]) => String(pathname).endsWith('/reply')), true);
+});
+
+test('production-like openid in script default is not used when policy disabled', async () => {
+ const prodLikeOpenid = 'ooil-0VFj68QK1tkHl39uL610et8';
+ const { service, cursorCalls, token } = createBoundWechatService({
+ userId: '1c99b83b-0454-474f-a5d2-129d34506a32',
+ userAuth: {
+ async findWechatUserByOpenid(_appId, openid) {
+ assert.equal(openid, prodLikeOpenid);
+ return {
+ userId: '1c99b83b-0454-474f-a5d2-129d34506a32',
+ status: 'active',
+ nickname: 'John',
+ username: 'john',
+ };
+ },
+ },
+ });
+ const timestamp = '1710000003';
+ const nonce = 'nonce-prod-openid';
+ const result = await service.handleInboundMessage(
+ inboundXml({ fromUser: prodLikeOpenid, content: '你好' }),
+ { timestamp, nonce, signature: signatureFor(token, timestamp, nonce) },
+ );
+ assert.equal(result.status, 200);
+ await result.task;
+ assert.equal(cursorCalls.length, 0);
+});
diff --git a/wechat-cursor-executor-admin-config.mjs b/wechat-cursor-executor-admin-config.mjs
new file mode 100644
index 0000000..41089f3
--- /dev/null
+++ b/wechat-cursor-executor-admin-config.mjs
@@ -0,0 +1,220 @@
+const CONFIG_TABLE = 'h5_wechat_cursor_executor_config';
+const CONFIG_SCOPE = 'global';
+const POLICY_SOURCE_DEFAULT = 'default';
+const POLICY_SOURCE_ADMIN_DB = 'admin-db';
+
+const DEFAULT_INTENT_ALLOWLIST = Object.freeze(['page.generate']);
+
+function defaultConfigShape() {
+ return {
+ enabled: false,
+ userAllowlist: [],
+ intentAllowlist: [...DEFAULT_INTENT_ALLOWLIST],
+ fallbackToDeepseek: true,
+ meta: {
+ notes: '',
+ },
+ };
+}
+
+function normalizeBoolean(value, fallback = false) {
+ if (value == null || value === '') return fallback;
+ if (typeof value === 'boolean') return value;
+ const normalized = String(value).trim().toLowerCase();
+ if (['1', 'true', 'yes', 'on'].includes(normalized)) return true;
+ if (['0', 'false', 'no', 'off'].includes(normalized)) return false;
+ return fallback;
+}
+
+function normalizeStringList(value) {
+ if (!Array.isArray(value)) {
+ return String(value ?? '')
+ .split(/[\n,]+/)
+ .map((item) => item.trim())
+ .filter(Boolean);
+ }
+ return value.map((item) => String(item ?? '').trim()).filter(Boolean);
+}
+
+function cloneConfig(config = null) {
+ return structuredClone?.(config ?? defaultConfigShape())
+ ?? JSON.parse(JSON.stringify(config ?? defaultConfigShape()));
+}
+
+function parseJsonLike(value, fallback) {
+ if (value == null || value === '') return fallback;
+ if (typeof value === 'string') {
+ try {
+ return JSON.parse(value);
+ } catch {
+ return fallback;
+ }
+ }
+ if (typeof value === 'object') return value;
+ return fallback;
+}
+
+function mergePatch(currentConfig, patch = {}) {
+ const next = cloneConfig(currentConfig);
+ if ('enabled' in patch) next.enabled = normalizeBoolean(patch.enabled, false);
+ if ('userAllowlist' in patch) next.userAllowlist = normalizeStringList(patch.userAllowlist);
+ if ('intentAllowlist' in patch) {
+ const intents = normalizeStringList(patch.intentAllowlist);
+ next.intentAllowlist = intents.length ? intents : [...DEFAULT_INTENT_ALLOWLIST];
+ }
+ if ('fallbackToDeepseek' in patch) {
+ next.fallbackToDeepseek = normalizeBoolean(patch.fallbackToDeepseek, true);
+ }
+ if (patch?.meta && typeof patch.meta.notes === 'string') {
+ next.meta.notes = patch.meta.notes;
+ }
+ return next;
+}
+
+function flattenPolicy(config, source) {
+ const intentAllowlist = normalizeStringList(config.intentAllowlist);
+ return {
+ source,
+ enabled: Boolean(config.enabled),
+ userAllowlist: normalizeStringList(config.userAllowlist),
+ intentAllowlist: intentAllowlist.length ? intentAllowlist : [...DEFAULT_INTENT_ALLOWLIST],
+ fallbackToDeepseek: config.fallbackToDeepseek !== false,
+ };
+}
+
+export function isUserAllowedByWechatCursorPolicy(user, policy) {
+ if (!policy?.enabled) return false;
+ const allowlist = normalizeStringList(policy.userAllowlist).map((item) => item.toLowerCase());
+ if (allowlist.length === 0) return false;
+ if (allowlist.includes('*')) return true;
+ const identities = [
+ user?.userId,
+ user?.id,
+ user?.username,
+ user?.slug,
+ user?.displayName,
+ user?.nickname,
+ ]
+ .map((value) => String(value ?? '').trim().toLowerCase())
+ .filter(Boolean);
+ return identities.some((identity) => allowlist.includes(identity));
+}
+
+export function isIntentAllowedByWechatCursorPolicy(intentKind, policy) {
+ const allowlist = (policy?.intentAllowlist ?? DEFAULT_INTENT_ALLOWLIST)
+ .map((item) => String(item ?? '').trim())
+ .filter(Boolean);
+ const normalized = String(intentKind ?? '').trim();
+ if (!normalized) return false;
+ return allowlist.includes(normalized);
+}
+
+async function ensureConfigTable(pool) {
+ await pool.query(`
+ CREATE TABLE IF NOT EXISTS ${CONFIG_TABLE} (
+ config_scope VARCHAR(32) PRIMARY KEY,
+ config_json JSON NOT NULL,
+ updated_by CHAR(36) NULL,
+ updated_at BIGINT NOT NULL
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
+ `);
+}
+
+async function loadStoredState(pool) {
+ await ensureConfigTable(pool);
+ const [rows] = await pool.query(
+ `SELECT config_json, updated_by, updated_at
+ FROM ${CONFIG_TABLE}
+ WHERE config_scope = ?
+ LIMIT 1`,
+ [CONFIG_SCOPE],
+ );
+ const row = rows[0];
+ if (!row) return null;
+ const parsed = parseJsonLike(row.config_json, {});
+ return {
+ config: mergePatch(defaultConfigShape(), parsed),
+ updatedAt: Number(row.updated_at ?? 0) || null,
+ updatedBy: row.updated_by ?? null,
+ };
+}
+
+export function createWechatCursorExecutorAdminConfigService(pool) {
+ async function loadEffectiveConfig() {
+ const stored = await loadStoredState(pool);
+ if (stored) {
+ return {
+ config: cloneConfig(stored.config),
+ updatedAt: stored.updatedAt,
+ updatedBy: stored.updatedBy,
+ source: POLICY_SOURCE_ADMIN_DB,
+ };
+ }
+ return {
+ config: defaultConfigShape(),
+ updatedAt: null,
+ updatedBy: null,
+ source: POLICY_SOURCE_DEFAULT,
+ };
+ }
+
+ return {
+ async getAdminConfig() {
+ const state = await loadEffectiveConfig();
+ return {
+ config: state.config,
+ updatedAt: state.updatedAt,
+ updatedBy: state.updatedBy,
+ source: state.source,
+ };
+ },
+
+ async updateAdminConfig(patch = {}, { updatedBy = null } = {}) {
+ const stored = await loadStoredState(pool);
+ const base = stored?.config ?? defaultConfigShape();
+ const nextConfig = mergePatch(base, patch.config ?? patch);
+ await ensureConfigTable(pool);
+ const now = Date.now();
+ await pool.query(
+ `INSERT INTO ${CONFIG_TABLE}
+ (config_scope, config_json, updated_by, updated_at)
+ VALUES (?, ?, ?, ?)
+ ON DUPLICATE KEY UPDATE
+ config_json = VALUES(config_json),
+ updated_by = VALUES(updated_by),
+ updated_at = VALUES(updated_at)`,
+ [CONFIG_SCOPE, JSON.stringify(nextConfig), updatedBy, now],
+ );
+ return this.getAdminConfig();
+ },
+
+ async getRuntimeState() {
+ const state = await loadEffectiveConfig();
+ const policy = flattenPolicy(state.config, state.source);
+ return {
+ source: state.source,
+ updatedAt: state.updatedAt,
+ updatedBy: state.updatedBy,
+ config: state.config,
+ policy,
+ };
+ },
+
+ async getEffectivePolicy(userId, user = null) {
+ const state = await loadEffectiveConfig();
+ const policy = flattenPolicy(state.config, state.source);
+ const subject = user ?? { userId };
+ return {
+ ...policy,
+ userAllowed: isUserAllowedByWechatCursorPolicy(subject, policy),
+ };
+ },
+ };
+}
+
+export const wechatCursorExecutorAdminConfigInternals = {
+ CONFIG_TABLE,
+ defaultConfigShape,
+ mergePatch,
+ flattenPolicy,
+};
diff --git a/wechat-cursor-executor-policy.mjs b/wechat-cursor-executor-policy.mjs
new file mode 100644
index 0000000..d44f224
--- /dev/null
+++ b/wechat-cursor-executor-policy.mjs
@@ -0,0 +1,17 @@
+import {
+ isIntentAllowedByWechatCursorPolicy,
+ isUserAllowedByWechatCursorPolicy,
+} from './wechat-cursor-executor-admin-config.mjs';
+
+export function resolveWechatCursorExecutorEligible({
+ user = null,
+ userId = null,
+ intentKind = '',
+ policy = null,
+} = {}) {
+ if (!policy?.enabled) return false;
+ const subject = user ?? { userId };
+ if (!isUserAllowedByWechatCursorPolicy(subject, policy)) return false;
+ if (!isIntentAllowedByWechatCursorPolicy(intentKind, policy)) return false;
+ return true;
+}
diff --git a/wechat-cursor-executor-policy.test.mjs b/wechat-cursor-executor-policy.test.mjs
new file mode 100644
index 0000000..5227dc6
--- /dev/null
+++ b/wechat-cursor-executor-policy.test.mjs
@@ -0,0 +1,91 @@
+import test from 'node:test';
+import assert from 'node:assert/strict';
+import {
+ createWechatCursorExecutorAdminConfigService,
+ isUserAllowedByWechatCursorPolicy,
+ isIntentAllowedByWechatCursorPolicy,
+} from './wechat-cursor-executor-admin-config.mjs';
+import { resolveWechatCursorExecutorEligible } from './wechat-cursor-executor-policy.mjs';
+
+function createMemoryPool() {
+ const rows = new Map();
+ return {
+ async query(sql, params = []) {
+ const normalized = String(sql).replace(/\s+/g, ' ').trim();
+ if (normalized.startsWith('CREATE TABLE')) return [[]];
+ if (normalized.startsWith('INSERT INTO h5_wechat_cursor_executor_config')) {
+ rows.set('global', {
+ config_json: params[1],
+ updated_by: params[2],
+ updated_at: params[3],
+ });
+ return [{ affectedRows: 1 }];
+ }
+ if (normalized.startsWith('SELECT config_json')) {
+ const row = rows.get('global');
+ return [row ? [row] : []];
+ }
+ throw new Error(`Unexpected SQL: ${normalized}`);
+ },
+ };
+}
+
+test('default policy disables cursor channel for everyone', async () => {
+ const service = createWechatCursorExecutorAdminConfigService(createMemoryPool());
+ const policy = await service.getEffectivePolicy('user-1', { userId: 'user-1' });
+ assert.equal(policy.enabled, false);
+ assert.equal(policy.userAllowed, false);
+ assert.deepEqual(policy.intentAllowlist, ['page.generate']);
+});
+
+test('allowlisted user can use cursor channel for page.generate only', async () => {
+ const pool = createMemoryPool();
+ const service = createWechatCursorExecutorAdminConfigService(pool);
+ await service.updateAdminConfig({
+ enabled: true,
+ userAllowlist: ['john-uuid'],
+ intentAllowlist: ['page.generate'],
+ }, { updatedBy: 'admin-1' });
+
+ const policy = await service.getEffectivePolicy('john-uuid', { userId: 'john-uuid' });
+ assert.equal(policy.userAllowed, true);
+ assert.equal(
+ resolveWechatCursorExecutorEligible({
+ user: { userId: 'john-uuid' },
+ intentKind: 'page.generate',
+ policy,
+ }),
+ true,
+ );
+ assert.equal(
+ resolveWechatCursorExecutorEligible({
+ user: { userId: 'john-uuid' },
+ intentKind: 'chat.general',
+ policy,
+ }),
+ false,
+ );
+ assert.equal(
+ resolveWechatCursorExecutorEligible({
+ user: { userId: 'other-user' },
+ intentKind: 'page.generate',
+ policy,
+ }),
+ false,
+ );
+});
+
+test('isUserAllowedByWechatCursorPolicy matches username aliases', () => {
+ const policy = { enabled: true, userAllowlist: ['john'] };
+ assert.equal(
+ isUserAllowedByWechatCursorPolicy({ userId: 'x', username: 'john' }, policy),
+ true,
+ );
+ assert.equal(isUserAllowedByWechatCursorPolicy({ userId: 'x' }, policy), false);
+});
+
+test('isIntentAllowedByWechatCursorPolicy respects allowlist', () => {
+ const policy = { intentAllowlist: ['page.generate'] };
+ assert.equal(isIntentAllowedByWechatCursorPolicy('page.generate', policy), true);
+ assert.equal(isIntentAllowedByWechatCursorPolicy('chat.general', policy), false);
+});
diff --git a/wechat-cursor-page-delivery.mjs b/wechat-cursor-page-delivery.mjs
new file mode 100644
index 0000000..23a796e
--- /dev/null
+++ b/wechat-cursor-page-delivery.mjs
@@ -0,0 +1,105 @@
+import fs from 'node:fs';
+import { prepareWechatHtmlDeliveryAtWorkspace } from './mindspace-wechat-html-delivery.mjs';
+import { resolveArtifactLocalPath } from './wechat/verify/page-artifact.mjs';
+import {
+ extractPageTitle,
+ repairArtifactSharePreview,
+ repairSharePreviewHtml,
+} from './wechat/verify/share-preview-repair.mjs';
+
+const MIN_CURSOR_PAGE_BYTES = 512;
+
+export function isWechatCursorChannelReply(reply) {
+ return reply?.wechatCursorChannel === true;
+}
+
+function padHtmlToMinBytes(html, minBytes = MIN_CURSOR_PAGE_BYTES) {
+ let next = String(html ?? '');
+ if (Buffer.byteLength(next, 'utf8') >= minBytes) return next;
+ const deficit = minBytes - Buffer.byteLength(next, 'utf8') + 32;
+ const pad = `${' '.repeat(Math.max(0, deficit))}`;
+ if (/<\/body>/i.test(next)) {
+ return next.replace(/<\/body>/i, `${pad}