Browse Source

merge: 合并新代码

master
Senor-Liu 6 hours ago
parent
commit
75ae782f92
  1. 30
      web/client/src/jsonRender/registry.jsx
  2. 2
      web/client/src/layout/index.jsx
  3. 147
      web/client/src/sections/superAgent/hooks/useSuperAgentStream.js

30
web/client/src/jsonRender/registry.jsx

@ -1049,18 +1049,19 @@ const { registry, handlers, executeAction } = defineRegistry(catalog, {
//
Table: ({ props, bindings }) => {
const schema = props.__inPlaceEditSchema || {};
const isInPlaceEditEnabled = Boolean(props.__inPlaceEditEnabled);
const [editableValue, setEditableValue] = useEditableProp(
props.__inPlaceEditValue,
bindings?.__inPlaceEditValue,
props.dataSource || []
props.dataSource || [],
isInPlaceEditEnabled ? bindings?.__inPlaceEditValue : undefined,
props.dataSource || []
);
const itemFields = schema.item_fields || [];
const editableFields = new Map(
itemFields.map((field) => [String(field.name), field])
);
const isInlineEditing = Boolean(
props.__inPlaceEditEnabled
&& bindings?.__inPlaceEditValue
);
const isInlineEditing = Boolean(
isInPlaceEditEnabled
&& bindings?.__inPlaceEditValue
&& Array.isArray(editableValue)
&& schema.kind === "list"
);
@ -1109,18 +1110,19 @@ const { registry, handlers, executeAction } = defineRegistry(catalog, {
Tree: ({ props, bindings }) => {
const schema = props.__inPlaceEditSchema || {};
const childrenKey = schema.children_key || "children";
const isInPlaceEditEnabled = Boolean(props.__inPlaceEditEnabled);
const [editableValue, setEditableValue] = useEditableProp(
props.__inPlaceEditValue,
bindings?.__inPlaceEditValue,
[]
props.treeData || [],
isInPlaceEditEnabled ? bindings?.__inPlaceEditValue : undefined,
props.treeData || []
);
const itemFields = schema.item_fields || [];
const titleField = itemFields.find(
(field) => field.name === schema.item_title_field
);
const isInlineEditing = Boolean(
props.__inPlaceEditEnabled
&& bindings?.__inPlaceEditValue
);
const isInlineEditing = Boolean(
isInPlaceEditEnabled
&& bindings?.__inPlaceEditValue
&& Array.isArray(editableValue)
&& schema.kind === "tree"
&& titleField

2
web/client/src/layout/index.jsx

@ -95,7 +95,7 @@ export default function index(props) {
<Routes>
{outerRoutes}
<Route path="/" element={<Layout routerNav={routerNav} />}>
<Route index element={<Navigate to="/superAgent" replace />} />
<Route index element={<Navigate to="/aichat" replace />} />
{innerRoutes}
</Route>
</Routes>

147
web/client/src/sections/superAgent/hooks/useSuperAgentStream.js

@ -73,6 +73,51 @@ const getInterruptStatus = (item = {}) => {
const isTerminalInterrupt = (item = {}) =>
TERMINAL_INTERRUPT_STATUSES.has(getInterruptStatus(item));
const getInterruptInteractionKey = (item = {}) => {
const payload = item?.value || item?.payload || item || {};
const interactionId = String(
payload.interaction_id ||
payload.interactionId ||
item.interaction_id ||
item.interactionId ||
""
).trim();
if (!interactionId) return "";
const revision = payload.revision ?? item.revision ?? "";
return `${interactionId}:${String(revision)}`;
};
const parseMessageContent = (content) => {
if (content && typeof content === "object") return content;
if (typeof content !== "string") return null;
try {
return JSON.parse(content);
} catch (_) {
return null;
}
};
const collectResolvedInterruptInteractions = (messages = []) => {
const resolvedInteractions = new Set();
(Array.isArray(messages) ? messages : []).forEach((message) => {
if (String(message?.type || "").toLowerCase() !== "tool") return;
const result = parseMessageContent(message.content);
const decisionType = String(result?.type || "").toLowerCase();
const interactionId = String(result?.interaction_id || "").trim();
const revision = result?.revision ?? "";
if (
interactionId &&
["approve", "edit", "reject"].includes(decisionType)
) {
resolvedInteractions.add(`${interactionId}:${String(revision)}`);
}
});
return resolvedInteractions;
};
const isSuperAgentEventRequest = (input) => {
const requestUrl = String(input?.url || input || "");
return /\/threads\/[^/]+\/stream\/events(?:\?|$)/.test(requestUrl);
@ -394,6 +439,7 @@ const useSuperAgentStream = ({
const [runtimeInterrupts, setRuntimeInterrupts] = useState([]);
const [dismissedInterruptIds, setDismissedInterruptIds] = useState(new Set());
const resolvedInterruptIdsRef = useRef(new Set());
const resolvedInterruptInteractionsRef = useRef(new Set());
const [currentInterruptId, setCurrentInterruptId] = useState("");
const [hydratedThreadId, setHydratedThreadId] = useState("");
const [transportStateRevision, setTransportStateRevision] = useState(0);
@ -580,6 +626,7 @@ const useSuperAgentStream = ({
setRuntimeInterrupts([]);
setDismissedInterruptIds(new Set());
resolvedInterruptIdsRef.current.clear();
resolvedInterruptInteractionsRef.current.clear();
setCurrentInterruptId("");
setHydratedThreadId("");
}, [activeSessionId]);
@ -671,7 +718,12 @@ const useSuperAgentStream = ({
if (event?.method === "input.requested") {
const data = event.params?.data || {};
const interruptId = String(data.interrupt_id || "").trim();
if (interruptId && !resolvedInterruptIdsRef.current.has(interruptId)) {
const interactionKey = getInterruptInteractionKey(data.payload);
const isResolvedInterrupt =
resolvedInterruptIdsRef.current.has(interruptId) ||
(interactionKey &&
resolvedInterruptInteractionsRef.current.has(interactionKey));
if (interruptId && !isResolvedInterrupt) {
localRunActiveRef.current = false;
setIsLocalRunActive(false);
setCurrentInterruptId(interruptId);
@ -763,6 +815,7 @@ const useSuperAgentStream = ({
setIsBusinessRunning(false);
setRuntimeInterrupts([]);
resolvedInterruptIdsRef.current.clear();
resolvedInterruptInteractionsRef.current.clear();
// 会话切换只断开当前浏览器订阅,不能发送 stop/cancel 命令。
// stopRun 只由用户点击停止按钮触发,否则后端会把原会话改为 suspended。
@ -862,6 +915,12 @@ const useSuperAgentStream = ({
}).then((result) => {
if (interruptId) {
resolvedInterruptIdsRef.current.add(interruptId);
const interactionKey = getInterruptInteractionKey(
response?.value || response?.payload || response
);
if (interactionKey) {
resolvedInterruptInteractionsRef.current.add(interactionKey);
}
setRuntimeInterrupts((currentInterrupts) =>
currentInterrupts.filter(
(item) => String(item?.id || "") !== interruptId
@ -947,12 +1006,25 @@ const useSuperAgentStream = ({
hydratedStateThreadId === currentSessionId
? eventTransport.latestState
: null;
const rawStreamMessages = isStreamProjectionReady ? stream.messages : [];
const rawStreamValues = isStreamProjectionReady ? stream.values || {} : {};
const hydratedMessages = Array.isArray(hydratedState?.values?.messages)
? hydratedState.values.messages
: [];
// 只有 hydrate 真正拿到消息后,才能解除首次提交的 thread 投影保护。
const rawStreamMessages = isStreamProjectionReady ? stream.messages : [];
const rawStreamValues = isStreamProjectionReady ? stream.values || {} : {};
const hydratedMessages = Array.isArray(hydratedState?.values?.messages)
? hydratedState.values.messages
: [];
const historicalResolvedInteractionKeys = useMemo(
() =>
collectResolvedInterruptInteractions([
...hydratedMessages,
...rawStreamMessages,
]),
[hydratedMessages, rawStreamMessages]
);
useEffect(() => {
historicalResolvedInteractionKeys.forEach((interactionKey) => {
resolvedInterruptInteractionsRef.current.add(interactionKey);
});
}, [historicalResolvedInteractionKeys]);
// 只有 hydrate 真正拿到消息后,才能解除首次提交的 thread 投影保护。
// 仅有 task 快照不代表消息已完成落库,否则 task 先到时会让当前消息再次被隐藏。
const hasHydratedSessionContent = hydratedMessages.length > 0;
useEffect(() => {
@ -1045,9 +1117,9 @@ const useSuperAgentStream = ({
const isPersistedTaskActive = ACTIVE_TASK_STATUSES.has(persistedTaskStatus);
const hasPersistedNextNodes =
Array.isArray(hydratedState?.next) && hydratedState.next.length > 0;
const hydratedInterrupts = [
...(Array.isArray(hydratedState?.interrupts)
? hydratedState.interrupts
const hydratedInterrupts = [
...(Array.isArray(hydratedState?.interrupts)
? hydratedState.interrupts
: []),
...(Array.isArray(hydratedState?.values?.__interrupt__)
? hydratedState.values.__interrupt__.map((item) => ({
@ -1056,15 +1128,23 @@ const useSuperAgentStream = ({
value: item?.value ?? item?.payload,
namespace: Array.isArray(item?.namespace) ? item.namespace : [],
}))
: []),
];
const hasPersistedInterrupts = hydratedInterrupts.length > 0;
const persistedInterrupts =
hydratedState
? hydratedInterrupts
: isStreamProjectionReady
? stream.interrupts
: [];
: []),
];
const pendingHydratedInterrupts = hydratedInterrupts.filter((item) => {
const interruptId = String(item?.id || item?.interruptId || "").trim();
const interactionKey = getInterruptInteractionKey(item);
return (
!resolvedInterruptIdsRef.current.has(interruptId) &&
!historicalResolvedInteractionKeys.has(interactionKey)
);
});
const hasPersistedInterrupts = pendingHydratedInterrupts.length > 0;
const persistedInterrupts =
hydratedState
? pendingHydratedInterrupts
: isStreamProjectionReady
? stream.interrupts
: [];
const streamInterrupts = [
...(Array.isArray(persistedInterrupts) ? persistedInterrupts : []),
...runtimeInterrupts,
@ -1130,16 +1210,27 @@ const useSuperAgentStream = ({
});
};
streamInterrupts.forEach((item) => appendInterrupt(item));
return [...merged.values()].filter(
(item) =>
!dismissedInterruptIds.has(String(item.id || "")) &&
!isTerminalInterrupt(item)
);
streamInterrupts.forEach((item) => appendInterrupt(item));
return [...merged.values()].filter(
(item) => {
const interruptId = String(item.id || "");
const interactionKey = getInterruptInteractionKey(item);
const isHistoricalResolved =
historicalResolvedInteractionKeys.has(interactionKey) ||
resolvedInterruptInteractionsRef.current.has(interactionKey);
return (
!dismissedInterruptIds.has(interruptId) &&
!resolvedInterruptIdsRef.current.has(interruptId) &&
!isHistoricalResolved &&
!isTerminalInterrupt(item)
);
}
);
}, [
dismissedInterruptIds,
isInterruptProjectionReady,
isStreamProjectionReady,
historicalResolvedInteractionKeys,
streamInterrupts,
]);
@ -1168,9 +1259,9 @@ const useSuperAgentStream = ({
isLocalRunActive: effectiveIsLocalRunActive,
runPhase: effectiveRunPhase,
isPersistedThreadActive,
currentInterruptId:
hydratedState &&
hydratedInterrupts.length === 0 &&
currentInterruptId:
hydratedState &&
pendingHydratedInterrupts.length === 0 &&
runtimeInterrupts.length === 0
? ""
: currentInterruptId,

Loading…
Cancel
Save