fix(page-data): harden structural delivery and Portal base URL
Reject /api/page-data/ legacy passthrough, verify live API before send, and resolve delivery links to Portal (8081) instead of Vite (5173). Add local repair/verify scripts for daily-register Page Data flow. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import path from 'node:path';
|
||||
import { isPageDataIntent } from './chat-skills.mjs';
|
||||
import {
|
||||
detectPageDataDatasetUsageFromHtml,
|
||||
htmlUsesForbiddenLegacyPageDataApi,
|
||||
htmlUsesPageDataApi,
|
||||
inferPageDataBindAccessMode,
|
||||
} from './page-data-html-detect.mjs';
|
||||
@@ -15,7 +16,7 @@ import {
|
||||
normalizePageDataApiBase,
|
||||
verifyPageDataDeliveryArtifacts,
|
||||
} from './page-data-delivery-assess.mjs';
|
||||
import { buildPublicUrl, resolvePublicBaseUrl } from './user-publish.mjs';
|
||||
import { buildPublicUrl, resolvePageDataDeliveryBaseUrl } from './user-publish.mjs';
|
||||
|
||||
export { inferPageDataBindAccessMode };
|
||||
|
||||
@@ -50,7 +51,7 @@ function readPublicHtmlFiles(publishDir) {
|
||||
|
||||
export function collectPageDataDeliveryArtifacts(
|
||||
publishDir,
|
||||
{ publicBaseUrl = resolvePublicBaseUrl() } = {},
|
||||
{ publicBaseUrl = resolvePageDataDeliveryBaseUrl() } = {},
|
||||
) {
|
||||
const ownerKey = path.basename(path.resolve(String(publishDir ?? '')));
|
||||
if (!ownerKey) return [];
|
||||
@@ -112,13 +113,17 @@ export function evaluatePageDataHtmlContent(html, { relativePath = '' } = {}) {
|
||||
const usesPageDataApi =
|
||||
usage.size > 0 ||
|
||||
PAGE_DATA_CLIENT_SCRIPT_PATTERN.test(content) ||
|
||||
/\bMindSpacePageData\b/.test(content);
|
||||
/\bMindSpacePageData\b/.test(content) ||
|
||||
htmlUsesForbiddenLegacyPageDataApi(content);
|
||||
|
||||
if (!usesPageDataApi) {
|
||||
return { usesPageDataApi: false, issues: [] };
|
||||
}
|
||||
|
||||
const issues = [];
|
||||
if (htmlUsesForbiddenLegacyPageDataApi(content)) {
|
||||
issues.push('forbidden_legacy_page_data_api');
|
||||
}
|
||||
if (!PAGE_DATA_CLIENT_SCRIPT_PATTERN.test(content)) {
|
||||
issues.push('missing_page_data_client_script');
|
||||
}
|
||||
@@ -382,7 +387,7 @@ export function buildPageDataCollectFailureText() {
|
||||
'这次问卷/数据收集页面没有完成 Page Data API 绑定,所以我先不发链接。',
|
||||
'请直接重发一次完整需求(例如:调查问卷 + 后台查看),我会按 page-data-collect 技能:',
|
||||
'建表 → 注册 dataset → 写含 page-data-client.js 的 HTML → private_data_bind_workspace_page 发布。',
|
||||
'数据必须走平台 API 写入 SQLite,禁止 localStorage 或自建后端。',
|
||||
'数据必须走平台 API(MindSpacePageData.createClient + /api/public/pages/...),禁止 /api/page-data/ 旁路、localStorage 或自建后端。',
|
||||
].join('');
|
||||
}
|
||||
|
||||
@@ -394,8 +399,9 @@ export function buildPageDataCollectRepairPrompt({
|
||||
'【系统补绑请求】检测到 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',
|
||||
'3. 禁止 /api/page-data/ 旁路、localStorage 或自建后端',
|
||||
'4. 对每个 public/*.html 调用 private_data_bind_workspace_page(问卷页 public insert,后台页 password read,口令默认 88888888)',
|
||||
'5. 向用户交付的链接必须使用 Portal(本地 http://127.0.0.1:8081),不要用 Vite 5173',
|
||||
];
|
||||
if (htmlIssues.length) {
|
||||
lines.push('', 'HTML 问题:');
|
||||
@@ -507,34 +513,50 @@ export async function resolvePageDataCollectOutcomeAsync({
|
||||
return { action: 'fail', failureText: buildPageDataCollectFailureText(), reason: 'invalid_html', evaluation };
|
||||
}
|
||||
|
||||
if (evaluation.unboundFiles.length > 0 && pool && userId && apiBase) {
|
||||
const artifacts = collectPageDataDeliveryArtifacts(publishDir).filter((artifact) =>
|
||||
evaluation.relevantFiles.some((file) => file.relativePath === artifact.relativePath),
|
||||
);
|
||||
if (artifacts.length > 0) {
|
||||
const failures = await verifyPageDataDeliveryArtifacts({
|
||||
artifacts,
|
||||
const deliveryArtifacts =
|
||||
evaluation.relevantFiles.length > 0
|
||||
? collectPageDataDeliveryArtifacts(publishDir).filter((artifact) =>
|
||||
evaluation.relevantFiles.some((file) => file.relativePath === artifact.relativePath),
|
||||
)
|
||||
: [];
|
||||
|
||||
if (pool && userId && apiBase && deliveryArtifacts.length > 0) {
|
||||
const deliveryFailures = await verifyPageDataDeliveryArtifacts({
|
||||
artifacts: deliveryArtifacts,
|
||||
publishDir,
|
||||
apiBase,
|
||||
pool,
|
||||
userId,
|
||||
findPageByRelativePath,
|
||||
fetchImpl,
|
||||
});
|
||||
if (deliveryFailures.length === 0) {
|
||||
const reassessment = await evaluatePageDataFinishGuardAsync({
|
||||
publishDir,
|
||||
apiBase,
|
||||
agentText,
|
||||
messages: reply?.messages ?? [],
|
||||
requestStartedAt,
|
||||
pool,
|
||||
userId,
|
||||
findPageByRelativePath,
|
||||
fetchImpl,
|
||||
});
|
||||
if (failures.length === 0) {
|
||||
const reassessment = await evaluatePageDataFinishGuardAsync({
|
||||
publishDir,
|
||||
agentText,
|
||||
messages: reply?.messages ?? [],
|
||||
requestStartedAt,
|
||||
pool,
|
||||
userId,
|
||||
findPageByRelativePath,
|
||||
});
|
||||
if (reassessment.unboundFiles.length === 0 && reassessment.htmlIssues.length === 0) {
|
||||
return { action: 'send', reason: 'verified_by_live_api', evaluation: reassessment };
|
||||
}
|
||||
if (reassessment.unboundFiles.length === 0 && reassessment.htmlIssues.length === 0) {
|
||||
return { action: 'send', reason: 'verified_by_live_api', evaluation: reassessment };
|
||||
}
|
||||
} else if (evaluation.unboundFiles.length > 0) {
|
||||
return {
|
||||
action: 'retry',
|
||||
reason: 'missing_bind',
|
||||
evaluation,
|
||||
deliveryFailures,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
action: 'retry',
|
||||
reason: 'delivery_smoke_failed',
|
||||
evaluation,
|
||||
deliveryFailures,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user