diff --git a/docs/103-rollback-agent-time-fix-2026-07-01.md b/docs/103-rollback-agent-time-fix-2026-07-01.md index 8461a87..9975f9b 100644 --- a/docs/103-rollback-agent-time-fix-2026-07-01.md +++ b/docs/103-rollback-agent-time-fix-2026-07-01.md @@ -4,16 +4,23 @@ - 分支:`0630007-agent-time-fix`(从 `0630007-memind` 切出,**不含** miniapp / 0630006 改动) - 修复:向 Agent 注入 `TKMind 当前时间基准`(Asia/Shanghai),避免回复里报 UTC 时间 +- **v2**:103 上 `harness_remember` 返回 `remembered:false`,仅靠 harness 不可靠;改为在每条 `/reply` 用户消息里直接注入时间前缀(不依赖 harness) ## 发布前 103 状态(可回退目标) | 项 | 值 | |---|---| -| release_id | `20260701-075736-16828a5` | -| git_head | `16828a58f7fc79d7b9a174ffa87f5579894798fc` | -| git_branch | `0630007-memind` | +| release_id | `20260701-083212-7e3c018` | +| git_head | `7e3c018a5503ba691b0f7cc26d0df2e4276e9a9c` | +| 说明 | 仅有 harness 时间锚点,103 harness 不生效 | -回退到上述 release 发布前版本: +回退到 harness-only 版: + +```bash +bash scripts/rollback-portal-runtime-prod.sh 20260701-083212-7e3c018 --yes +``` + +更早(完全无时间锚点): ```bash bash scripts/rollback-portal-runtime-prod.sh 20260701-075736-16828a5 --yes diff --git a/tkmind-proxy.mjs b/tkmind-proxy.mjs index 0ebf587..610bbec 100644 --- a/tkmind-proxy.mjs +++ b/tkmind-proxy.mjs @@ -4,7 +4,7 @@ import { appendBalanceEvent, createSseBillingTransform } from './sse-billing.mjs import { developerToolsFromPolicy } from './capabilities.mjs'; import { evaluateProxyRequest, isNativeH5ApiPath } from './policies.mjs'; import { buildSandboxSessionConstraints } from './user-publish.mjs'; -import { buildTaskRoutingAgentText } from './user-memory-profile.mjs'; +import { buildCurrentTimeAgentPrefix, buildTaskRoutingAgentText } from './user-memory-profile.mjs'; import { reconcileAgentSession } from './session-reconcile.mjs'; import { createImgproxySigner } from './imgproxy-signer.mjs'; @@ -119,11 +119,11 @@ function messageHasImages(userMessage) { return extractImageUrlsFromMessage(userMessage).length > 0; } -function injectTaskRoutingHint(body, sessionPolicy) { +function prependAgentTextToUserMessage(body, transformText) { const originalText = firstUserText(body?.user_message); if (!originalText?.trim()) return body; - const agentText = buildTaskRoutingAgentText(originalText, sessionPolicy); + const agentText = transformText(originalText); if (!agentText || agentText === originalText) return body; const userMessage = body.user_message ?? {}; @@ -153,6 +153,20 @@ function injectTaskRoutingHint(body, sessionPolicy) { }; } +function injectCurrentTimeAnchor(body, timezone) { + const tz = String(timezone ?? process.env.H5_DEFAULT_TIMEZONE ?? 'Asia/Shanghai').trim() || 'Asia/Shanghai'; + return prependAgentTextToUserMessage(body, (originalText) => { + if (originalText.includes('TKMind 当前时间基准')) return originalText; + return `${buildCurrentTimeAgentPrefix({ timezone: tz })}${originalText}`; + }); +} + +function injectTaskRoutingHint(body, sessionPolicy) { + return prependAgentTextToUserMessage(body, (originalText) => + buildTaskRoutingAgentText(originalText, sessionPolicy), + ); +} + export async function buildVisionPayload({ userMessage, userId, @@ -899,8 +913,12 @@ export function createTkmindProxy({ let baseBody = req.body; if (isReplyPath && !policyState.unrestricted) { + const publishLayout = await userAuth + .getUserPublishLayout(req.currentUser.id) + .catch(() => null); + baseBody = injectCurrentTimeAnchor(req.body, publishLayout?.timezone); baseBody = injectTaskRoutingHint( - req.body, + baseBody, await userAuth.getAgentSessionPolicy(req.currentUser.id), ); } diff --git a/user-memory-profile.mjs b/user-memory-profile.mjs index 4ff9817..eadaa0a 100644 --- a/user-memory-profile.mjs +++ b/user-memory-profile.mjs @@ -192,6 +192,14 @@ export function resolveTimeOfDayPeriod(hour) { return { period: '晚上', greeting: '晚上好' }; } +export function buildCurrentTimeAgentPrefix({ now = Date.now(), timezone = DEFAULT_TIMEZONE } = {}) { + return [ + '【TKMind 时间基准 - 仅供回答时间/日期/时段问候,不要向用户复述】', + renderCurrentTimeAnchor({ now, timezone }), + '', + ].join('\n'); +} + export function renderCurrentTimeAnchor({ now = Date.now(), timezone = DEFAULT_TIMEZONE } = {}) { const { year, month, day } = formatDateParts(now, timezone); const { hour, minute } = formatLocalClockParts(now, timezone); diff --git a/user-memory-profile.test.mjs b/user-memory-profile.test.mjs index f665285..048d141 100644 --- a/user-memory-profile.test.mjs +++ b/user-memory-profile.test.mjs @@ -12,6 +12,7 @@ import { resolveTimeOfDayPeriod, suggestCodeExecutorForTask, buildSessionMemoryEntries, + buildCurrentTimeAgentPrefix, ensureUserMemoryProfile, hasMemoryStore, loadUserMemoryProfile, @@ -123,6 +124,16 @@ test('buildSessionMemoryEntries injects time anchor even without memory store', assert.match(entries[0].content, /13:00(中午)/); }); +test('buildCurrentTimeAgentPrefix wraps Shanghai anchor for agent-only injection', () => { + const text = buildCurrentTimeAgentPrefix({ + now: Date.UTC(2026, 5, 29, 5, 0, 0), + timezone: 'Asia/Shanghai', + }); + assert.match(text, /不要向用户复述/); + assert.match(text, /2026-06-29(星期一)/); + assert.match(text, /13:00(中午)/); +}); + test('renderCurrentTimeAnchor formats Shanghai weekday from exact date', () => { const text = renderCurrentTimeAnchor({ now: Date.UTC(2026, 5, 29, 5, 0, 0), diff --git a/wechat-mp.mjs b/wechat-mp.mjs index d0560a1..8736e86 100644 --- a/wechat-mp.mjs +++ b/wechat-mp.mjs @@ -6,7 +6,7 @@ import { developerToolsFromPolicy } from './capabilities.mjs'; import { mergeMessageContent } from './message-stream.mjs'; import { reconcileAgentSession } from './session-reconcile.mjs'; import { isScheduleIntent, parseScheduleIntent, shouldUseScheduleAssistant } from './schedule-intent.mjs'; -import { renderCurrentTimeAnchor } from './user-memory-profile.mjs'; +import { buildCurrentTimeAgentPrefix } from './user-memory-profile.mjs'; import { PUBLISH_ROOT_DIR } from './user-publish.mjs'; import { downloadTemporaryMedia, persistWechatImage } from './wechat-media.mjs'; import { buildAckText } from './wechat/ack/ack-provider.mjs'; @@ -1073,12 +1073,12 @@ function buildWechatAgentPrompt(intent) { ].join('\n') : ''; const scheduleTimezone = process.env.H5_DEFAULT_TIMEZONE || 'Asia/Shanghai'; + const currentTimeHint = buildCurrentTimeAgentPrefix({ timezone: scheduleTimezone }); const scheduleAssistantHint = shouldUseScheduleAssistant(intent?.agentText ?? intent?.content) ? [ '【日程技能要求】这条消息涉及待办、提醒或日程。', '开始前先加载 `schedule-assistant` skill,并严格按 skill 里的边界执行。', '写入工具时优先使用 startLocal / endLocal / remindLocal(YYYY-MM-DD HH:mm),不要自行估算 Unix 毫秒。', - renderCurrentTimeAnchor({ timezone: scheduleTimezone }), '只有在 `schedule_create_item` / `schedule_create_reminder` 等工具成功返回后,才能告诉用户“已经设置好了”。', intent?.msgId ? `调用 schedule_create_item 时必须传入 sourceMessageId: ${intent.msgId}` : '', '', @@ -1086,6 +1086,7 @@ function buildWechatAgentPrompt(intent) { : ''; if (msgType === 'voice') { return [ + currentTimeHint, scheduleAssistantHint, '【微信服务号语音消息】用户通过语音输入,以下是微信识别结果。', '', @@ -1096,6 +1097,7 @@ function buildWechatAgentPrompt(intent) { } if (msgType === 'image') { return [ + currentTimeHint, scheduleAssistantHint, '【微信服务号图片消息】用户发送了图片。', '如用户没有明确要求,请先根据图片内容给出简短理解,并询问下一步。', @@ -1108,6 +1110,7 @@ function buildWechatAgentPrompt(intent) { if (msgType === 'location') { const location = intent?.location ?? {}; return [ + currentTimeHint, scheduleAssistantHint, '【微信服务号位置消息】用户发送了当前位置。', location.label ? `地址:${location.label}` : '', @@ -1125,6 +1128,7 @@ function buildWechatAgentPrompt(intent) { if (msgType === 'link') { const link = intent?.link ?? {}; return [ + currentTimeHint, scheduleAssistantHint, '【微信服务号链接消息】用户分享了链接。', link.title ? `标题:${link.title}` : '', @@ -1135,7 +1139,7 @@ function buildWechatAgentPrompt(intent) { .join('\n'); } const content = String(intent?.agentText ?? intent?.content ?? '').trim(); - const lines = []; + const lines = [currentTimeHint]; if (pagePublishHint) lines.push(pagePublishHint); if (scheduleAssistantHint) lines.push(scheduleAssistantHint); lines.push(