mindspace: close authority boundaries
This commit is contained in:
@@ -0,0 +1,598 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import {
|
||||
extractPublicHtmlWriteArtifacts,
|
||||
isStubPublicHtmlContent,
|
||||
materializeMissingPublicHtmlWrites,
|
||||
normalizePublicHtmlRelativePath,
|
||||
} from './mindspace-public-finish-sync.mjs';
|
||||
import {
|
||||
repairUnambiguousFreshWechatPageThumbnail,
|
||||
verifyFreshWechatPageThumbnails,
|
||||
} from './wechat/verify/generated-thumbnail.mjs';
|
||||
import {
|
||||
ensureWorkspaceHtmlThumbnail,
|
||||
workspaceThumbnailRelativePath,
|
||||
} from './mindspace-workspace-thumbnails.mjs';
|
||||
import {
|
||||
verifySharePreviewMeta,
|
||||
} from './wechat/verify/share-preview.mjs';
|
||||
|
||||
const PUBLIC_HTML_LINK_PATTERN =
|
||||
/https?:\/\/[^\s<>"')\]]+\/MindSpace\/([0-9a-f-]{36}|[a-z0-9._-]+)\/public\/([^\s<>"')\]]+\.html)/gi;
|
||||
const AUXILIARY_PAGE_PATH_PATTERN =
|
||||
/(?:^|[-_.\/])(?:admin|manage|management|backend|password|redirect|download|error)(?:[-_.\/]|$)/i;
|
||||
|
||||
function replyMessages(reply) {
|
||||
return Array.isArray(reply?.messages)
|
||||
? reply.messages
|
||||
: [];
|
||||
}
|
||||
|
||||
function extractHtmlWriteTargets(messages = []) {
|
||||
const targets = new Set();
|
||||
for (const message of messages) {
|
||||
for (const item of message?.content ?? []) {
|
||||
if (item?.type !== 'toolRequest') continue;
|
||||
const toolCall = item.toolCall?.value;
|
||||
const args = toolCall?.arguments ?? {};
|
||||
const name = String(toolCall?.name ?? '').trim();
|
||||
const action = String(args.action ?? '').trim().toLowerCase();
|
||||
const writeLike =
|
||||
(name === 'developer' &&
|
||||
(action === 'write' || action === 'edit')) ||
|
||||
name === 'write' ||
|
||||
name.endsWith('__write') ||
|
||||
name === 'write_file' ||
|
||||
name === 'edit_file' ||
|
||||
name.endsWith('__write_file') ||
|
||||
name.endsWith('__edit_file');
|
||||
if (!writeLike || typeof args.path !== 'string') {
|
||||
continue;
|
||||
}
|
||||
const candidate = String(args.path).trim();
|
||||
if (candidate.toLowerCase().endsWith('.html')) {
|
||||
targets.add(candidate);
|
||||
}
|
||||
}
|
||||
}
|
||||
return [...targets];
|
||||
}
|
||||
|
||||
function extractRequestedHtmlTarget(text) {
|
||||
const value = String(text ?? '');
|
||||
if (!value) return '';
|
||||
const explicitPublicMatch =
|
||||
value.match(
|
||||
/(?:^|[\s`'"])(public\/[a-z0-9._-]+\.html)\b/i,
|
||||
);
|
||||
if (explicitPublicMatch?.[1]) {
|
||||
return explicitPublicMatch[1];
|
||||
}
|
||||
const bareMatch =
|
||||
value.match(
|
||||
/(?:^|[\s`'"])([a-z0-9._-]+\.html)\b/i,
|
||||
);
|
||||
return bareMatch?.[1]
|
||||
? `public/${bareMatch[1]}`
|
||||
: '';
|
||||
}
|
||||
|
||||
function collectLinkedHtmlFilenames(text) {
|
||||
const filenames = new Set();
|
||||
for (
|
||||
const match of String(text ?? '')
|
||||
.matchAll(PUBLIC_HTML_LINK_PATTERN)
|
||||
) {
|
||||
const filename =
|
||||
path.posix.basename(
|
||||
String(match[2] ?? '').trim(),
|
||||
);
|
||||
if (filename) filenames.add(filename);
|
||||
}
|
||||
return filenames;
|
||||
}
|
||||
|
||||
function readArtifactDescriptor(
|
||||
target,
|
||||
{
|
||||
publishDir,
|
||||
buildCanonicalUrl,
|
||||
copyIntoPublic = true,
|
||||
},
|
||||
) {
|
||||
const workspaceRoot =
|
||||
path.resolve(String(publishDir ?? ''));
|
||||
if (!workspaceRoot) return null;
|
||||
const candidate = String(target ?? '').trim();
|
||||
if (!candidate) return null;
|
||||
const source = path.isAbsolute(candidate)
|
||||
? path.resolve(candidate)
|
||||
: path.resolve(workspaceRoot, candidate);
|
||||
if (
|
||||
source !== workspaceRoot &&
|
||||
!source.startsWith(
|
||||
`${workspaceRoot}${path.sep}`,
|
||||
)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
if (
|
||||
!fs.existsSync(source) ||
|
||||
!fs.statSync(source).isFile()
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
const publicRoot =
|
||||
path.join(workspaceRoot, 'public');
|
||||
let publishedPath = source;
|
||||
if (
|
||||
copyIntoPublic &&
|
||||
source !== publicRoot &&
|
||||
!source.startsWith(`${publicRoot}${path.sep}`)
|
||||
) {
|
||||
fs.mkdirSync(publicRoot, { recursive: true });
|
||||
publishedPath = path.join(
|
||||
publicRoot,
|
||||
path.basename(source),
|
||||
);
|
||||
if (publishedPath !== source) {
|
||||
fs.copyFileSync(source, publishedPath);
|
||||
}
|
||||
}
|
||||
|
||||
const relativePath = path
|
||||
.relative(workspaceRoot, publishedPath)
|
||||
.replace(/\\/g, '/');
|
||||
if (
|
||||
!relativePath ||
|
||||
relativePath.startsWith('..') ||
|
||||
path.isAbsolute(relativePath)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
const normalizedRelativePath =
|
||||
normalizePublicHtmlRelativePath(relativePath);
|
||||
if (
|
||||
!normalizedRelativePath
|
||||
.toLowerCase()
|
||||
.endsWith('.html')
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
let content = '';
|
||||
let stat;
|
||||
try {
|
||||
content = fs.readFileSync(
|
||||
publishedPath,
|
||||
'utf8',
|
||||
);
|
||||
stat = fs.statSync(publishedPath);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
const canonicalUrl =
|
||||
String(
|
||||
buildCanonicalUrl?.(
|
||||
normalizedRelativePath,
|
||||
) ?? '',
|
||||
).trim();
|
||||
const auxiliary =
|
||||
AUXILIARY_PAGE_PATH_PATTERN.test(
|
||||
normalizedRelativePath,
|
||||
) ||
|
||||
/<meta[^>]*name=["']mindspace-page-role["'][^>]*content=["'](?:admin|auxiliary)["']/i.test(
|
||||
content,
|
||||
) ||
|
||||
/<[^>]+data-mindspace-page-role=["'](?:admin|auxiliary)["']/i.test(
|
||||
content,
|
||||
);
|
||||
const sharePreview =
|
||||
verifySharePreviewMeta(content);
|
||||
return {
|
||||
localPath: publishedPath,
|
||||
relativePath: normalizedRelativePath,
|
||||
url: canonicalUrl,
|
||||
canonicalUrl,
|
||||
exists: true,
|
||||
isStub: isStubPublicHtmlContent(content),
|
||||
isHtmlDocument:
|
||||
/<(?:html|body|main|article)\b/i.test(
|
||||
content,
|
||||
),
|
||||
sharePreview,
|
||||
auxiliary,
|
||||
sizeBytes: Number(stat.size ?? 0),
|
||||
mtimeMs: Number(stat.mtimeMs ?? 0),
|
||||
};
|
||||
}
|
||||
|
||||
function collectRecentArtifacts({
|
||||
publishDir,
|
||||
buildCanonicalUrl,
|
||||
replyText = '',
|
||||
requestStartedAt = 0,
|
||||
limit = 20,
|
||||
}) {
|
||||
const publicRoot = path.join(
|
||||
path.resolve(publishDir),
|
||||
'public',
|
||||
);
|
||||
try {
|
||||
if (
|
||||
!fs.existsSync(publicRoot) ||
|
||||
!fs.statSync(publicRoot).isDirectory()
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
const linkedFilenames =
|
||||
collectLinkedHtmlFilenames(replyText);
|
||||
const artifacts = [];
|
||||
const stack = [publicRoot];
|
||||
while (
|
||||
stack.length > 0 &&
|
||||
artifacts.length < limit
|
||||
) {
|
||||
const current = stack.pop();
|
||||
let entries;
|
||||
try {
|
||||
entries = fs.readdirSync(current, {
|
||||
withFileTypes: true,
|
||||
});
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
for (const entry of entries) {
|
||||
const absolutePath =
|
||||
path.join(current, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
stack.push(absolutePath);
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
!entry.isFile() ||
|
||||
!entry.name.toLowerCase().endsWith('.html')
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
const artifact = readArtifactDescriptor(
|
||||
absolutePath,
|
||||
{
|
||||
publishDir,
|
||||
buildCanonicalUrl,
|
||||
copyIntoPublic: false,
|
||||
},
|
||||
);
|
||||
if (!artifact) continue;
|
||||
if (
|
||||
requestStartedAt > 0 &&
|
||||
artifact.mtimeMs + 1 < requestStartedAt
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
const filename = path.posix.basename(
|
||||
artifact.relativePath,
|
||||
);
|
||||
if (
|
||||
linkedFilenames.size > 0 &&
|
||||
!linkedFilenames.has(filename)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
artifacts.push(artifact);
|
||||
if (artifacts.length >= limit) break;
|
||||
}
|
||||
}
|
||||
return artifacts.sort(
|
||||
(left, right) =>
|
||||
right.mtimeMs - left.mtimeMs,
|
||||
);
|
||||
}
|
||||
|
||||
function uniqueArtifacts(artifacts = []) {
|
||||
const byRelativePath = new Map();
|
||||
for (const artifact of artifacts) {
|
||||
const relativePath = String(
|
||||
artifact?.relativePath ?? '',
|
||||
).trim();
|
||||
if (relativePath) {
|
||||
byRelativePath.set(relativePath, artifact);
|
||||
}
|
||||
}
|
||||
return [...byRelativePath.values()];
|
||||
}
|
||||
|
||||
function sanitizeArtifact(artifact) {
|
||||
if (!artifact) return null;
|
||||
const {
|
||||
localPath: _localPath,
|
||||
mtimeMs: _mtimeMs,
|
||||
...safeArtifact
|
||||
} = artifact;
|
||||
return safeArtifact;
|
||||
}
|
||||
|
||||
function sanitizeArtifacts(artifacts = []) {
|
||||
return artifacts
|
||||
.map(sanitizeArtifact)
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function buildValidReplyUrls(
|
||||
replyText,
|
||||
confirmedArtifacts,
|
||||
) {
|
||||
const artifactsByFilename = new Map(
|
||||
confirmedArtifacts.map((artifact) => [
|
||||
path.posix.basename(artifact.relativePath),
|
||||
artifact,
|
||||
]),
|
||||
);
|
||||
const urls = [];
|
||||
for (
|
||||
const match of String(replyText ?? '')
|
||||
.matchAll(PUBLIC_HTML_LINK_PATTERN)
|
||||
) {
|
||||
const filename = path.posix.basename(
|
||||
String(match[2] ?? '').trim(),
|
||||
);
|
||||
if (artifactsByFilename.has(filename)) {
|
||||
urls.push(match[0]);
|
||||
}
|
||||
}
|
||||
return [...new Set(urls)];
|
||||
}
|
||||
|
||||
export function prepareWechatHtmlDeliveryAtWorkspace({
|
||||
reply = null,
|
||||
intent = null,
|
||||
publishDir,
|
||||
buildCanonicalUrl,
|
||||
requestStartedAt = 0,
|
||||
allowRecentArtifacts = true,
|
||||
} = {}) {
|
||||
const messages = replyMessages(reply);
|
||||
const materialization =
|
||||
materializeMissingPublicHtmlWrites({
|
||||
messages,
|
||||
publishDir,
|
||||
});
|
||||
const publishedArtifacts = uniqueArtifacts(
|
||||
extractHtmlWriteTargets(messages)
|
||||
.map((target) =>
|
||||
readArtifactDescriptor(target, {
|
||||
publishDir,
|
||||
buildCanonicalUrl,
|
||||
}),
|
||||
)
|
||||
.filter(Boolean),
|
||||
);
|
||||
const expectedTarget =
|
||||
extractRequestedHtmlTarget(
|
||||
intent?.agentText ??
|
||||
intent?.displayText ??
|
||||
'',
|
||||
);
|
||||
const expectedArtifacts = expectedTarget
|
||||
? [
|
||||
readArtifactDescriptor(expectedTarget, {
|
||||
publishDir,
|
||||
buildCanonicalUrl,
|
||||
}),
|
||||
].filter(Boolean)
|
||||
: [];
|
||||
const recentArtifacts = allowRecentArtifacts
|
||||
? collectRecentArtifacts({
|
||||
publishDir,
|
||||
buildCanonicalUrl,
|
||||
replyText: reply?.text,
|
||||
requestStartedAt,
|
||||
})
|
||||
: [];
|
||||
const confirmedArtifacts = uniqueArtifacts([
|
||||
...publishedArtifacts,
|
||||
...expectedArtifacts,
|
||||
...recentArtifacts,
|
||||
]);
|
||||
const linkedFilenames =
|
||||
collectLinkedHtmlFilenames(reply?.text);
|
||||
const matchedArtifacts =
|
||||
linkedFilenames.size > 0
|
||||
? confirmedArtifacts.filter((artifact) =>
|
||||
linkedFilenames.has(
|
||||
path.posix.basename(
|
||||
artifact.relativePath,
|
||||
),
|
||||
),
|
||||
)
|
||||
: confirmedArtifacts;
|
||||
const validReplyUrls = buildValidReplyUrls(
|
||||
reply?.text,
|
||||
confirmedArtifacts,
|
||||
);
|
||||
return {
|
||||
materialization,
|
||||
publishedArtifacts:
|
||||
sanitizeArtifacts(publishedArtifacts),
|
||||
expectedArtifacts:
|
||||
sanitizeArtifacts(expectedArtifacts),
|
||||
recentArtifacts:
|
||||
sanitizeArtifacts(recentArtifacts),
|
||||
confirmedArtifacts:
|
||||
sanitizeArtifacts(confirmedArtifacts),
|
||||
verifiedArtifacts:
|
||||
sanitizeArtifacts(matchedArtifacts),
|
||||
validReplyUrls,
|
||||
hasValidReplyLink:
|
||||
validReplyUrls.length > 0,
|
||||
};
|
||||
}
|
||||
|
||||
function restoreWorkspaceArtifacts({
|
||||
artifacts,
|
||||
publishDir,
|
||||
buildCanonicalUrl,
|
||||
}) {
|
||||
return uniqueArtifacts(
|
||||
artifacts
|
||||
.map((artifact) =>
|
||||
readArtifactDescriptor(
|
||||
artifact?.relativePath,
|
||||
{
|
||||
publishDir,
|
||||
buildCanonicalUrl,
|
||||
copyIntoPublic: false,
|
||||
},
|
||||
),
|
||||
)
|
||||
.filter(Boolean),
|
||||
);
|
||||
}
|
||||
|
||||
function sanitizeFreshVerification(
|
||||
verification,
|
||||
repair = null,
|
||||
) {
|
||||
return {
|
||||
ok: verification?.ok === true,
|
||||
reason:
|
||||
verification?.reason == null
|
||||
? null
|
||||
: String(verification.reason),
|
||||
artifact: sanitizeArtifact(
|
||||
verification?.artifact,
|
||||
),
|
||||
cover:
|
||||
String(verification?.cover ?? '').trim() ||
|
||||
null,
|
||||
matchRelativePaths: Array.isArray(
|
||||
verification?.matches,
|
||||
)
|
||||
? verification.matches
|
||||
.map((match) =>
|
||||
String(
|
||||
match?.artifact?.relativePath ?? '',
|
||||
).trim(),
|
||||
)
|
||||
.filter(Boolean)
|
||||
: [],
|
||||
skippedRelativePaths: Array.isArray(
|
||||
verification?.skippedArtifacts,
|
||||
)
|
||||
? verification.skippedArtifacts
|
||||
.map((artifact) =>
|
||||
String(
|
||||
artifact?.relativePath ?? '',
|
||||
).trim(),
|
||||
)
|
||||
.filter(Boolean)
|
||||
: [],
|
||||
repair: repair
|
||||
? {
|
||||
ok: repair.ok === true,
|
||||
reason:
|
||||
repair.reason == null
|
||||
? null
|
||||
: String(repair.reason),
|
||||
relativePath:
|
||||
String(
|
||||
repair?.artifact?.relativePath ?? '',
|
||||
).trim() || null,
|
||||
cover:
|
||||
String(repair?.cover ?? '').trim() ||
|
||||
null,
|
||||
}
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
export async function ensureWechatFreshPageThumbnailsAtWorkspace({
|
||||
artifacts = [],
|
||||
images = [],
|
||||
messages = [],
|
||||
publishDir,
|
||||
buildCanonicalUrl,
|
||||
repairEnabled = false,
|
||||
} = {}) {
|
||||
const workspaceArtifacts =
|
||||
restoreWorkspaceArtifacts({
|
||||
artifacts,
|
||||
publishDir,
|
||||
buildCanonicalUrl,
|
||||
});
|
||||
let verification =
|
||||
verifyFreshWechatPageThumbnails(
|
||||
workspaceArtifacts,
|
||||
images,
|
||||
);
|
||||
let repair = null;
|
||||
if (
|
||||
!verification.ok &&
|
||||
repairEnabled
|
||||
) {
|
||||
repair =
|
||||
repairUnambiguousFreshWechatPageThumbnail({
|
||||
artifacts: workspaceArtifacts,
|
||||
images,
|
||||
currentRunHtmlArtifacts:
|
||||
extractPublicHtmlWriteArtifacts(
|
||||
messages,
|
||||
{ publishDir },
|
||||
),
|
||||
verificationReason:
|
||||
verification.reason,
|
||||
});
|
||||
if (repair.ok) {
|
||||
verification =
|
||||
verifyFreshWechatPageThumbnails(
|
||||
restoreWorkspaceArtifacts({
|
||||
artifacts,
|
||||
publishDir,
|
||||
buildCanonicalUrl,
|
||||
}),
|
||||
images,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (verification.ok) {
|
||||
await Promise.all(
|
||||
verification.matches.map((match) =>
|
||||
ensureWorkspaceHtmlThumbnail(
|
||||
publishDir,
|
||||
match.artifact.relativePath,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return {
|
||||
...sanitizeFreshVerification(
|
||||
verification,
|
||||
repair,
|
||||
),
|
||||
thumbnailRelativePaths:
|
||||
verification.ok
|
||||
? verification.matches.map((match) =>
|
||||
workspaceThumbnailRelativePath(
|
||||
match.artifact.relativePath,
|
||||
),
|
||||
)
|
||||
: [],
|
||||
};
|
||||
}
|
||||
|
||||
export const mindSpaceWechatHtmlDeliveryInternals = {
|
||||
buildValidReplyUrls,
|
||||
collectLinkedHtmlFilenames,
|
||||
extractHtmlWriteTargets,
|
||||
extractRequestedHtmlTarget,
|
||||
readArtifactDescriptor,
|
||||
sanitizeArtifact,
|
||||
};
|
||||
Reference in New Issue
Block a user