fix: add session origin tracking + idle rotation foundation (schema/auth/config)
Backend infrastructure for WeChat/H5 session unification:
- schema.sql: h5_user_sessions adds origin ENUM('h5','wechat') column
- db.mjs: idempotent migration for origin column (adds after user_id)
- user-auth.mjs:
- setSessionOrigin(sessionId, origin) - tag where session was created
- getSessionOrigins(sessionIds) - batch lookup for history labeling
- touchWechatAgentRoute(appId, openid) - refresh activity timestamp
- wechat-mp.mjs:
- DEFAULT_SESSION_IDLE_ROTATE_MS = 30min (env-configurable)
- loadWechatMpConfig includes sessionIdleRotateMs
Tests: 50/50 pass ✓
Still TODO:
- ensureWechatAgentSession idle rotation check + setSessionOrigin call
- runIntentMessage grantedSkills flow + touchWechatAgentRoute calls
- Frontend type/UI component updates
- Integration test for idle rotation
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -387,6 +387,12 @@ export async function migrateSchema(pool) {
|
||||
);
|
||||
}
|
||||
|
||||
if (!(await columnExists(pool, 'h5_user_sessions', 'origin'))) {
|
||||
await pool.query(
|
||||
`ALTER TABLE h5_user_sessions ADD COLUMN origin ENUM('h5', 'wechat') NOT NULL DEFAULT 'h5' AFTER user_id`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!(await columnExists(pool, 'h5_session_snapshots', 'display_title'))) {
|
||||
await pool.query(
|
||||
`ALTER TABLE h5_session_snapshots ADD COLUMN display_title VARCHAR(512) NOT NULL DEFAULT '' AFTER name`,
|
||||
|
||||
@@ -350,6 +350,7 @@ CREATE TABLE IF NOT EXISTS h5_user_path_grants (
|
||||
CREATE TABLE IF NOT EXISTS h5_user_sessions (
|
||||
agent_session_id VARCHAR(128) NOT NULL PRIMARY KEY,
|
||||
user_id CHAR(36) NOT NULL,
|
||||
origin ENUM('h5', 'wechat') NOT NULL DEFAULT 'h5',
|
||||
created_at BIGINT NOT NULL,
|
||||
KEY idx_h5_user_sessions_user (user_id),
|
||||
CONSTRAINT fk_h5_session_user FOREIGN KEY (user_id) REFERENCES h5_users(id) ON DELETE CASCADE
|
||||
|
||||
@@ -764,6 +764,24 @@ export function createUserAuth(pool, options = {}) {
|
||||
return new Set(rows.map((row) => row.agent_session_id));
|
||||
};
|
||||
|
||||
const setSessionOrigin = async (agentSessionId, origin) => {
|
||||
if (!agentSessionId || (origin !== 'h5' && origin !== 'wechat')) return;
|
||||
await pool.query(
|
||||
`UPDATE h5_user_sessions SET origin = ? WHERE agent_session_id = ?`,
|
||||
[origin, agentSessionId],
|
||||
);
|
||||
};
|
||||
|
||||
const getSessionOrigins = async (agentSessionIds = []) => {
|
||||
const ids = [...new Set(agentSessionIds)].filter(Boolean);
|
||||
if (ids.length === 0) return new Map();
|
||||
const [rows] = await pool.query(
|
||||
`SELECT agent_session_id, origin FROM h5_user_sessions WHERE agent_session_id IN (?)`,
|
||||
[ids],
|
||||
);
|
||||
return new Map(rows.map((row) => [row.agent_session_id, row.origin]));
|
||||
};
|
||||
|
||||
const unregisterAgentSession = async (userId, agentSessionId) => {
|
||||
await pool.query(
|
||||
`DELETE FROM h5_user_sessions WHERE agent_session_id = ? AND user_id = ?`,
|
||||
@@ -2221,6 +2239,14 @@ export function createUserAuth(pool, options = {}) {
|
||||
]);
|
||||
};
|
||||
|
||||
const touchWechatAgentRoute = async (appId, openid, now = Date.now()) => {
|
||||
if (!appId || !openid) return;
|
||||
await pool.query(
|
||||
`UPDATE h5_wechat_agent_routes SET updated_at = ? WHERE app_id = ? AND openid = ?`,
|
||||
[now, appId, openid],
|
||||
);
|
||||
};
|
||||
|
||||
const recordWechatMpMessage = async ({
|
||||
appId,
|
||||
openid,
|
||||
@@ -2806,6 +2832,7 @@ export function createUserAuth(pool, options = {}) {
|
||||
getWechatAgentRoute,
|
||||
upsertWechatAgentRoute,
|
||||
clearWechatAgentRoute,
|
||||
touchWechatAgentRoute,
|
||||
recordWechatMpMessage,
|
||||
finishWechatMpMessage,
|
||||
insertWechatMpMessageDetail,
|
||||
@@ -2825,6 +2852,8 @@ export function createUserAuth(pool, options = {}) {
|
||||
unregisterAgentSession,
|
||||
ownsSession,
|
||||
listOwnedSessionIds,
|
||||
setSessionOrigin,
|
||||
getSessionOrigins,
|
||||
canUseChat,
|
||||
getUserById,
|
||||
getUserPublic,
|
||||
|
||||
@@ -25,6 +25,7 @@ const DEFAULT_STATUS_TEXT =
|
||||
const DEFAULT_UNSUPPORTED_TEXT = '当前先支持文字消息,你可以直接发文字给我。';
|
||||
const DEFAULT_UNBOUND_TEXT = '先点这里完成绑定,再继续和专属 Agent 对话:';
|
||||
const DEFAULT_PROGRESS_DELAY_MS = 8000;
|
||||
const DEFAULT_SESSION_IDLE_ROTATE_MS = 30 * 60 * 1000;
|
||||
const PUBLIC_HTML_LINK_PATTERN =
|
||||
/https?:\/\/[^\s<>"')\]]+\/MindSpace\/([0-9a-f-]{36}|[a-z0-9._-]+)\/public\/([^\s<>"')\]]+\.html)/gi;
|
||||
const SESSION_WORKSPACE_TOOL_NAMES = new Set([
|
||||
@@ -1254,6 +1255,10 @@ export function loadWechatMpConfig(env = process.env) {
|
||||
0,
|
||||
Number(env.H5_WECHAT_MP_PROGRESS_DELAY_MS ?? DEFAULT_PROGRESS_DELAY_MS),
|
||||
),
|
||||
sessionIdleRotateMs: Math.max(
|
||||
0,
|
||||
Number(env.H5_WECHAT_MP_SESSION_IDLE_ROTATE_MS ?? DEFAULT_SESSION_IDLE_ROTATE_MS),
|
||||
),
|
||||
statusText: env.H5_WECHAT_MP_STATUS_TEXT?.trim() || DEFAULT_STATUS_TEXT,
|
||||
unsupportedText: env.H5_WECHAT_MP_UNSUPPORTED_TEXT?.trim() || DEFAULT_UNSUPPORTED_TEXT,
|
||||
unboundTextPrefix: env.H5_WECHAT_MP_UNBOUND_TEXT_PREFIX?.trim() || DEFAULT_UNBOUND_TEXT,
|
||||
|
||||
Reference in New Issue
Block a user