028bb18dc0
Scheduled tasks can finish once the expected HTML is on disk and stable, instead of waiting for Goose Finish. Adds news-item templates and scripts for generating a single-event page and committing it as a WeChat draft. Co-authored-by: Cursor <cursoragent@cursor.com>
221 lines
8.4 KiB
JavaScript
221 lines
8.4 KiB
JavaScript
/**
|
||
* 单条新闻 → 公众号草稿流程的共享上下文。
|
||
*
|
||
* 流程被拆成四个独立入口(生成 / 传图 / 生成配图 / 提交草稿),只有「生成」需要
|
||
* 完整的 Portal + Agent bootstrap;其余三个只要数据库连接和公众号凭证,因此这里
|
||
* 提供两级上下文,避免预览和推送也背上几十秒的 bootstrap 成本。
|
||
*/
|
||
import fs from 'node:fs';
|
||
import path from 'node:path';
|
||
import process from 'node:process';
|
||
import { fileURLToPath } from 'node:url';
|
||
import { createDbPool, isDatabaseConfigured } from '../db.mjs';
|
||
import { createMindSpaceWechatMpConfigService, fetchWechatMpAccessToken } from '../mindspace-wechat-mp-config.mjs';
|
||
import { resolveMindSpaceAnalyticsConfig } from '../mindspace-analytics.mjs';
|
||
import {
|
||
resolveMindSpaceServerRuntimeOptions,
|
||
resolvePortalH5Root,
|
||
} from '../mindspace-runtime-config.mjs';
|
||
import { startWorkspaceAssetSyncWatcher } from '../mindspace-workspace-sync.mjs';
|
||
import { startWorkspaceThumbnailWatcher } from '../mindspace-workspace-thumbnails.mjs';
|
||
import { bootstrapPortalAgentServices } from '../server/portal-agent-services-bootstrap.mjs';
|
||
import { bootstrapPortalAuthServices } from '../server/portal-auth-services-bootstrap.mjs';
|
||
import { bootstrapPortalDomainServices } from '../server/portal-domain-services-bootstrap.mjs';
|
||
import { bootstrapPortalGatewayServices } from '../server/portal-gateway-services-bootstrap.mjs';
|
||
import { bootstrapPortalMemorySessionServices } from '../server/portal-memory-session-services-bootstrap.mjs';
|
||
import { resolveWechatFetch } from '../wechat-egress-fetch.mjs';
|
||
import { loadH5Environment } from './load-env.mjs';
|
||
import { loadMemindEnvFiles } from './memind-runtime-profile.mjs';
|
||
|
||
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
||
|
||
export const CODE_ROOT = path.join(scriptDir, '..');
|
||
|
||
export function resolvePortalRoot(env = process.env) {
|
||
return path.resolve(String(env.MEMIND_PORTAL_H5_ROOT ?? '/Users/john/Project/Memind').trim());
|
||
}
|
||
|
||
/** 读取 stdin 上的 JSON 载荷。四个脚本统一用这种调用约定。 */
|
||
export function readStdinPayload() {
|
||
const text = fs.readFileSync(0, 'utf8').trim();
|
||
if (!text) throw new Error('缺少 stdin JSON 输入');
|
||
return JSON.parse(text);
|
||
}
|
||
|
||
/**
|
||
* 统一的环境准备:news-engine 用 MEMIND_DATABASE_URL,Memind 侧用 DATABASE_URL。
|
||
*/
|
||
export function loadPushEnvironment(env = process.env) {
|
||
loadH5Environment(scriptDir);
|
||
loadMemindEnvFiles(resolvePortalRoot(env));
|
||
if (!String(env.DATABASE_URL ?? '').trim() && String(env.MEMIND_DATABASE_URL ?? '').trim()) {
|
||
env.DATABASE_URL = String(env.MEMIND_DATABASE_URL).trim();
|
||
}
|
||
env.MEMIND_PORTAL_H5_ROOT = resolvePortalRoot(env);
|
||
env.MEMIND_WORKSPACE_MAINTENANCE = env.MEMIND_WORKSPACE_MAINTENANCE ?? '0';
|
||
env.MEMIND_PUSH_SKIP_SCHEMA_INIT = env.MEMIND_PUSH_SKIP_SCHEMA_INIT ?? '1';
|
||
if (!isDatabaseConfigured()) {
|
||
throw new Error('DATABASE_URL / MEMIND_DATABASE_URL 未配置');
|
||
}
|
||
return env;
|
||
}
|
||
|
||
/**
|
||
* 轻量上下文:只要数据库和公众号凭证,用于传图 / 生成配图 / 提交草稿。
|
||
*/
|
||
export function createLightContext(env = process.env) {
|
||
const pool = createDbPool();
|
||
const h5Root = resolvePortalH5Root(CODE_ROOT, env);
|
||
const wechatFetch = resolveWechatFetch(env);
|
||
const wechatMpConfigService = createMindSpaceWechatMpConfigService(pool, { env, wechatFetch });
|
||
return {
|
||
pool,
|
||
h5Root,
|
||
wechatFetch,
|
||
wechatMpConfigService,
|
||
async resolveWechat(userId) {
|
||
const credentials = await wechatMpConfigService.getCredentials(userId);
|
||
const token = await fetchWechatMpAccessToken(credentials, { wechatFetch });
|
||
return { credentials, accessToken: token.accessToken };
|
||
},
|
||
async close() {
|
||
await pool.end().catch(() => {});
|
||
},
|
||
};
|
||
}
|
||
|
||
function resolveInitSchemaFn(env = process.env) {
|
||
const skip = String(env.MEMIND_PUSH_SKIP_SCHEMA_INIT ?? '1').trim().toLowerCase();
|
||
if (skip === '1' || skip === 'true' || skip === 'yes') return async () => {};
|
||
if (fs.existsSync(path.join(CODE_ROOT, 'schema.sql'))) return undefined;
|
||
console.warn('[news-item] schema.sql 不在 memind-lib,跳过 initSchema(假定生产库已由 Portal 初始化)');
|
||
return async () => {};
|
||
}
|
||
|
||
/**
|
||
* 完整 Agent 上下文:只有页面生成阶段需要。
|
||
*/
|
||
export async function bootstrapAgentContext(env = process.env) {
|
||
const h5Root = resolvePortalH5Root(CODE_ROOT, env);
|
||
const usersRoot = env.H5_USERS_ROOT?.trim() || path.join(h5Root, 'users');
|
||
const runtime = resolveMindSpaceServerRuntimeOptions(h5Root, env);
|
||
const apiTarget = String(env.TKMIND_API_TARGET ?? 'https://127.0.0.1:18006').trim();
|
||
const apiSecret = env.TKMIND_SERVER__SECRET_KEY ?? 'local-dev-secret';
|
||
const analyticsConfig = resolveMindSpaceAnalyticsConfig();
|
||
const pool = createDbPool();
|
||
let userAuth = null;
|
||
let sessionSnapshotService = null;
|
||
|
||
const domainServices = await bootstrapPortalDomainServices({
|
||
pool,
|
||
h5Root,
|
||
env,
|
||
runtime,
|
||
analyticsConfig,
|
||
getUserAuth: () => userAuth,
|
||
getSessionSnapshotService: () => sessionSnapshotService,
|
||
workspaceMaintenanceEnabled: env.MEMIND_WORKSPACE_MAINTENANCE !== '0',
|
||
logger: console,
|
||
initSchemaFn: resolveInitSchemaFn(env),
|
||
});
|
||
|
||
const authServices = await bootstrapPortalAuthServices({
|
||
pool,
|
||
h5Root,
|
||
usersRoot,
|
||
mindSearchConfigService: domainServices.mindSearchConfigService,
|
||
env,
|
||
logger: console,
|
||
});
|
||
userAuth = authServices.userAuth;
|
||
|
||
const agentServices = await bootstrapPortalAgentServices({
|
||
pool,
|
||
h5Root,
|
||
env,
|
||
runtime,
|
||
apiTarget,
|
||
apiTargets: [apiTarget],
|
||
apiSecret,
|
||
userAuth,
|
||
sessionAccess: authServices.sessionAccess,
|
||
subscriptionService: authServices.subscriptionService,
|
||
mindSpaceRuntimeAdapter: domainServices.mindSpaceRuntimeAdapter,
|
||
mindSpaceAssets: domainServices.mindSpaceAssets,
|
||
resolveUserIdByDirKey: domainServices.resolveUserIdByDirKey,
|
||
workspaceMaintenanceEnabled: env.MEMIND_WORKSPACE_MAINTENANCE !== '0',
|
||
startWorkspaceThumbnailWatcher,
|
||
startWorkspaceAssetSyncWatcher,
|
||
logger: console,
|
||
});
|
||
|
||
const memorySessionServices = await bootstrapPortalMemorySessionServices({
|
||
pool,
|
||
h5Root,
|
||
env,
|
||
llmProviderService: agentServices.llmProviderService,
|
||
userAuth,
|
||
sessionAccess: authServices.sessionAccess,
|
||
logger: console,
|
||
});
|
||
sessionSnapshotService = memorySessionServices.sessionSnapshotService;
|
||
|
||
const gatewayServices = bootstrapPortalGatewayServices({
|
||
pool,
|
||
h5Root,
|
||
env,
|
||
apiTarget,
|
||
apiTargets: [apiTarget],
|
||
apiSecret,
|
||
userAuth,
|
||
sessionAccess: authServices.sessionAccess,
|
||
sessionStreamStore: memorySessionServices.sessionStreamStore,
|
||
llmProviderService: agentServices.llmProviderService,
|
||
subscriptionService: authServices.subscriptionService,
|
||
billingConfigService: authServices.billingConfigService,
|
||
sessionSnapshotService,
|
||
conversationMemoryService: memorySessionServices.conversationMemoryService,
|
||
memoryV2: memorySessionServices.memoryV2,
|
||
systemDisclosurePolicyService: memorySessionServices.systemDisclosurePolicyService,
|
||
wechatCursorExecutorPolicyService: memorySessionServices.wechatCursorExecutorPolicyService,
|
||
mindSpaceAssets: domainServices.mindSpaceAssets,
|
||
directChatService: memorySessionServices.directChatService,
|
||
chatIntentRouter: memorySessionServices.chatIntentRouter,
|
||
syncUserGeneratedPages: domainServices.mindSpacePageSync
|
||
? (userId, options) => domainServices.mindSpacePageSync.syncUserGeneratedPages(userId, options)
|
||
: async () => {},
|
||
isSessionPageDeliveryActive: () => false,
|
||
experienceService: agentServices.experienceService,
|
||
});
|
||
|
||
return {
|
||
pool,
|
||
h5Root,
|
||
userAuth,
|
||
tkmindProxy: gatewayServices.tkmindProxy,
|
||
agentRunGateway: gatewayServices.agentRunGateway,
|
||
cursorExecutorPolicyService: memorySessionServices.wechatCursorExecutorPolicyService,
|
||
sessionSnapshotService,
|
||
wechatMpConfigService: createMindSpaceWechatMpConfigService(pool, { env }),
|
||
async close() {
|
||
await pool.end().catch(() => {});
|
||
},
|
||
};
|
||
}
|
||
|
||
/** 四个脚本统一的 stdout 协议:最后一行是 JSON。 */
|
||
export function runScript(main) {
|
||
main()
|
||
.then((result) => {
|
||
console.log(JSON.stringify({ ok: true, ...result }));
|
||
})
|
||
.catch((error) => {
|
||
console.log(JSON.stringify({
|
||
ok: false,
|
||
error: error instanceof Error ? error.message : String(error),
|
||
code: error?.code ?? null,
|
||
}));
|
||
process.exit(1);
|
||
});
|
||
}
|