fix(page-data): deliver MindSpace workspace URLs instead of /u/.../pages/ routes.
Page Data questionnaires were returning publication slugs to users; password-mode pages always fell back to /u/ routes. Prefer MindSpace paths at publish time and rewrite mistaken delivery links in WeChat and Portal chat. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,531 @@
|
||||
import crypto from 'node:crypto';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { isPageDataIntent } from './chat-skills.mjs';
|
||||
import { detectPageDataDatasetUsageFromHtml } from './page-data-html-detect.mjs';
|
||||
import { bindWorkspaceHtmlForPageData } from './page-data-workspace-bind.mjs';
|
||||
import { listPageAccessPolicies } from './page-data-policy-store.mjs';
|
||||
import { buildPublicUrl, resolvePublicBaseUrl } from './user-publish.mjs';
|
||||
|
||||
const PUBLICATION_ROUTE_LINK_PATTERN =
|
||||
/https?:\/\/[^\s<>"')\]]+\/u\/([0-9a-f-]{36}|[a-z0-9._-]+)\/pages\/([^\s<>"')\]]+)/gi;
|
||||
|
||||
const PAGE_DATA_CLIENT_SCRIPT_PATTERN = /\/assets\/page-data-client\.js/i;
|
||||
const LOCAL_STORAGE_DATA_PATTERN = /localStorage\.(?:getItem|setItem)\s*\(/i;
|
||||
const LOCAL_STORAGE_FALLBACK_HINT_PATTERN = /fallback\s*到\s*localStorage|localStorage\s*fallback/i;
|
||||
|
||||
const repairAttemptsBySession = new Map();
|
||||
|
||||
function envFlag(value, fallback = false) {
|
||||
const raw = String(value ?? '').trim().toLowerCase();
|
||||
if (!raw) return fallback;
|
||||
return ['1', 'true', 'yes', 'on'].includes(raw);
|
||||
}
|
||||
|
||||
function readPublicHtmlFiles(publishDir) {
|
||||
const publicDir = path.join(path.resolve(String(publishDir ?? '')), 'public');
|
||||
if (!fs.existsSync(publicDir) || !fs.statSync(publicDir).isDirectory()) return [];
|
||||
return fs
|
||||
.readdirSync(publicDir)
|
||||
.filter((name) => name.toLowerCase().endsWith('.html'))
|
||||
.map((name) => {
|
||||
const relativePath = `public/${name}`;
|
||||
const absolutePath = path.join(publicDir, name);
|
||||
const content = fs.readFileSync(absolutePath, 'utf8');
|
||||
return { relativePath, absolutePath, content };
|
||||
});
|
||||
}
|
||||
|
||||
export function collectPageDataDeliveryArtifacts(
|
||||
publishDir,
|
||||
{ publicBaseUrl = resolvePublicBaseUrl() } = {},
|
||||
) {
|
||||
const ownerKey = path.basename(path.resolve(String(publishDir ?? '')));
|
||||
if (!ownerKey) return [];
|
||||
return collectPageDataPublicHtmlFiles(publishDir)
|
||||
.map((file) => ({
|
||||
localPath: file.absolutePath,
|
||||
relativePath: file.relativePath,
|
||||
isAdmin: /-admin\.html$/i.test(file.relativePath),
|
||||
url: buildPublicUrl(publicBaseUrl, ownerKey, file.relativePath),
|
||||
}));
|
||||
}
|
||||
|
||||
function pickPublicationRouteReplacement(slug, artifacts, { adminUsed, surveyIndex }) {
|
||||
const normalizedSlug = String(slug ?? '').toLowerCase();
|
||||
const adminArtifact = artifacts.find((artifact) => artifact.isAdmin);
|
||||
const surveyArtifacts = artifacts.filter((artifact) => !artifact.isAdmin);
|
||||
if (adminArtifact && /admin/.test(normalizedSlug) && !adminUsed.value) {
|
||||
adminUsed.value = true;
|
||||
return adminArtifact.url;
|
||||
}
|
||||
if (surveyArtifacts[surveyIndex.value]) {
|
||||
surveyIndex.value += 1;
|
||||
return surveyArtifacts[surveyIndex.value - 1].url;
|
||||
}
|
||||
if (adminArtifact && !adminUsed.value) {
|
||||
adminUsed.value = true;
|
||||
return adminArtifact.url;
|
||||
}
|
||||
return artifacts[0]?.url ?? null;
|
||||
}
|
||||
|
||||
/** 将 Agent 误发的 /u/用户名/pages/... 链接改写为 MindSpace 工作区 URL。 */
|
||||
export function rewritePageDataDeliveryLinks(text, artifacts = []) {
|
||||
const value = String(text ?? '');
|
||||
if (!value || !Array.isArray(artifacts) || artifacts.length === 0) return value;
|
||||
const adminUsed = { value: false };
|
||||
const surveyIndex = { value: 0 };
|
||||
return value.replace(PUBLICATION_ROUTE_LINK_PATTERN, (match, _owner, slug) => {
|
||||
const replacement = pickPublicationRouteReplacement(slug, artifacts, { adminUsed, surveyIndex });
|
||||
return replacement ?? match;
|
||||
});
|
||||
}
|
||||
|
||||
export function buildPageDataDeliveryArtifactsFromBindResult(autoBind, publishDir, options = {}) {
|
||||
const boundUrls = new Map(
|
||||
(autoBind?.bound ?? [])
|
||||
.filter((item) => item?.relativePath && item?.workspaceUrl)
|
||||
.map((item) => [item.relativePath, item.workspaceUrl]),
|
||||
);
|
||||
return collectPageDataDeliveryArtifacts(publishDir, options).map((artifact) => ({
|
||||
...artifact,
|
||||
url: boundUrls.get(artifact.relativePath) ?? artifact.url,
|
||||
}));
|
||||
}
|
||||
|
||||
export function evaluatePageDataHtmlContent(html, { relativePath = '' } = {}) {
|
||||
const content = String(html ?? '');
|
||||
const usage = detectPageDataDatasetUsageFromHtml(content);
|
||||
const usesPageDataApi =
|
||||
usage.size > 0 ||
|
||||
PAGE_DATA_CLIENT_SCRIPT_PATTERN.test(content) ||
|
||||
/\bMindSpacePageData\b/.test(content);
|
||||
|
||||
if (!usesPageDataApi) {
|
||||
return { usesPageDataApi: false, issues: [] };
|
||||
}
|
||||
|
||||
const issues = [];
|
||||
if (!PAGE_DATA_CLIENT_SCRIPT_PATTERN.test(content)) {
|
||||
issues.push('missing_page_data_client_script');
|
||||
}
|
||||
if (LOCAL_STORAGE_DATA_PATTERN.test(content)) {
|
||||
issues.push('forbidden_local_storage');
|
||||
}
|
||||
if (LOCAL_STORAGE_FALLBACK_HINT_PATTERN.test(content)) {
|
||||
issues.push('forbidden_local_storage_fallback');
|
||||
}
|
||||
if (usage.size > 0 && !/\bMindSpacePageData\b/.test(content)) {
|
||||
issues.push('missing_page_data_client_api');
|
||||
}
|
||||
if (relativePath && usage.size > 0) {
|
||||
const hasInsert = [...usage.values()].some((item) => item.insert);
|
||||
const hasRead = [...usage.values()].some((item) => item.read);
|
||||
const looksAdmin = /-admin\.html$/i.test(relativePath);
|
||||
if (looksAdmin && hasInsert && !hasRead) {
|
||||
issues.push('admin_page_should_not_insert_only');
|
||||
}
|
||||
if (!looksAdmin && hasRead && !hasInsert) {
|
||||
issues.push('survey_page_should_not_be_read_only');
|
||||
}
|
||||
}
|
||||
|
||||
return { usesPageDataApi: true, issues, usage };
|
||||
}
|
||||
|
||||
export function collectPageDataPublicHtmlFiles(publishDir) {
|
||||
return readPublicHtmlFiles(publishDir)
|
||||
.map((file) => ({
|
||||
...file,
|
||||
evaluation: evaluatePageDataHtmlContent(file.content, { relativePath: file.relativePath }),
|
||||
}))
|
||||
.filter((file) => file.evaluation.usesPageDataApi);
|
||||
}
|
||||
|
||||
export function inferPageDataBindAccessMode(relativePath, html) {
|
||||
const usage = detectPageDataDatasetUsageFromHtml(html);
|
||||
const hasRead = [...usage.values()].some((item) => item.read);
|
||||
const hasInsert = [...usage.values()].some((item) => item.insert);
|
||||
if (/-admin\.html$/i.test(String(relativePath ?? '')) || (hasRead && !hasInsert)) {
|
||||
return 'password';
|
||||
}
|
||||
return 'public';
|
||||
}
|
||||
|
||||
function pageHasBoundPolicy({ publishDir, relativePath, html }) {
|
||||
const usage = detectPageDataDatasetUsageFromHtml(html);
|
||||
if (!usage.size) return true;
|
||||
const policies = listPageAccessPolicies(publishDir);
|
||||
if (!policies.length) return false;
|
||||
const htmlDatasetNames = [...usage.keys()].sort().join(',');
|
||||
return policies.some((policy) => {
|
||||
const policyNames = Object.keys(policy?.datasets ?? {}).sort().join(',');
|
||||
return policyNames === htmlDatasetNames;
|
||||
});
|
||||
}
|
||||
|
||||
export function usedPageDataCollectSkill(messages = []) {
|
||||
return messages.some((message) =>
|
||||
message?.content?.some((item) => {
|
||||
if (item?.type !== 'toolRequest') return false;
|
||||
const toolCall = item.toolCall?.value;
|
||||
const name = String(toolCall?.name ?? '').trim();
|
||||
const args = toolCall?.arguments ?? {};
|
||||
const skillName = String(args.name ?? '').trim();
|
||||
return name === 'load_skill' && skillName === 'page-data-collect';
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export function usedPageDataBindTool(messages = []) {
|
||||
return messages.some((message) =>
|
||||
message?.content?.some((item) => {
|
||||
if (item?.type !== 'toolRequest') return false;
|
||||
const name = String(item.toolCall?.value?.name ?? '').trim();
|
||||
return name === 'private_data_bind_workspace_page';
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export function extractRecentPageDataHtmlWrites(messages = [], { sinceMs = 0 } = {}) {
|
||||
const targets = new Set();
|
||||
for (const message of Array.isArray(messages) ? messages : []) {
|
||||
const createdAt = Number(message?.created ?? message?.createdAt ?? 0);
|
||||
if (sinceMs > 0 && createdAt > 0 && createdAt < sinceMs) continue;
|
||||
for (const item of message?.content ?? []) {
|
||||
if (item?.type !== 'toolRequest') continue;
|
||||
const toolCall = item.toolCall?.value;
|
||||
const name = String(toolCall?.name ?? '').trim();
|
||||
if (!['write_file', 'edit_file', 'write', 'edit'].includes(name)) continue;
|
||||
const args = toolCall?.arguments ?? {};
|
||||
const candidate = String(args.path ?? args.file_path ?? '').trim().replace(/\\/g, '/');
|
||||
if (!candidate.toLowerCase().endsWith('.html')) continue;
|
||||
const normalized = candidate.startsWith('public/') ? candidate : path.posix.basename(candidate);
|
||||
if (normalized.startsWith('public/')) targets.add(normalized);
|
||||
}
|
||||
}
|
||||
return [...targets];
|
||||
}
|
||||
|
||||
export function evaluatePageDataFinishGuard({
|
||||
publishDir,
|
||||
agentText = '',
|
||||
messages = [],
|
||||
requestStartedAt = 0,
|
||||
} = {}) {
|
||||
const pageDataIntent = isPageDataIntent(agentText);
|
||||
const pageDataFiles = collectPageDataPublicHtmlFiles(publishDir);
|
||||
const recentWrites = extractRecentPageDataHtmlWrites(messages, { sinceMs: requestStartedAt });
|
||||
const relevantFiles = pageDataFiles.filter((file) =>
|
||||
pageDataIntent || recentWrites.includes(file.relativePath),
|
||||
);
|
||||
|
||||
const htmlIssues = relevantFiles.flatMap((file) =>
|
||||
file.evaluation.issues.map((issue) => ({
|
||||
issue,
|
||||
relativePath: file.relativePath,
|
||||
})),
|
||||
);
|
||||
|
||||
const unboundFiles = relevantFiles.filter(
|
||||
(file) =>
|
||||
file.evaluation.usage?.size > 0 &&
|
||||
!pageHasBoundPolicy({
|
||||
publishDir,
|
||||
relativePath: file.relativePath,
|
||||
html: file.content,
|
||||
}),
|
||||
);
|
||||
|
||||
const needsRepair =
|
||||
pageDataIntent &&
|
||||
(htmlIssues.length > 0 ||
|
||||
unboundFiles.length > 0 ||
|
||||
(relevantFiles.length === 0 && extractRecentPageDataHtmlWrites(messages, { sinceMs: requestStartedAt }).length === 0 && usedPageDataCollectSkill(messages)));
|
||||
|
||||
return {
|
||||
pageDataIntent,
|
||||
relevantFiles,
|
||||
htmlIssues,
|
||||
unboundFiles,
|
||||
needsRepair,
|
||||
usedSkill: usedPageDataCollectSkill(messages),
|
||||
usedBindTool: usedPageDataBindTool(messages),
|
||||
};
|
||||
}
|
||||
|
||||
export function buildPageDataCollectFailureText() {
|
||||
return [
|
||||
'这次问卷/数据收集页面没有完成 Page Data API 绑定,所以我先不发链接。',
|
||||
'请直接重发一次完整需求(例如:调查问卷 + 后台查看),我会按 page-data-collect 技能:',
|
||||
'建表 → 注册 dataset → 写含 page-data-client.js 的 HTML → private_data_bind_workspace_page 发布。',
|
||||
'数据必须走平台 API 写入 SQLite,禁止 localStorage 或自建后端。',
|
||||
].join('');
|
||||
}
|
||||
|
||||
export function buildPageDataCollectRepairPrompt({
|
||||
htmlIssues = [],
|
||||
unboundFiles = [],
|
||||
} = {}) {
|
||||
const lines = [
|
||||
'【系统补绑请求】检测到 Page Data 问卷/数据页交付不完整。请立即按 page-data-collect 技能修复:',
|
||||
'1. load_skill → page-data-collect',
|
||||
'2. 确保 public/*.html 引入 /assets/page-data-client.js,且 JS 使用 MindSpacePageData.createClient({ apiBase: "/api" })',
|
||||
'3. 禁止 localStorage / 浏览器本地存储 fallback',
|
||||
'4. 对每个 public/*.html 调用 private_data_bind_workspace_page(问卷页 public insert,后台页 password read,口令默认 88888888)',
|
||||
];
|
||||
if (htmlIssues.length) {
|
||||
lines.push('', 'HTML 问题:');
|
||||
for (const item of htmlIssues) {
|
||||
lines.push(`- ${item.relativePath}: ${item.issue}`);
|
||||
}
|
||||
}
|
||||
if (unboundFiles.length) {
|
||||
lines.push('', '尚未 bind 的 Page Data 页面:');
|
||||
for (const file of unboundFiles) {
|
||||
lines.push(`- ${file.relativePath}`);
|
||||
}
|
||||
}
|
||||
lines.push('', '修复完成前不要告诉用户“已发布/已可提交”。');
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
export function shouldRetryPageDataCollectReply({
|
||||
reply,
|
||||
intent,
|
||||
publishDir,
|
||||
confirmedArtifacts = [],
|
||||
requestStartedAt = 0,
|
||||
}) {
|
||||
const agentText = String(intent?.agentText ?? intent?.displayText ?? '').trim();
|
||||
if (!isPageDataIntent(agentText)) return false;
|
||||
|
||||
const evaluation = evaluatePageDataFinishGuard({
|
||||
publishDir,
|
||||
agentText,
|
||||
messages: reply?.messages ?? [],
|
||||
requestStartedAt,
|
||||
});
|
||||
|
||||
if (!evaluation.pageDataIntent) return false;
|
||||
if (evaluation.htmlIssues.length > 0) return true;
|
||||
if (evaluation.unboundFiles.length > 0) return true;
|
||||
|
||||
const wrotePageDataHtml = evaluation.relevantFiles.length > 0;
|
||||
const wroteAnyHtml = confirmedArtifacts.length > 0 || extractRecentPageDataHtmlWrites(reply?.messages ?? [], { sinceMs: requestStartedAt }).length > 0;
|
||||
if (wroteAnyHtml && !usedPageDataCollectSkill(reply?.messages ?? []) && wrotePageDataHtml) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const replyClaimsDone = /(?:问卷|报名|后台|数据).*(?:已创建|已生成|已发布|可以提交|完成)/iu.test(String(reply?.text ?? ''));
|
||||
if (replyClaimsDone && evaluation.unboundFiles.length > 0) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function resolvePageDataCollectOutcome({
|
||||
reply,
|
||||
intent,
|
||||
publishDir,
|
||||
requestStartedAt = 0,
|
||||
}) {
|
||||
const agentText = String(intent?.agentText ?? intent?.displayText ?? '').trim();
|
||||
if (!isPageDataIntent(agentText)) {
|
||||
return { action: 'skip' };
|
||||
}
|
||||
|
||||
const evaluation = evaluatePageDataFinishGuard({
|
||||
publishDir,
|
||||
agentText,
|
||||
messages: reply?.messages ?? [],
|
||||
requestStartedAt,
|
||||
});
|
||||
|
||||
if (evaluation.htmlIssues.length > 0) {
|
||||
return { action: 'fail', failureText: buildPageDataCollectFailureText(), reason: 'invalid_html', evaluation };
|
||||
}
|
||||
if (evaluation.unboundFiles.length > 0) {
|
||||
return { action: 'retry', reason: 'missing_bind', evaluation };
|
||||
}
|
||||
if (shouldRetryPageDataCollectReply({ reply, intent, publishDir, requestStartedAt })) {
|
||||
return { action: 'retry', reason: 'incomplete_delivery', evaluation };
|
||||
}
|
||||
return { action: 'send', evaluation };
|
||||
}
|
||||
|
||||
export function isPageDataFinishGuardEnabled(env = process.env) {
|
||||
return envFlag(env.MEMIND_PAGE_DATA_FINISH_GUARD, true);
|
||||
}
|
||||
|
||||
export function resetPageDataFinishGuardAttempts(sessionId = null) {
|
||||
if (sessionId) repairAttemptsBySession.delete(String(sessionId));
|
||||
else repairAttemptsBySession.clear();
|
||||
}
|
||||
|
||||
export async function maybeAutoBindPageDataHtmlPages({
|
||||
pool,
|
||||
userId,
|
||||
publishDir,
|
||||
h5Root,
|
||||
storageRoot,
|
||||
onlyRelativePaths = null,
|
||||
} = {}) {
|
||||
if (!pool) {
|
||||
return { bound: [], skipped: [], errors: [{ code: 'database_unconfigured' }] };
|
||||
}
|
||||
|
||||
const bound = [];
|
||||
const skipped = [];
|
||||
const errors = [];
|
||||
const allowList = onlyRelativePaths ? new Set(onlyRelativePaths) : null;
|
||||
|
||||
for (const file of collectPageDataPublicHtmlFiles(publishDir)) {
|
||||
if (allowList && !allowList.has(file.relativePath)) continue;
|
||||
if (file.evaluation.issues.length > 0) {
|
||||
skipped.push({ relativePath: file.relativePath, reason: 'invalid_html', issues: file.evaluation.issues });
|
||||
continue;
|
||||
}
|
||||
if (file.evaluation.usage?.size === 0) {
|
||||
skipped.push({ relativePath: file.relativePath, reason: 'no_dataset_usage' });
|
||||
continue;
|
||||
}
|
||||
if (pageHasBoundPolicy({
|
||||
publishDir,
|
||||
relativePath: file.relativePath,
|
||||
html: file.content,
|
||||
})) {
|
||||
skipped.push({ relativePath: file.relativePath, reason: 'already_bound' });
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const accessMode = inferPageDataBindAccessMode(file.relativePath, file.content);
|
||||
const result = await bindWorkspaceHtmlForPageData({
|
||||
pool,
|
||||
h5Root,
|
||||
storageRoot,
|
||||
userId,
|
||||
workspaceRoot: publishDir,
|
||||
relativePath: file.relativePath,
|
||||
accessMode,
|
||||
password: accessMode === 'password' ? '88888888' : null,
|
||||
});
|
||||
bound.push({ relativePath: file.relativePath, pageId: result.pageId, workspaceUrl: result.workspaceUrl });
|
||||
} catch (err) {
|
||||
errors.push({
|
||||
relativePath: file.relativePath,
|
||||
message: err instanceof Error ? err.message : String(err),
|
||||
code: err?.code ?? 'bind_failed',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return { bound, skipped, errors };
|
||||
}
|
||||
|
||||
export async function maybeRepairPageDataAfterFinish({
|
||||
sessionId,
|
||||
userId,
|
||||
publishDir,
|
||||
messages,
|
||||
pool,
|
||||
h5Root,
|
||||
storageRoot,
|
||||
tkmindProxy = null,
|
||||
maxAttempts = 1,
|
||||
env = process.env,
|
||||
logger = console,
|
||||
userText = '',
|
||||
} = {}) {
|
||||
const recentUserText = String(userText ?? '').trim();
|
||||
const evaluation = evaluatePageDataFinishGuard({
|
||||
publishDir,
|
||||
agentText: recentUserText,
|
||||
messages,
|
||||
});
|
||||
|
||||
if (!evaluation.pageDataIntent && evaluation.relevantFiles.length === 0) {
|
||||
resetPageDataFinishGuardAttempts(sessionId);
|
||||
return { repaired: false, skipped: 'not_page_data', ...evaluation };
|
||||
}
|
||||
|
||||
const autoBind = await maybeAutoBindPageDataHtmlPages({
|
||||
pool,
|
||||
userId,
|
||||
publishDir,
|
||||
h5Root,
|
||||
storageRoot,
|
||||
onlyRelativePaths: evaluation.relevantFiles.map((file) => file.relativePath),
|
||||
});
|
||||
|
||||
const afterBind = evaluatePageDataFinishGuard({
|
||||
publishDir,
|
||||
agentText: recentUserText,
|
||||
messages,
|
||||
});
|
||||
|
||||
if (!afterBind.needsRepair) {
|
||||
resetPageDataFinishGuardAttempts(sessionId);
|
||||
return { repaired: autoBind.bound.length > 0, skipped: 'ok', autoBind, ...afterBind };
|
||||
}
|
||||
|
||||
if (!isPageDataFinishGuardEnabled(env)) {
|
||||
logger.warn?.(
|
||||
`[MindSpace][page-data-guard] incomplete page data delivery for session ${sessionId}: `
|
||||
+ `issues=${afterBind.htmlIssues.map((item) => `${item.relativePath}:${item.issue}`).join(',') || 'none'} `
|
||||
+ `unbound=${afterBind.unboundFiles.map((file) => file.relativePath).join(',') || 'none'}`,
|
||||
);
|
||||
return { repaired: false, skipped: 'disabled', autoBind, ...afterBind };
|
||||
}
|
||||
|
||||
if (!tkmindProxy?.submitSessionReplyForUser) {
|
||||
return { repaired: false, skipped: 'no_proxy', autoBind, ...afterBind };
|
||||
}
|
||||
|
||||
const key = String(sessionId ?? '');
|
||||
const attempts = repairAttemptsBySession.get(key) ?? 0;
|
||||
if (!key || attempts >= maxAttempts) {
|
||||
logger.warn?.(
|
||||
`[MindSpace][page-data-guard] repair limit reached for session ${sessionId}`,
|
||||
afterBind,
|
||||
);
|
||||
return { repaired: false, skipped: 'limit', attempts, autoBind, ...afterBind };
|
||||
}
|
||||
repairAttemptsBySession.set(key, attempts + 1);
|
||||
|
||||
const prompt = buildPageDataCollectRepairPrompt(afterBind);
|
||||
const requestId = `page-data-repair-${crypto.randomUUID()}`;
|
||||
const userMessage = {
|
||||
role: 'user',
|
||||
content: [{ type: 'text', text: prompt }],
|
||||
metadata: {
|
||||
displayText: '请补全 Page Data 问卷绑定与页面脚本',
|
||||
userVisible: false,
|
||||
agentVisible: true,
|
||||
memindRun: { pageDataFinishRepair: true },
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
await tkmindProxy.submitSessionReplyForUser(userId, sessionId, requestId, userMessage);
|
||||
logger.info?.(
|
||||
`[MindSpace][page-data-guard] triggered repair for session ${sessionId} `
|
||||
+ `(attempt ${attempts + 1}/${maxAttempts})`,
|
||||
);
|
||||
return { repaired: true, triggered: true, attempts: attempts + 1, autoBind, ...afterBind };
|
||||
} catch (err) {
|
||||
logger.warn?.(
|
||||
`[MindSpace][page-data-guard] repair failed for session ${sessionId}: `
|
||||
+ `${err instanceof Error ? err.message : err}`,
|
||||
);
|
||||
return {
|
||||
repaired: false,
|
||||
skipped: 'error',
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
attempts: attempts + 1,
|
||||
autoBind,
|
||||
...afterBind,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user