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

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

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

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

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

Loading…
Cancel
Save