Files
memind/mindspace-local-server-adapter.mjs
T
john d3239ff292 fix: MindSpace remote sync, thumbnail fallback, and Plaza URL guards
Ensure page sync runs via pageSyncService in remote mode, fall back to
workspace HTML when storage assets are missing, and prevent production
Portal from linking to loopback Plaza URLs baked in at build time.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-05 07:57:04 +08:00

164 lines
5.3 KiB
JavaScript

import { backfillConversationPackageArtifacts } from './mindspace-conversation-package-backfill.mjs';
import { createConversationPackagePublicHtmlHydrator } from './mindspace-conversation-package-public-html.mjs';
import { createMindSpaceLocalRuntimeServices } from './mindspace-local-runtime-services.mjs';
export function createMindSpaceLocalServerAdapter({
pool,
h5Root,
env = process.env,
maxFileBytes,
publicPageLimit,
resolveUserIdForAgentSession,
resolveSessionSnapshot,
registerPublicHtmlArtifactsForConversation,
logger = console,
setIntervalFn = setInterval,
syncWorkspaceAssetsEnabled = true,
} = {}) {
let services;
services = createMindSpaceLocalRuntimeServices({
pool,
h5Root,
env,
maxFileBytes,
publicPageLimit,
resolveUserIdForAgentSession,
conversationPackageBackfill: ({ user, sessionId, storageRoot, registry }) =>
backfillConversationPackageArtifacts({
pool,
registry,
storageRoot,
h5Root,
user,
sessionId,
}),
conversationPackagePublicHtmlHydrator: createConversationPackagePublicHtmlHydrator({
h5Root,
resolveSessionSnapshot,
registerPublicHtmlArtifactsForConversation,
}),
logger,
syncWorkspaceAssets: syncWorkspaceAssetsEnabled
? (targetUserId, options) =>
services?.assetService?.syncWorkspaceAssets?.(targetUserId, options)
: null,
});
return {
...services,
kind: 'local',
implementationStatus: 'ready',
assertReady() {
return true;
},
startPublicationCleanup(intervalMs = 60 * 1000) {
if (!services.publicationService || typeof setIntervalFn !== 'function') return null;
const timer = setIntervalFn(async () => {
try {
const result = await services.publicationService.cleanupExpiredUnconfirmedPublications();
if (result.cleaned > 0) {
logger.log?.(
`[Publication Cleanup] Auto-privatized ${result.cleaned} expired unconfirmed publications`,
);
}
} catch (error) {
logger.error?.('[Publication Cleanup Error]', error instanceof Error ? error.message : error);
}
}, intervalMs);
timer?.unref?.();
return timer;
},
startAgentWorker({
enabled = false,
concurrency = 1,
pollMs = 1000,
staleMs = 5 * 60 * 1000,
runner,
} = {}) {
if (!enabled || !services.agentJobService || !runner || typeof setIntervalFn !== 'function') return null;
let inFlight = 0;
let draining = false;
const drainQueue = async () => {
if (draining) return;
draining = true;
try {
while (inFlight < concurrency) {
const claim = await services.agentJobService.claimNextJob();
if (!claim) break;
inFlight += 1;
void runner
.runJob(claim.jobId, claim)
.catch((error) => {
logger.error?.('Agent worker job failed:', error);
})
.finally(() => {
inFlight -= 1;
});
}
} catch (error) {
logger.error?.('Agent worker drain failed:', error);
} finally {
draining = false;
}
};
const workerTimer = setIntervalFn(() => {
void drainQueue();
}, pollMs);
const reaperTimer = setIntervalFn(() => {
void services.agentJobService
.reapStaleJobs(staleMs)
.then((reaped) => {
if (reaped > 0) {
logger.warn?.(`Agent worker reaped ${reaped} stale running job(s)`);
}
})
.catch((error) => {
logger.error?.('Agent worker reaper failed:', error);
});
}, Math.min(staleMs, 60_000));
workerTimer?.unref?.();
reaperTimer?.unref?.();
logger.log?.(`Agent job worker enabled (concurrency=${concurrency}, poll=${pollMs}ms)`);
return { workerTimer, reaperTimer };
},
startBackgroundJobs({
publicationCleanupIntervalMs = 60 * 1000,
agentWorker,
agentRunner,
workspaceMaintenanceEnabled = false,
startWorkspaceThumbnailWatcher,
startWorkspaceAssetSyncWatcher,
publishRoot,
syncUserWorkspaceByDirKey,
expireStaleUploadsIntervalMs = 5 * 60 * 1000,
} = {}) {
const backgroundJobs = {
publicationCleanup: this.startPublicationCleanup(publicationCleanupIntervalMs),
agentWorker: this.startAgentWorker({
...agentWorker,
runner: agentRunner,
}),
workspaceMaintenance: null,
};
if (!workspaceMaintenanceEnabled) {
logger.log?.('Workspace maintenance daemons disabled (MEMIND_WORKSPACE_MAINTENANCE=0)');
return backgroundJobs;
}
startWorkspaceThumbnailWatcher?.(publishRoot);
startWorkspaceAssetSyncWatcher?.({
publishRoot,
syncUserWorkspaceByDirKey,
});
void services.assetService?.expireStaleUploads?.().catch(() => {});
backgroundJobs.workspaceMaintenance = {
expireStaleUploadsTimer:
setIntervalFn(() => {
void services.assetService?.expireStaleUploads?.().catch(() => {});
}, expireStaleUploadsIntervalMs),
};
backgroundJobs.workspaceMaintenance.expireStaleUploadsTimer?.unref?.();
return backgroundJobs;
},
};
}