diff --git a/api/.vscode/launch.json b/api/.vscode/launch.json
index a68174c..e2bbc65 100644
--- a/api/.vscode/launch.json
+++ b/api/.vscode/launch.json
@@ -115,6 +115,9 @@
"IMAGE_ASSETS_DATABASE_URL":"postgres://postgres:postgres@10.8.16.77:5434/deep_agent_db",
/* 阿里云百炼 API KEY */
"DASHSCOPE_API_KEY": "",
+ "OCR_API_URL": "http://112.4.244.131:8056",
+ "FS_SUPER_AGENT_BUSINESS_API": "http://localhost:8000",
+ "FS_SUPER_AGENT_FRAMEWORK_API": "http://localhost:8005",
"AUTH_JWT_VERIFICATION_KEY": "local-development-only-long-random-secret",
}
}
diff --git a/api/app/controllers/superAgentProtocol.js b/api/app/controllers/superAgentProtocol.js
index a927022..2d55534 100644
--- a/api/app/controllers/superAgentProtocol.js
+++ b/api/app/controllers/superAgentProtocol.js
@@ -55,10 +55,6 @@ const buildTrustedHeaders = (ctx, hasJsonBody = false) => {
Accept: 'application/json, text/event-stream',
Authorization: `Bearer ${issued.token}`,
};
- const searchToken = String(ctx.fs?.curUser?.token || '').trim();
- if (searchToken) {
- headers['X-PEP-Token'] = searchToken;
- }
if (hasJsonBody) headers['Content-Type'] = 'application/json';
return headers;
};
diff --git a/api/config/default.js b/api/config/default.js
index 9b4d83e..be45fad 100644
--- a/api/config/default.js
+++ b/api/config/default.js
@@ -406,7 +406,12 @@ module.exports = {
hostUrl: process.env.XFYUN_SPEECH_HOST_URL || "wss://iat-api.xfyun.cn/v2/iat",
},
superAgent: {
- baseUrl: process.env.FS_SUPER_AGENT_API || "http://localhost:8080",
+ baseUrl: process.env.FS_SUPER_AGENT_FRAMEWORK_API || "http://127.0.0.1:8005",
+ businessBaseUrl:
+ process.env.FS_SUPER_AGENT_BUSINESS_API ||
+ process.env.FS_SUPER_AGENT_API ||
+ "http://127.0.0.1:8000",
+ frameworkBaseUrl: process.env.FS_SUPER_AGENT_FRAMEWORK_API || "http://127.0.0.1:8005",
jwt: {
algorithm: process.env.FS_SUPER_AGENT_JWT_ALGORITHM || "HS256",
secret: process.env.AUTH_JWT_VERIFICATION_KEY || "",
diff --git a/web/client/src/sections/superAgent/components/SuperAgentSidebar.jsx b/web/client/src/sections/superAgent/components/SuperAgentSidebar.jsx
index 495bd60..0c5123d 100644
--- a/web/client/src/sections/superAgent/components/SuperAgentSidebar.jsx
+++ b/web/client/src/sections/superAgent/components/SuperAgentSidebar.jsx
@@ -73,6 +73,7 @@ const SuperAgentSidebar = ({
collapsedFolderMap,
activeSessionId,
isLoadingSessions,
+ sidebarError,
isCreatingChat,
isCreatingTask,
isLoggedIn,
@@ -259,6 +260,8 @@ const SuperAgentSidebar = ({
{isLoadingSessions ? (
正在加载会话...
+ ) : sidebarError ? (
+ {sidebarError.message}
) : (
<>
diff --git a/web/client/src/sections/superAgent/constants.js b/web/client/src/sections/superAgent/constants.js
index 01736f7..ae09877 100644
--- a/web/client/src/sections/superAgent/constants.js
+++ b/web/client/src/sections/superAgent/constants.js
@@ -23,17 +23,25 @@ import NetworkIcon from "@assets/img/newAiChat/icon (11).png";
import ShortcutArticleIcon from "@assets/img/newAiChat/icon (4).png";
import ShortcutWriteIcon from "@assets/img/newAiChat/icon (7).png";
-export const FOLDER_COLLAPSED_STORAGE_KEY = "superAgent_folder_collapsed";
+export const FOLDER_COLLAPSED_STORAGE_KEY = "superAgent_v2_folder_collapsed";
export const SIDEBAR_SESSION_DRAG_TYPE = "application/x-super-sidebar-session";
/** 虚拟默认文件夹(folder_id = null 的任务归入此处,不落库) */
export const DEFAULT_TASK_FOLDER_ID = "__default__";
export const DEFAULT_TASK_FOLDER_NAME = "默认任务集";
-export const TASK_STORAGE_KEY = "superAgent_tender_task_list";
-export const TASK_SESSION_STORAGE_KEY = "superAgent_tender_session_ids";
-export const PENDING_EMPTY_SESSION_STORAGE_KEY = "superAgent_pending_empty_session";
+export const TASK_STORAGE_KEY = "superAgent_v2_tender_task_list";
+export const TASK_SESSION_STORAGE_KEY = "superAgent_v2_tender_session_ids";
+export const PENDING_EMPTY_SESSION_STORAGE_KEY = "superAgent_v2_pending_empty_session";
export const ACTIVE_SESSION_STORAGE_KEY = "superAgent_v2_active_conversation_id";
+
+export const LEGACY_SUPER_AGENT_STORAGE_KEYS = [
+ "superAgent_folder_collapsed",
+ "superAgent_tender_task_list",
+ "superAgent_tender_session_ids",
+ "superAgent_pending_empty_session",
+ "superAgent_active_session_id",
+];
export const PHONE_REGEXP = /^1\d{10}$/;
export const CHAT_WELCOME_TITLE = "你好,我是飞小尚 AI 办公助手";
diff --git a/web/client/src/sections/superAgent/container/index.jsx b/web/client/src/sections/superAgent/container/index.jsx
index e080310..1ff21a3 100644
--- a/web/client/src/sections/superAgent/container/index.jsx
+++ b/web/client/src/sections/superAgent/container/index.jsx
@@ -56,6 +56,7 @@ import {
normalizeCheckpoint,
replaceCheckpointData,
} from "../utils/checkpoint";
+import { getSuperAgentErrorMessage } from "../utils/agentApiParse";
import { shouldShowBodyWorkspaceEntry } from "../utils/workspace";
import {
buildStreamConversationMessages,
@@ -457,6 +458,7 @@ const SuperAgent = () => {
activeSessionId,
activeConversationId,
isLoadingSessions,
+ sidebarError,
isUploadingFile,
isCreatingChat,
isCreatingTask,
@@ -799,7 +801,7 @@ const SuperAgent = () => {
]);
const handleProtocolError = useCallback((error) => {
- message.error(error?.message || "智能体服务暂不可用");
+ message.error(getSuperAgentErrorMessage(error, "智能体服务暂不可用"));
}, []);
const stream = useSuperAgentStream({
@@ -2444,6 +2446,7 @@ const SuperAgent = () => {
collapsedFolderMap,
activeSessionId: activeConversationId || activeSessionId,
isLoadingSessions,
+ sidebarError,
isCreatingChat,
isCreatingTask,
isLoggedIn,
diff --git a/web/client/src/sections/superAgent/hooks/useSuperAgentChat.js b/web/client/src/sections/superAgent/hooks/useSuperAgentChat.js
index 9878694..6d798c1 100644
--- a/web/client/src/sections/superAgent/hooks/useSuperAgentChat.js
+++ b/web/client/src/sections/superAgent/hooks/useSuperAgentChat.js
@@ -110,6 +110,7 @@ const useSuperAgentChat = ({
sidebarTree,
taskList,
ordinaryChatList,
+ sidebarError,
isLoadingSidebar,
collapsedFolderMap,
loadSidebar,
@@ -1865,6 +1866,7 @@ const useSuperAgentChat = ({
uploadedFileList,
uploadProgress,
ordinaryChatList,
+ sidebarError,
sidebarTree,
collapsedFolderMap,
activeSessionId,
diff --git a/web/client/src/sections/superAgent/hooks/useSuperAgentSidebar.js b/web/client/src/sections/superAgent/hooks/useSuperAgentSidebar.js
index 73afd52..23a2af0 100644
--- a/web/client/src/sections/superAgent/hooks/useSuperAgentSidebar.js
+++ b/web/client/src/sections/superAgent/hooks/useSuperAgentSidebar.js
@@ -13,6 +13,7 @@ import {
} from "../actions/api";
import { listAllConversations, listAllTasksets } from "../actions/agentResources";
import { computeSessionDrop } from "../utils/sidebarDrag";
+import { getSuperAgentErrorMessage } from "../utils/agentApiParse";
import {
normalizeSessionTitle,
buildSidebarTreeFromAgentResources,
@@ -44,6 +45,7 @@ const EMPTY_TREE = { realms: [] };
const useSuperAgentSidebar = ({ currentUserId, actions, dispatch }) => {
const [sidebarTree, setSidebarTree] = useState(EMPTY_TREE);
const [isLoadingSidebar, setIsLoadingSidebar] = useState(false);
+ const [sidebarError, setSidebarError] = useState(null);
const [collapsedFolderMap, setCollapsedFolderMap] = useState(() => readCollapsedFolderMap());
const taskList = useMemo(
@@ -73,9 +75,15 @@ const useSuperAgentSidebar = ({ currentUserId, actions, dispatch }) => {
]);
const nextTree = buildSidebarTreeFromAgentResources(tasksets, conversations);
setSidebarTree(nextTree);
+ setSidebarError(null);
return nextTree;
} catch (error) {
- message.error(error?.message || "获取侧边栏失败");
+ const normalizedError = {
+ ...error,
+ message: getSuperAgentErrorMessage(error, "获取侧边栏失败"),
+ };
+ setSidebarError(normalizedError);
+ message.error(normalizedError.message);
return EMPTY_TREE;
} finally {
if (shouldShowLoading) {
@@ -236,6 +244,7 @@ const useSuperAgentSidebar = ({ currentUserId, actions, dispatch }) => {
return {
sidebarTree,
+ sidebarError,
taskList,
ordinaryChatList,
isLoadingSidebar,
diff --git a/web/client/src/sections/superAgent/hooks/useSuperAgentStream.js b/web/client/src/sections/superAgent/hooks/useSuperAgentStream.js
index 1f31090..1204aea 100644
--- a/web/client/src/sections/superAgent/hooks/useSuperAgentStream.js
+++ b/web/client/src/sections/superAgent/hooks/useSuperAgentStream.js
@@ -20,11 +20,13 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useStream } from "@langchain/react";
import { ProtocolSseTransportAdapter } from "@langchain/langgraph-sdk";
import { ROOT_PUMP_CHANNELS } from "@langchain/langgraph-sdk/stream";
-import { getCompatibleToken } from "../../../utils/authCompat";
import {
+ clearSuperAgentAccessToken,
getSuperAgentFrameworkApiUrl,
withAgentAuthorization,
} from "../utils/agentApi";
+import { getSuperAgentErrorMessage } from "../utils/agentApiParse";
+import { superAgentLog, superAgentWarn } from "../utils/debugLog";
const BUSINESS_CHANNELS = [
"custom",
@@ -254,9 +256,16 @@ class SingleSubscriberSseTransportAdapter extends ProtocolSseTransportAdapter {
async getState() {
const requestVersion = ++this.stateRequestVersion;
const requestedThreadId = String(this.threadId || "").trim();
- // Agent 当前未提供 /threads/:threadId/state;useStream hydrate 只能使用
- // 已从事件流获得的快照,不能为新会话触发一个必然失败的 HTTP 请求。
- const state = this.latestState;
+ if (!requestedThreadId) {
+ this.latestState = null;
+ this.onStateChange?.();
+ return null;
+ }
+
+ // ProtocolSseTransportAdapter 的 getState() 会请求当前 thread 的
+ // /state;其 fetchImpl 已绑定 fetchUseStreamTransport,因此会复用
+ // 8005 代理、Agent JWT 和 401 换票逻辑。
+ const state = await super.getState();
// 旧 state 快照不能在切换 thread 后覆盖新会话。
if (
requestVersion === this.stateRequestVersion &&
@@ -534,14 +543,21 @@ const getEventDedupeKey = (event) => {
const normalizeStreamError = (error) => {
const status = error?.status || error?.response?.status;
- if (status === 401 || status === 403) return new Error("登录状态已失效,请重新登录");
- if (status === 404) return new Error("智能体服务暂不可用");
+ const normalized = error instanceof Error ? error : new Error("智能体协议响应错误");
+ normalized.status = status || normalized.status || 0;
+ normalized.code = error?.code || normalized.code || "";
+ normalized.requestId = error?.requestId || error?.request_id || normalized.requestId || "";
+ normalized.message = getSuperAgentErrorMessage(normalized, "智能体协议响应错误");
+ if (status === 404) normalized.message = "智能体服务暂不可用";
const message = String(error?.message || "");
if (status === 429 || /(?:^|\D)429(?:\D|$)|too many requests|rate limit/i.test(message)) {
- return new Error("事件流连接繁忙,正在自动恢复,请稍候");
+ normalized.message = "事件流连接繁忙,正在自动恢复,请稍候";
}
- if (/sse|stream|network|fetch/i.test(message)) return new Error("智能体流式连接异常");
- return error instanceof Error ? error : new Error("智能体协议响应错误");
+ if (/sse|stream|network|fetch/i.test(message)) normalized.message = "智能体流式连接异常";
+ if (normalized.requestId && !normalized.message.includes(normalized.requestId)) {
+ normalized.message = `${normalized.message}(request_id: ${normalized.requestId})`;
+ }
+ return normalized;
};
const useSuperAgentStream = ({
@@ -584,16 +600,31 @@ const useSuperAgentStream = ({
const localRunHasSeenLoadingRef = useRef(false);
const activeSessionIdRef = useRef("");
activeSessionIdRef.current = activeSessionId;
- const loginToken = getCompatibleToken();
- // 保持登录态变化时重建 transport;Agent JWT 在 fetch 拦截器中换票。
- // 会话切换由 callerOptions 的会话级 fetch 变化触发新 controller。
+ // Agent 请求只使用短时 Agent JWT;ai-center token 只用于 query api 换票。
const defaultHeaders = useMemo(
() => ({
Accept: "application/json, text/event-stream",
- ...(loginToken ? { "X-PEP-Token": loginToken } : {}),
}),
- [loginToken]
+ []
);
+ const fetchAuthorizedStream = useCallback(async (input, requestInit) => {
+ let authorizedInit = await withAgentAuthorization(requestInit);
+ let response = await fetchEventStreamWithRecovery(input, authorizedInit);
+ if (response.status !== 401 || requestInit?.signal?.aborted) return response;
+
+ superAgentWarn("stream.auth", "agent_token_refresh", {
+ endpoint: String(input?.url || input || "").split("?")[0],
+ status: response.status,
+ });
+ clearSuperAgentAccessToken();
+ authorizedInit = await withAgentAuthorization(requestInit);
+ response = await fetchEventStreamWithRecovery(input, authorizedInit);
+ superAgentLog("stream.auth", "agent_token_refresh_result", {
+ endpoint: String(input?.url || input || "").split("?")[0],
+ status: response.status,
+ });
+ return response;
+ }, []);
const fetchUseStreamTransport = useCallback(async (...args) => {
const [input, init = {}] = args;
if (isUseStreamHistoryDiscoveryRequest(input)) {
@@ -629,9 +660,8 @@ const useSuperAgentStream = ({
};
}
- const authorizedInit = await withAgentAuthorization(requestInit);
- return fetchEventStreamWithRecovery(input, authorizedInit);
- }, []);
+ return fetchAuthorizedStream(input, requestInit);
+ }, [fetchAuthorizedStream]);
const eventTransport = useMemo(
() => {
const transport = new SingleSubscriberSseTransportAdapter({
@@ -898,6 +928,11 @@ const useSuperAgentStream = ({
// 切换会话后旧 Thread 的监听器可能在清理前再收到一个事件,
// 不能把旧会话的事件写入新会话的流式消息状态。
if (activeSessionIdRef.current !== listenerSessionId) return;
+ superAgentLog("stream.event", "received", {
+ threadId: listenerSessionId,
+ eventType: event?.method || "unknown",
+ lifecycleType: event?.params?.data?.event || "",
+ });
handleMessageEvent(event);
const eventPayload = getEventPayload(event) || {};
if (shouldRefreshRuntimeState({
diff --git a/web/client/src/sections/superAgent/hooks/useSuperAgentWorkspace.js b/web/client/src/sections/superAgent/hooks/useSuperAgentWorkspace.js
index a3c438f..56f365d 100644
--- a/web/client/src/sections/superAgent/hooks/useSuperAgentWorkspace.js
+++ b/web/client/src/sections/superAgent/hooks/useSuperAgentWorkspace.js
@@ -58,6 +58,7 @@ import {
isSupportedCheckpointUiType,
normalizeCheckpoint,
} from "../utils/checkpoint";
+import { getSuperAgentErrorMessage } from "../utils/agentApiParse";
import { renderSuperAgentMarkdown } from "../utils/tenderMarkdown";
const buildSectionParams = (sessionId, sectionId) => ({
@@ -647,7 +648,7 @@ const useSuperAgentWorkspace = ({
error,
});
if (!silent) {
- message.error(error?.message || "刷新正文工作区失败");
+ message.error(getSuperAgentErrorMessage(error, "刷新正文工作区失败"));
}
return false;
}
@@ -1332,7 +1333,7 @@ const useSuperAgentWorkspace = ({
workspaceType: workspaceData.type,
error,
});
- message.error(error?.message || "保存修改失败");
+ message.error(getSuperAgentErrorMessage(error, "保存修改失败"));
}
};
@@ -1662,7 +1663,7 @@ const useSuperAgentWorkspace = ({
await refreshSectionAfterConflict(sessionId, { id: sectionId });
message.warning("章节已被智能体更新,请刷新后重新编辑");
} else {
- message.error(error?.message || "章节保存失败,请稍后重试");
+ message.error(getSuperAgentErrorMessage(error, "章节保存失败,请稍后重试"));
}
return false;
}
@@ -1716,7 +1717,7 @@ const useSuperAgentWorkspace = ({
sessionId,
error,
});
- message.error(error?.message || "导出失败");
+ message.error(getSuperAgentErrorMessage(error, "导出失败"));
}
};
@@ -1802,7 +1803,7 @@ const useSuperAgentWorkspace = ({
error,
});
if (!silent) {
- message.error(error?.message || "获取正文章节失败");
+ message.error(getSuperAgentErrorMessage(error, "获取正文章节失败"));
}
return false;
}
diff --git a/web/client/src/sections/superAgent/utils/agentApi.js b/web/client/src/sections/superAgent/utils/agentApi.js
index 02e9234..fdd2d2a 100644
--- a/web/client/src/sections/superAgent/utils/agentApi.js
+++ b/web/client/src/sections/superAgent/utils/agentApi.js
@@ -25,6 +25,7 @@ import {
parseAgentErrorPayload,
shouldSendIdempotencyKey,
} from "./agentApiParse";
+import { superAgentLog, superAgentWarn } from "./debugLog";
const ACCESS_TOKEN_PATH = "/_api/super-agent/access-token";
const TOKEN_REFRESH_SKEW_MS = 30 * 1000;
@@ -125,7 +126,15 @@ export const issueSuperAgentAccessToken = async ({ force = false } = {}) => {
const data = await readJsonSafe(response);
if (!response.ok || !data?.token) {
clearSuperAgentAccessToken();
- throw parseAgentErrorPayload(data, response.status, "签发 Agent 访问令牌失败");
+ const error = parseAgentErrorPayload(data, response.status, "签发 Agent 访问令牌失败");
+ error.requestId =
+ error.requestId ||
+ response.headers.get("x-request-id") ||
+ response.headers.get("request-id") ||
+ "";
+ error.code = "AGENT_TOKEN_ISSUE_FAILED";
+ error.message = "Agent JWT 签发失败,请刷新页面重试";
+ throw error;
}
const expiresAt = data.expires_at
@@ -155,13 +164,21 @@ const parseAgentResponse = async (response, fallbackMessage, returnMeta = false)
}
const data = await readJsonSafe(response);
if (!response.ok) {
- throw parseAgentErrorPayload(data, response.status, fallbackMessage);
+ const error = parseAgentErrorPayload(data, response.status, fallbackMessage);
+ error.requestId =
+ error.requestId ||
+ response.headers.get("x-request-id") ||
+ response.headers.get("request-id") ||
+ "";
+ throw error;
}
return returnMeta
? { data, headers: response.headers, status: response.status }
: data;
};
+const getRequestPath = (path, params = {}) => buildSuperAgentPath(path, params);
+
export const requestAgentJson = async ({
method,
path,
@@ -206,7 +223,19 @@ export const requestAgentJson = async ({
}
);
+ superAgentLog("api.request", "response", {
+ endpoint: getRequestPath(path, params),
+ service,
+ method: methodName,
+ status: response.status,
+ });
+
if (response.status === 401 && retryOnUnauthorized) {
+ superAgentWarn("api.auth", "agent_token_refresh", {
+ endpoint: getRequestPath(path, params),
+ service,
+ status: response.status,
+ });
clearSuperAgentAccessToken();
return requestAgentJson({
method: methodName,
@@ -224,7 +253,13 @@ export const requestAgentJson = async ({
});
}
- return parseAgentResponse(response, fallbackMessage, returnMeta);
+ return parseAgentResponse(
+ response,
+ response.status === 401
+ ? "Agent 访问令牌已失效,换票失败,请刷新页面重试"
+ : fallbackMessage,
+ returnMeta
+ );
};
export const withAgentAuthorization = async (init = {}) => {
diff --git a/web/client/src/sections/superAgent/utils/agentApiParse.js b/web/client/src/sections/superAgent/utils/agentApiParse.js
index 4725d17..8774d84 100644
--- a/web/client/src/sections/superAgent/utils/agentApiParse.js
+++ b/web/client/src/sections/superAgent/utils/agentApiParse.js
@@ -63,6 +63,33 @@ export const parseAgentErrorPayload = (data, status, fallbackMessage) => {
});
};
+export const getSuperAgentErrorMessage = (error, fallbackMessage = "超级智能体请求失败") => {
+ const status = Number(error?.status || error?.response?.status || 0);
+ const code = String(error?.code || "").toUpperCase();
+ let message = String(error?.message || fallbackMessage);
+
+ if (code === "AGENT_TOKEN_ISSUE_FAILED") {
+ message = "Agent JWT 签发失败,请刷新页面重试";
+ } else if (code === "AUTHENTICATION_REQUIRED") {
+ message = "请先登录后再使用飞小尚超级智能体";
+ } else if (status === 401 || code === "UNAUTHORIZED") {
+ message = "Agent 访问令牌已失效,换票失败,请刷新页面重试";
+ } else if (status === 403) {
+ message = "当前账号没有执行此操作的权限";
+ } else if (status === 409) {
+ message = "请求与当前运行冲突,请稍后重试";
+ } else if (status === 412) {
+ message = "内容已被更新,请刷新后重试";
+ } else if (status === 422) {
+ message = message || "请求参数校验失败";
+ } else if (!message || /failed to fetch|networkerror/i.test(message)) {
+ message = "网络连接异常,请检查网络后重试";
+ }
+
+ const requestId = String(error?.requestId || error?.request_id || "").trim();
+ return requestId ? `${message}(request_id: ${requestId})` : message;
+};
+
export const createIdempotencyKey = () => {
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
return crypto.randomUUID();
diff --git a/web/client/src/sections/superAgent/utils/debugLog.js b/web/client/src/sections/superAgent/utils/debugLog.js
index c39ab87..ce29b73 100644
--- a/web/client/src/sections/superAgent/utils/debugLog.js
+++ b/web/client/src/sections/superAgent/utils/debugLog.js
@@ -5,7 +5,6 @@
*/
"use strict";
-const MAX_PREVIEW = 160;
const MAX_KEYS = 14;
const getWindowEnv = () => {
@@ -33,7 +32,6 @@ const summarizeString = (value) => {
return {
type: "string",
len: text.length,
- preview: text.length > MAX_PREVIEW ? `${text.slice(0, MAX_PREVIEW)}...` : text,
};
};
@@ -44,8 +42,10 @@ export const summarizeSuperAgentPayload = (value, depth = 0) => {
if (value instanceof Error) {
return {
name: value.name,
- message: value.message,
- stack: value.stack ? summarizeString(value.stack) : undefined,
+ message: summarizeString(value.message),
+ status: value.status || value.response?.status || 0,
+ code: value.code || "",
+ requestId: value.requestId || "",
};
}
if (typeof File !== "undefined" && value instanceof File) {
@@ -71,7 +71,7 @@ export const summarizeSuperAgentPayload = (value, depth = 0) => {
};
if (depth >= 2) return out;
keys.slice(0, MAX_KEYS).forEach((key) => {
- if (["content", "text", "message", "raw", "html", "markdown"].includes(key)) {
+ if (/(?:token|authorization|secret|password|content|text|message|raw|html|markdown|input|body|file)/i.test(key)) {
out[key] = summarizeString(value[key]);
} else if (["payload", "data", "checkpoint", "task", "edits", "sections", "chapters"].includes(key)) {
out[key] = summarizeSuperAgentPayload(value[key], depth + 1);
diff --git a/web/client/src/sections/superAgent/utils/session.js b/web/client/src/sections/superAgent/utils/session.js
index 1c28f7d..174dae8 100644
--- a/web/client/src/sections/superAgent/utils/session.js
+++ b/web/client/src/sections/superAgent/utils/session.js
@@ -23,10 +23,19 @@ import {
ACTIVE_SESSION_STORAGE_KEY,
DEFAULT_TASK_FOLDER_ID,
DEFAULT_TASK_FOLDER_NAME,
+ LEGACY_SUPER_AGENT_STORAGE_KEYS,
TASK_SESSION_STORAGE_KEY,
TASK_STORAGE_KEY,
} from "../constants";
+let hasClearedLegacyStorage = false;
+
+const ensureVersionedStorage = () => {
+ if (hasClearedLegacyStorage || typeof window === "undefined") return;
+ LEGACY_SUPER_AGENT_STORAGE_KEYS.forEach((key) => window.localStorage.removeItem(key));
+ hasClearedLegacyStorage = true;
+};
+
export const normalizeSessionTitle = (title, fallbackTitle) => {
const sessionTitle = String(title || "").trim();
if (sessionTitle.toLowerCase() === "new session") return "新对话";
@@ -35,6 +44,7 @@ export const normalizeSessionTitle = (title, fallbackTitle) => {
};
export const readTaskListFromStorage = () => {
+ ensureVersionedStorage();
try {
const rawValue = window.localStorage.getItem(TASK_STORAGE_KEY);
const parsedValue = JSON.parse(rawValue || "[]");
@@ -49,10 +59,12 @@ export const readTaskListFromStorage = () => {
};
export const saveTaskListToStorage = (taskList) => {
+ ensureVersionedStorage();
window.localStorage.setItem(TASK_STORAGE_KEY, JSON.stringify(taskList));
};
export const readTenderSessionIds = () => {
+ ensureVersionedStorage();
try {
const rawValue = window.localStorage.getItem(TASK_SESSION_STORAGE_KEY);
const parsedValue = JSON.parse(rawValue || "[]");
@@ -65,12 +77,14 @@ export const readTenderSessionIds = () => {
};
export const markTenderSessionId = (sessionId) => {
+ ensureVersionedStorage();
if (!sessionId) return;
const nextIds = Array.from(new Set([String(sessionId), ...readTenderSessionIds()]));
window.localStorage.setItem(TASK_SESSION_STORAGE_KEY, JSON.stringify(nextIds));
};
export const removeTenderSessionId = (sessionId) => {
+ ensureVersionedStorage();
if (!sessionId) return;
const nextIds = readTenderSessionIds().filter((item) => item !== String(sessionId));
window.localStorage.setItem(TASK_SESSION_STORAGE_KEY, JSON.stringify(nextIds));
@@ -101,6 +115,7 @@ const normalizePendingEmptySessionMap = (parsedValue = {}) => {
};
export const readPendingEmptySessionMap = () => {
+ ensureVersionedStorage();
try {
const rawValue = window.localStorage.getItem(PENDING_EMPTY_SESSION_STORAGE_KEY);
const parsedValue = JSON.parse(rawValue || "{}");
@@ -117,6 +132,7 @@ export const readPendingEmptySession = (type = "") => {
};
export const markPendingEmptySession = (sessionId, type, folderId = null) => {
+ ensureVersionedStorage();
if (!sessionId || !type) return;
const sessionMap = readPendingEmptySessionMap();
const nextSessionMap = {
@@ -134,6 +150,7 @@ export const markPendingEmptySession = (sessionId, type, folderId = null) => {
};
export const clearPendingEmptySession = (sessionId = "") => {
+ ensureVersionedStorage();
if (!sessionId) {
window.localStorage.removeItem(PENDING_EMPTY_SESSION_STORAGE_KEY);
return;
@@ -159,6 +176,7 @@ export const clearPendingEmptySession = (sessionId = "") => {
};
export const readActiveSessionId = () => {
+ ensureVersionedStorage();
try {
return String(window.localStorage.getItem(ACTIVE_SESSION_STORAGE_KEY) || "").trim();
} catch (error) {
@@ -167,11 +185,13 @@ export const readActiveSessionId = () => {
};
export const saveActiveSessionId = (sessionId) => {
+ ensureVersionedStorage();
if (!sessionId) return;
window.localStorage.setItem(ACTIVE_SESSION_STORAGE_KEY, String(sessionId));
};
export const clearActiveSessionId = (sessionId = "") => {
+ ensureVersionedStorage();
if (!sessionId) {
window.localStorage.removeItem(ACTIVE_SESSION_STORAGE_KEY);
return;
@@ -183,6 +203,7 @@ export const clearActiveSessionId = (sessionId = "") => {
};
export const readCollapsedFolderMap = () => {
+ ensureVersionedStorage();
try {
const rawValue = window.localStorage.getItem(FOLDER_COLLAPSED_STORAGE_KEY);
const parsedValue = JSON.parse(rawValue || "{}");
@@ -193,6 +214,7 @@ export const readCollapsedFolderMap = () => {
};
export const saveCollapsedFolderMap = (folderMap) => {
+ ensureVersionedStorage();
window.localStorage.setItem(FOLDER_COLLAPSED_STORAGE_KEY, JSON.stringify(folderMap || {}));
};
diff --git a/web/tests/agentApiAdapter.test.mjs b/web/tests/agentApiAdapter.test.mjs
index 5efcdfb..54677af 100644
--- a/web/tests/agentApiAdapter.test.mjs
+++ b/web/tests/agentApiAdapter.test.mjs
@@ -12,6 +12,7 @@ const {
isAgentUuid,
parseAgentErrorPayload,
parseAgentPage,
+ getSuperAgentErrorMessage,
shouldSendIdempotencyKey,
} = await import("../client/src/sections/superAgent/utils/agentApiParse.js");
const {
@@ -81,6 +82,16 @@ test("attachment upload intent follows the v2 three-stage payload", () => {
assert.equal(isUploadIntentExpired(intent, Date.parse("2031-01-01T00:00:00Z")), true);
});
+test("agent errors keep request_id without exposing credentials", () => {
+ const error = parseAgentErrorPayload(
+ { code: "UNAUTHORIZED", message: "expired", request_id: "req_auth_1" },
+ 401,
+ "fallback",
+ );
+ assert.match(getSuperAgentErrorMessage(error), /换票失败/);
+ assert.match(getSuperAgentErrorMessage(error), /req_auth_1/);
+});
+
test("adapter source exchanges login token for agent jwt and omits identity headers", () => {
const table = read("client", "src", "utils", "superAgentApiTable.js");
const agentApi = read("client", "src", "sections", "superAgent", "utils", "agentApi.js");
@@ -113,6 +124,9 @@ test("adapter source exchanges login token for agent jwt and omits identity head
assert.match(agentApi, /buildSuperAgentServiceUrl = \(path = "", service = superAgentBusinessService\)/);
assert.match(agentApi, /superAgentFrameworkProxyMatch/);
assert.match(agentApi, /getSuperAgentFrameworkApiUrl/);
+ assert.match(agentApi, /response\.status === 401 && retryOnUnauthorized/);
+ assert.match(streamHook, /fetchAuthorizedStream/);
+ assert.doesNotMatch(streamHook, /X-PEP-Token/);
assert.match(resources, /completeAttachmentUpload/);
assert.match(resources, /listAttachments/);
assert.match(resources, /getArtifactChapter/);
@@ -133,3 +147,10 @@ test("adapter source exchanges login token for agent jwt and omits identity head
assert.match(uploadUtils, /xhr\.send\(file\)/);
assert.match(resources, /cancelThreadRun/);
});
+
+test("superAgent diagnostics redact message and file payload text", () => {
+ const debugLog = read("client", "src", "sections", "superAgent", "utils", "debugLog.js");
+ assert.doesNotMatch(debugLog, /preview:/);
+ assert.match(debugLog, /token|authorization/);
+ assert.match(debugLog, /requestId/);
+});
diff --git a/web/tests/superAgentFlowContract.test.mjs b/web/tests/superAgentFlowContract.test.mjs
index 6e647db..fdeb77c 100644
--- a/web/tests/superAgentFlowContract.test.mjs
+++ b/web/tests/superAgentFlowContract.test.mjs
@@ -32,7 +32,8 @@ assert.match(streamHook, /stream\.submit\(/);
assert.match(streamHook, /stream\.respond\(/);
assert.match(streamHook, /role:\s*"user"/);
assert.doesNotMatch(streamHook, /type:\s*"human"/);
-assert.doesNotMatch(streamHook, /super\.getState\(\)/);
+ assert.match(streamHook, /const state = await super\.getState\(\)/);
+ assert.match(streamHook, /hydratedState\?\.values\?\.messages/);
assert.match(streamHook, /const stopRun =/);
assert.match(streamHook, /await stream\.stop\(\)/);
assert.match(streamHook, /disconnect:\s*stream\.disconnect/);
@@ -42,6 +43,11 @@ assert.match(streamHook, /status: 204/);
assert.match(streamHook, /currentThread\.close/);
assert.match(streamHook, /event\?\.method === "input\.requested"/);
assert.match(streamHook, /runtimeInterrupts/);
+assert.match(streamHook, /setRunPhase\("failed"\)/);
+assert.match(streamHook, /setIsBusinessRunning\(false\)/);
+assert.match(streamHook, /eventType: event\?\.method/);
+assert.match(streamHook, /requestId/);
+assert.match(container, /getSuperAgentErrorMessage/);
assert.doesNotMatch(container, /useSuperAgentChat/);
assert.doesNotMatch(container, /respondSuperAgentCheckpoint/);
assert.doesNotMatch(container, /sendSuperAgentChat/);
diff --git a/web/tests/superAgentSidebarContract.test.mjs b/web/tests/superAgentSidebarContract.test.mjs
index b9f8e43..e9595d0 100644
--- a/web/tests/superAgentSidebarContract.test.mjs
+++ b/web/tests/superAgentSidebarContract.test.mjs
@@ -10,6 +10,7 @@ const sidebarHook = read("client", "src", "sections", "superAgent", "hooks", "us
const chatHook = read("client", "src", "sections", "superAgent", "hooks", "useSuperAgentChat.js");
const actions = read("client", "src", "sections", "superAgent", "actions", "api.js");
const router = read("client", "src", "sections", "superAgent", "router.js");
+const constants = read("client", "src", "sections", "superAgent", "constants.js");
function loadSessionUtils() {
const transformed =
@@ -30,6 +31,13 @@ test("chat route remains /chat", () => {
assert.match(router, /path: "\/chat"/);
});
+test("superAgent storage keys are versioned and legacy ids are cleared", () => {
+ assert.match(constants, /superAgent_v2_active_conversation_id/);
+ assert.match(constants, /LEGACY_SUPER_AGENT_STORAGE_KEYS/);
+ assert.match(sessionSource, /ensureVersionedStorage/);
+ assert.match(sessionSource, /removeItem\(key\)/);
+});
+
test("sidebar session keeps conversation id and thread id separate", () => {
const session = normalizeSidebarSession({
id: "11111111-1111-4111-8111-111111111111",