feat(mindspace): gate published page delivery

This commit is contained in:
john
2026-07-14 19:13:01 +08:00
parent ae9090948a
commit 27b11894f2
16 changed files with 448 additions and 39 deletions
+29 -3
View File
@@ -279,6 +279,10 @@ function isTransientConnectError(err: unknown) {
return /超时|timeout/i.test(err.message);
}
function isMissingAgentSessionError(err: unknown) {
return err instanceof ApiError && /session not found/i.test(err.message);
}
async function withTransientConnectRetry<T>(fn: () => Promise<T>, attempts = 3): Promise<T> {
let lastErr: unknown;
for (let attempt = 0; attempt < attempts; attempt += 1) {
@@ -329,6 +333,7 @@ export function useTKMindChat(
const subscribedSessionIdRef = useRef<string | null>(null);
const unsubscribeRef = useRef<(() => void) | null>(null);
const connectTokenRef = useRef(0);
const unavailableAgentSessionIdsRef = useRef(new Set<string>());
const messagesRef = useRef<Message[]>([]);
const sessionRef = useRef<Session | null>(null);
const sessionsRef = useRef<SessionSummary[]>([]);
@@ -1139,7 +1144,9 @@ export function useTKMindChat(
}),
);
const resumedPromise =
options?.skipResume || isDirectChatSessionId(sessionId)
options?.skipResume ||
isDirectChatSessionId(sessionId) ||
unavailableAgentSessionIdsRef.current.has(sessionId)
? Promise.resolve(options.seedSession ?? null)
: withTransientConnectRetry(() =>
resumeSession(sessionId, {
@@ -1160,7 +1167,16 @@ export function useTKMindChat(
setMessageHistoryHasMore(messageHistoryHasMoreRef.current);
setMessageHistoryTotal(messageHistoryTotalRef.current);
setMessages(history);
const resumed = await resumedPromise;
let resumed: Session | null = null;
try {
resumed = await resumedPromise;
} catch (err) {
if (!isMissingAgentSessionError(err)) throw err;
// Persisted conversation is still readable even when its transient
// Agent runtime has been reclaimed. Do not leave a completed chat
// in the connecting state or retry resume on future selections.
unavailableAgentSessionIdsRef.current.add(sessionId);
}
if (token !== connectTokenRef.current) return;
setSession({ ...detail, ...(resumed ?? {}), id: sessionId });
setChatState('idle');
@@ -1383,6 +1399,7 @@ export function useTKMindChat(
mindspaceContext?: MindSpaceChatContext;
messageId?: string;
forceDeepReasoning?: boolean;
pgRequired?: boolean;
selectedChatSkill?: string;
fileAttachments?: ChatFileAttachment[];
},
@@ -1412,7 +1429,10 @@ export function useTKMindChat(
: '';
const userPrefix = buildUserAddressPrefix(userRef.current);
const skillPrefix = buildAutoChatSkillPrefix(trimmed, grantedSkills ?? []);
const agentPrefix = `${userPrefix}${mindspacePrefix}${skillPrefix}`;
const pgContractPrefix = options?.pgRequired
? '[交付约束:用户已明确要求使用专属 PostgreSQL 数据空间。若生成页面,必须按 page-data-collect 完成建表、dataset、policy 和 workspace page 绑定;禁止 localStorage、SQLite、静态 JSON 或内存持久化。所有验证通过前不得回复已发布或给出页面链接。]\n'
: '';
const agentPrefix = `${userPrefix}${mindspacePrefix}${skillPrefix}${pgContractPrefix}`;
const priorMessageCount = messagesRef.current.length;
const userMessage = buildUserMessage(trimmed, {
id: options?.messageId,
@@ -1429,6 +1449,7 @@ export function useTKMindChat(
? userMessage.metadata.memindRun
: {}),
sessionMessageCount: priorMessageCount,
...(options?.pgRequired ? { pgRequired: true } : {}),
...(options?.selectedChatSkill ? { selectedChatSkill: options.selectedChatSkill } : {}),
},
};
@@ -1582,6 +1603,11 @@ export function useTKMindChat(
const nextChatState = resolvePostAgentRunChatState({
chatState: chatStateRef.current,
finishedViaPortalDirectChat,
// The agent-run result is authoritative even when the immediate
// session snapshot has not yet carried portal-direct metadata.
// Without this, a completed Page Data task can re-enter streaming
// and leave the Stop button attached to no active request.
agentRunSucceeded: finishedRun.status === 'succeeded',
});
if (nextChatState === 'idle') {
clearActiveRequestMissingTimer();