mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-05-17 21:44:43 +02:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 96564d4d89 | |||
| d85afa2d39 | |||
| 55b6bceb21 | |||
| 65d73b3d66 | |||
| 913115d1fb | |||
| e1b967d781 | |||
| 9d9efa886f | |||
| cae45e9dc5 |
@@ -1,5 +1,5 @@
|
||||
<div align="center">
|
||||
<img src="web/static/logo.png" alt="CyberStrikeAI Logo" width="200">
|
||||
<img src="images/logo.png" alt="CyberStrikeAI Logo" width="200">
|
||||
</div>
|
||||
|
||||
# CyberStrikeAI
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
<div align="center">
|
||||
<img src="web/static/logo.png" alt="CyberStrikeAI Logo" width="200">
|
||||
<img src="images/logo.png" alt="CyberStrikeAI Logo" width="200">
|
||||
</div>
|
||||
|
||||
# CyberStrikeAI
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
# ============================================
|
||||
|
||||
# 前端显示的版本号(可选,不填则显示默认版本)
|
||||
version: "v1.5.7"
|
||||
version: "v1.5.8"
|
||||
# 服务器配置
|
||||
server:
|
||||
host: 0.0.0.0 # 监听地址,0.0.0.0 表示监听所有网络接口
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.0 MiB |
@@ -91,6 +91,20 @@ func DefaultSingleAgentSystemPrompt() string {
|
||||
|
||||
当工具返回错误时,错误信息会包含在工具响应中,请仔细阅读并做出合理的决策。
|
||||
|
||||
## 结束条件与停止约束
|
||||
|
||||
- 在「未完成用户目标」前,不得输出纯计划/纯建议式结论并结束本轮;必须继续给出可执行下一步,并优先通过工具验证。
|
||||
- 若你准备结束回答,先执行一次自检:
|
||||
1) 是否已有可验证证据支撑“任务完成/无法继续”的结论;
|
||||
2) 是否至少尝试过当前路径的合理替代(参数、路径、方法、入口);
|
||||
3) 是否仍存在可执行且低成本的下一步验证动作。
|
||||
- 仅当满足以下任一条件时,才允许输出最终收尾:
|
||||
1) 已达到用户目标并给出证据;
|
||||
2) 达到明确边界(超时、权限、目标不可达、工具不可用且无替代),并清楚说明阻断点与已尝试项;
|
||||
3) 用户明确要求停止。
|
||||
- 若最近一步得到 404/空结果/无效响应,不得直接结束;至少再进行一次“同目标不同策略”的验证(如变更路径、参数、请求方法、上下文来源)。
|
||||
- 避免无效空转:同一工具+同类参数连续失败 3 次后,必须切换策略(改工具、改入口、改假设)并说明切换原因。
|
||||
|
||||
## 漏洞记录
|
||||
|
||||
发现有效漏洞时,必须使用 ` + builtin.ToolRecordVulnerability + ` 记录:标题、描述、严重程度、类型、目标、证明(POC)、影响、修复建议。
|
||||
|
||||
@@ -57,19 +57,30 @@ func newEinoSummarizationMiddleware(
|
||||
if modelName == "" {
|
||||
modelName = "gpt-4o"
|
||||
}
|
||||
tokenCounter := einoSummarizationTokenCounter(modelName)
|
||||
recentTrailMax := trigger / 4
|
||||
if recentTrailMax < 2048 {
|
||||
recentTrailMax = 2048
|
||||
}
|
||||
if recentTrailMax > trigger/2 {
|
||||
recentTrailMax = trigger / 2
|
||||
}
|
||||
|
||||
mw, err := summarization.New(ctx, &summarization.Config{
|
||||
Model: summaryModel,
|
||||
Trigger: &summarization.TriggerCondition{
|
||||
ContextTokens: trigger,
|
||||
},
|
||||
TokenCounter: einoSummarizationTokenCounter(modelName),
|
||||
TokenCounter: tokenCounter,
|
||||
UserInstruction: einoSummarizeUserInstruction,
|
||||
EmitInternalEvents: false,
|
||||
PreserveUserMessages: &summarization.PreserveUserMessages{
|
||||
Enabled: true,
|
||||
MaxTokens: preserveMax,
|
||||
},
|
||||
Finalize: func(ctx context.Context, originalMessages []adk.Message, summary adk.Message) ([]adk.Message, error) {
|
||||
return summarizeFinalizeWithRecentAssistantToolTrail(ctx, originalMessages, summary, tokenCounter, recentTrailMax)
|
||||
},
|
||||
Callback: func(ctx context.Context, before, after adk.ChatModelAgentState) error {
|
||||
if logger == nil {
|
||||
return nil
|
||||
@@ -89,6 +100,108 @@ func newEinoSummarizationMiddleware(
|
||||
return mw, nil
|
||||
}
|
||||
|
||||
// summarizeFinalizeWithRecentAssistantToolTrail 在摘要消息后保留最近 assistant/tool 轨迹,避免压缩后执行链断裂。
|
||||
func summarizeFinalizeWithRecentAssistantToolTrail(
|
||||
ctx context.Context,
|
||||
originalMessages []adk.Message,
|
||||
summary adk.Message,
|
||||
tokenCounter summarization.TokenCounterFunc,
|
||||
recentTrailTokenBudget int,
|
||||
) ([]adk.Message, error) {
|
||||
systemMsgs := make([]adk.Message, 0, len(originalMessages))
|
||||
nonSystem := make([]adk.Message, 0, len(originalMessages))
|
||||
for _, msg := range originalMessages {
|
||||
if msg == nil {
|
||||
continue
|
||||
}
|
||||
if msg.Role == schema.System {
|
||||
systemMsgs = append(systemMsgs, msg)
|
||||
continue
|
||||
}
|
||||
nonSystem = append(nonSystem, msg)
|
||||
}
|
||||
|
||||
if recentTrailTokenBudget <= 0 || len(nonSystem) == 0 {
|
||||
out := make([]adk.Message, 0, len(systemMsgs)+1)
|
||||
out = append(out, systemMsgs...)
|
||||
out = append(out, summary)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
selectedReverse := make([]adk.Message, 0, 8)
|
||||
seen := make(map[adk.Message]struct{})
|
||||
totalTokens := 0
|
||||
assistantToolKept := 0
|
||||
const minAssistantToolTrail = 4
|
||||
|
||||
tryKeep := func(msg adk.Message) (bool, error) {
|
||||
if msg == nil {
|
||||
return false, nil
|
||||
}
|
||||
if _, ok := seen[msg]; ok {
|
||||
return false, nil
|
||||
}
|
||||
n, err := tokenCounter(ctx, &summarization.TokenCounterInput{Messages: []adk.Message{msg}})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if n <= 0 {
|
||||
n = 1
|
||||
}
|
||||
if totalTokens+n > recentTrailTokenBudget {
|
||||
return false, nil
|
||||
}
|
||||
totalTokens += n
|
||||
selectedReverse = append(selectedReverse, msg)
|
||||
seen[msg] = struct{}{}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// 优先保留最近 assistant/tool,确保执行轨迹可续跑。
|
||||
for i := len(nonSystem) - 1; i >= 0; i-- {
|
||||
msg := nonSystem[i]
|
||||
if msg.Role != schema.Assistant && msg.Role != schema.Tool {
|
||||
continue
|
||||
}
|
||||
ok, err := tryKeep(msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ok {
|
||||
assistantToolKept++
|
||||
}
|
||||
if assistantToolKept >= minAssistantToolTrail {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 在预算内回填更多最近消息,保持短链路上下文。
|
||||
for i := len(nonSystem) - 1; i >= 0; i-- {
|
||||
_, exists := seen[nonSystem[i]]
|
||||
if exists {
|
||||
continue
|
||||
}
|
||||
ok, err := tryKeep(nonSystem[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
selected := make([]adk.Message, 0, len(selectedReverse))
|
||||
for i := len(selectedReverse) - 1; i >= 0; i-- {
|
||||
selected = append(selected, selectedReverse[i])
|
||||
}
|
||||
|
||||
out := make([]adk.Message, 0, len(systemMsgs)+1+len(selected))
|
||||
out = append(out, systemMsgs...)
|
||||
out = append(out, summary)
|
||||
out = append(out, selected...)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func einoSummarizationTokenCounter(openAIModel string) summarization.TokenCounterFunc {
|
||||
tc := agent.NewTikTokenCounter()
|
||||
return func(ctx context.Context, input *summarization.TokenCounterInput) (int, error) {
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 85 KiB |
+62
-37
@@ -765,50 +765,59 @@ async function sendMessage() {
|
||||
if (!response.ok) {
|
||||
throw new Error('请求失败: ' + response.status);
|
||||
}
|
||||
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
let buffer = '';
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
const lines = buffer.split('\n');
|
||||
buffer = lines.pop(); // 保留最后一个不完整的行
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data: ')) {
|
||||
try {
|
||||
const eventData = JSON.parse(line.slice(6));
|
||||
handleStreamEvent(eventData, progressElement, progressId,
|
||||
() => assistantMessageId, (id) => { assistantMessageId = id; },
|
||||
() => mcpExecutionIds, (ids) => { mcpExecutionIds = ids; });
|
||||
} catch (e) {
|
||||
console.error('解析事件数据失败:', e, line);
|
||||
|
||||
window.__csAgentLiveStream = {
|
||||
active: true,
|
||||
conversationId: currentConversationId || null,
|
||||
progressId: progressId
|
||||
};
|
||||
try {
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
let buffer = '';
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
const lines = buffer.split('\n');
|
||||
buffer = lines.pop(); // 保留最后一个不完整的行
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data: ')) {
|
||||
try {
|
||||
const eventData = JSON.parse(line.slice(6));
|
||||
handleStreamEvent(eventData, progressElement, progressId,
|
||||
() => assistantMessageId, (id) => { assistantMessageId = id; },
|
||||
() => mcpExecutionIds, (ids) => { mcpExecutionIds = ids; });
|
||||
} catch (e) {
|
||||
console.error('解析事件数据失败:', e, line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理剩余的buffer
|
||||
if (buffer.trim()) {
|
||||
const lines = buffer.split('\n');
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data: ')) {
|
||||
try {
|
||||
const eventData = JSON.parse(line.slice(6));
|
||||
handleStreamEvent(eventData, progressElement, progressId,
|
||||
() => assistantMessageId, (id) => { assistantMessageId = id; },
|
||||
() => mcpExecutionIds, (ids) => { mcpExecutionIds = ids; });
|
||||
} catch (e) {
|
||||
console.error('解析事件数据失败:', e, line);
|
||||
|
||||
// 处理剩余的buffer
|
||||
if (buffer.trim()) {
|
||||
const lines = buffer.split('\n');
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data: ')) {
|
||||
try {
|
||||
const eventData = JSON.parse(line.slice(6));
|
||||
handleStreamEvent(eventData, progressElement, progressId,
|
||||
() => assistantMessageId, (id) => { assistantMessageId = id; },
|
||||
() => mcpExecutionIds, (ids) => { mcpExecutionIds = ids; });
|
||||
} catch (e) {
|
||||
console.error('解析事件数据失败:', e, line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
window.__csAgentLiveStream = { active: false, conversationId: null, progressId: null };
|
||||
}
|
||||
|
||||
|
||||
// 消息发送成功后,再次确保草稿被清除
|
||||
clearChatDraft();
|
||||
try {
|
||||
@@ -2922,6 +2931,22 @@ async function loadConversation(conversationId) {
|
||||
await window.restoreHitlInlineForConversation(conversationId);
|
||||
}
|
||||
}
|
||||
|
||||
// 页面刷新后主流式连接会中断;若该会话仍在后端运行,自动挂载 task-events 补流继续更新前端迭代进度。
|
||||
const skipReplay = typeof window.shouldSkipTaskEventReplayAttach === 'function'
|
||||
&& window.shouldSkipTaskEventReplayAttach(conversationId);
|
||||
if (
|
||||
seq === loadConversationRequestSeq &&
|
||||
currentConversationId === conversationId &&
|
||||
typeof window.attachRunningTaskEventStream === 'function' &&
|
||||
!skipReplay
|
||||
) {
|
||||
Promise.resolve()
|
||||
.then(() => window.attachRunningTaskEventStream(conversationId))
|
||||
.catch((e) => {
|
||||
console.warn('attachRunningTaskEventStream on loadConversation failed', e);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载对话失败:', error);
|
||||
alert('加载对话失败: ' + error.message);
|
||||
|
||||
+136
-72
@@ -3,6 +3,36 @@ let activeTaskInterval = null;
|
||||
const ACTIVE_TASK_REFRESH_INTERVAL = 10000; // 10秒检查一次
|
||||
const TASK_FINAL_STATUSES = new Set(['failed', 'timeout', 'cancelled', 'completed']);
|
||||
|
||||
/**
|
||||
* 主对话 POST 流仍在读取时,禁止再挂 task-events 补流,否则同一事件会画两遍(与 HITL 是否开启无关)。
|
||||
* window.__csAgentLiveStream 由 chat.js sendMessage 在读到 body 后设置,在 finally 中清除。
|
||||
*/
|
||||
function syncAgentLiveStreamConversationId(cid) {
|
||||
if (!cid) return;
|
||||
try {
|
||||
const live = window.__csAgentLiveStream;
|
||||
if (live && live.active) {
|
||||
live.conversationId = cid;
|
||||
}
|
||||
} catch (e) { /* ignore */ }
|
||||
}
|
||||
|
||||
function shouldSkipTaskEventReplayAttach(conversationId) {
|
||||
try {
|
||||
const live = window.__csAgentLiveStream;
|
||||
if (!live || !live.active || !live.progressId) return false;
|
||||
if (!document.getElementById(live.progressId)) return false;
|
||||
// 新会话:conversation 事件尚未到达前 conversationId 可能仍为 null,一律不补挂
|
||||
if (live.conversationId == null) return true;
|
||||
return live.conversationId === conversationId;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (typeof window !== 'undefined') {
|
||||
window.shouldSkipTaskEventReplayAttach = shouldSkipTaskEventReplayAttach;
|
||||
}
|
||||
|
||||
// 当前界面语言对应的 BCP 47 标签(与时间格式化一致)
|
||||
function getCurrentTimeLocale() {
|
||||
if (typeof window.__locale === 'string' && window.__locale.length) {
|
||||
@@ -934,6 +964,7 @@ function handleStreamEvent(event, progressElement, progressId,
|
||||
|
||||
// 更新当前对话ID
|
||||
currentConversationId = event.data.conversationId;
|
||||
syncAgentLiveStreamConversationId(event.data.conversationId);
|
||||
updateActiveConversation();
|
||||
addAttackChainButton(currentConversationId);
|
||||
loadActiveTasks();
|
||||
@@ -1472,6 +1503,7 @@ function handleStreamEvent(event, progressElement, progressId,
|
||||
break;
|
||||
}
|
||||
currentConversationId = responseData.conversationId;
|
||||
syncAgentLiveStreamConversationId(responseData.conversationId);
|
||||
updateActiveConversation();
|
||||
addAttackChainButton(currentConversationId);
|
||||
updateProgressConversation(progressId, responseData.conversationId);
|
||||
@@ -1552,6 +1584,7 @@ function handleStreamEvent(event, progressElement, progressId,
|
||||
}
|
||||
|
||||
currentConversationId = responseData.conversationId;
|
||||
syncAgentLiveStreamConversationId(responseData.conversationId);
|
||||
updateActiveConversation();
|
||||
addAttackChainButton(currentConversationId);
|
||||
updateProgressConversation(progressId, responseData.conversationId);
|
||||
@@ -1682,6 +1715,7 @@ function handleStreamEvent(event, progressElement, progressId,
|
||||
// 更新对话ID
|
||||
if (event.data && event.data.conversationId) {
|
||||
currentConversationId = event.data.conversationId;
|
||||
syncAgentLiveStreamConversationId(event.data.conversationId);
|
||||
updateActiveConversation();
|
||||
addAttackChainButton(currentConversationId);
|
||||
updateProgressConversation(progressId, event.data.conversationId);
|
||||
@@ -1982,90 +2016,120 @@ async function refreshLastAssistantProcessDetails(conversationId) {
|
||||
|
||||
window.refreshLastAssistantProcessDetails = refreshLastAssistantProcessDetails;
|
||||
|
||||
const taskEventReplayAttachState = {
|
||||
conversationId: null,
|
||||
inFlightPromise: null
|
||||
};
|
||||
|
||||
/**
|
||||
* 订阅运行中任务的 SSE 镜像(GET /api/agent-loop/task-events),用于 HITL 通过后主连接已断开时接续 UI。
|
||||
*/
|
||||
async function attachRunningTaskEventStream(conversationId) {
|
||||
if (!conversationId || typeof apiFetch !== 'function') return false;
|
||||
try {
|
||||
const check = await apiFetch('/api/agent-loop/tasks');
|
||||
if (!check.ok) return false;
|
||||
const j = await check.json().catch(function () { return {}; });
|
||||
const active = (j.tasks || []).some(function (t) {
|
||||
return t && t.conversationId === conversationId && (t.status === 'running' || t.status === 'cancelling');
|
||||
});
|
||||
if (!active) return false;
|
||||
if (
|
||||
taskEventReplayAttachState.inFlightPromise &&
|
||||
taskEventReplayAttachState.conversationId === conversationId
|
||||
) {
|
||||
return taskEventReplayAttachState.inFlightPromise;
|
||||
}
|
||||
if (shouldSkipTaskEventReplayAttach(conversationId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const asEl = findLastAssistantMessageElInChat();
|
||||
if (!asEl || !asEl.id) return false;
|
||||
const backendId = asEl.dataset && asEl.dataset.backendMessageId;
|
||||
if (backendId && typeof renderProcessDetails === 'function') {
|
||||
const res = await apiFetch('/api/messages/' + encodeURIComponent(String(backendId)) + '/process-details');
|
||||
const jd = await res.json().catch(function () { return {}; });
|
||||
if (res.ok && Array.isArray(jd.processDetails)) {
|
||||
renderProcessDetails(asEl.id, jd.processDetails);
|
||||
}
|
||||
}
|
||||
expandProcessDetailsTimeline(asEl.id);
|
||||
const attachPromise = (async function () {
|
||||
try {
|
||||
const check = await apiFetch('/api/agent-loop/tasks');
|
||||
if (!check.ok) return false;
|
||||
const j = await check.json().catch(function () { return {}; });
|
||||
const active = (j.tasks || []).some(function (t) {
|
||||
return t && t.conversationId === conversationId && (t.status === 'running' || t.status === 'cancelling');
|
||||
});
|
||||
if (!active) return false;
|
||||
|
||||
const progressId = taskReplayProgressId(conversationId);
|
||||
beginCsTaskReplay(progressId, asEl.id, conversationId);
|
||||
|
||||
const url = '/api/agent-loop/task-events?conversationId=' + encodeURIComponent(conversationId);
|
||||
const response = await apiFetch(url, {
|
||||
method: 'GET',
|
||||
headers: { Accept: 'text/event-stream' }
|
||||
});
|
||||
if (!response.ok) {
|
||||
clearCsTaskReplay();
|
||||
if (progressTaskState.has(progressId)) {
|
||||
progressTaskState.delete(progressId);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
let mcpIds = [];
|
||||
const assistantDomId = asEl.id;
|
||||
const getAssistantIdFn = function () { return assistantDomId; };
|
||||
const setAssistantIdFn = function () {};
|
||||
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
let buffer = '';
|
||||
while (true) {
|
||||
const chunk = await reader.read();
|
||||
if (chunk.done) break;
|
||||
buffer += decoder.decode(chunk.value, { stream: true });
|
||||
const lines = buffer.split('\n');
|
||||
buffer = lines.pop() || '';
|
||||
for (let li = 0; li < lines.length; li++) {
|
||||
const line = lines[li];
|
||||
if (line.indexOf('data: ') === 0) {
|
||||
try {
|
||||
const eventData = JSON.parse(line.slice(6));
|
||||
handleStreamEvent(eventData, null, progressId, getAssistantIdFn, setAssistantIdFn, function () { return mcpIds; }, function (ids) { mcpIds = ids; });
|
||||
} catch (e) {
|
||||
console.error('task-events parse', e);
|
||||
const asEl = findLastAssistantMessageElInChat();
|
||||
if (!asEl || !asEl.id) return false;
|
||||
const backendId = asEl.dataset && asEl.dataset.backendMessageId;
|
||||
if (backendId && typeof renderProcessDetails === 'function') {
|
||||
const res = await apiFetch('/api/messages/' + encodeURIComponent(String(backendId)) + '/process-details');
|
||||
const jd = await res.json().catch(function () { return {}; });
|
||||
if (res.ok && Array.isArray(jd.processDetails)) {
|
||||
renderProcessDetails(asEl.id, jd.processDetails);
|
||||
// renderProcessDetails 会重建时间线节点,需重新挂载 HITL 审批入口
|
||||
if (typeof window.restoreHitlInlineForConversation === 'function') {
|
||||
await window.restoreHitlInlineForConversation(conversationId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (window.csTaskReplay && window.csTaskReplay.progressId === progressId) {
|
||||
expandProcessDetailsTimeline(asEl.id);
|
||||
|
||||
const progressId = taskReplayProgressId(conversationId);
|
||||
beginCsTaskReplay(progressId, asEl.id, conversationId);
|
||||
|
||||
const url = '/api/agent-loop/task-events?conversationId=' + encodeURIComponent(conversationId);
|
||||
const response = await apiFetch(url, {
|
||||
method: 'GET',
|
||||
headers: { Accept: 'text/event-stream' }
|
||||
});
|
||||
if (!response.ok) {
|
||||
clearCsTaskReplay();
|
||||
if (progressTaskState.has(progressId)) {
|
||||
progressTaskState.delete(progressId);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
let mcpIds = [];
|
||||
const assistantDomId = asEl.id;
|
||||
const getAssistantIdFn = function () { return assistantDomId; };
|
||||
const setAssistantIdFn = function () {};
|
||||
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
let buffer = '';
|
||||
while (true) {
|
||||
const chunk = await reader.read();
|
||||
if (chunk.done) break;
|
||||
buffer += decoder.decode(chunk.value, { stream: true });
|
||||
const lines = buffer.split('\n');
|
||||
buffer = lines.pop() || '';
|
||||
for (let li = 0; li < lines.length; li++) {
|
||||
const line = lines[li];
|
||||
if (line.indexOf('data: ') === 0) {
|
||||
try {
|
||||
const eventData = JSON.parse(line.slice(6));
|
||||
handleStreamEvent(eventData, null, progressId, getAssistantIdFn, setAssistantIdFn, function () { return mcpIds; }, function (ids) { mcpIds = ids; });
|
||||
} catch (e) {
|
||||
console.error('task-events parse', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (window.csTaskReplay && window.csTaskReplay.progressId === progressId) {
|
||||
clearCsTaskReplay();
|
||||
}
|
||||
if (progressTaskState.has(progressId)) {
|
||||
finalizeProgressTask(progressId, typeof window.t === 'function' ? window.t('tasks.statusCompleted') : '已完成');
|
||||
}
|
||||
if (typeof loadActiveTasks === 'function') loadActiveTasks();
|
||||
if (typeof window.loadConversation === 'function' && window.currentConversationId === conversationId) {
|
||||
await window.loadConversation(conversationId);
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.warn('attachRunningTaskEventStream', e);
|
||||
clearCsTaskReplay();
|
||||
return false;
|
||||
} finally {
|
||||
if (taskEventReplayAttachState.inFlightPromise === attachPromise) {
|
||||
taskEventReplayAttachState.inFlightPromise = null;
|
||||
taskEventReplayAttachState.conversationId = null;
|
||||
}
|
||||
}
|
||||
if (progressTaskState.has(progressId)) {
|
||||
finalizeProgressTask(progressId, typeof window.t === 'function' ? window.t('tasks.statusCompleted') : '已完成');
|
||||
}
|
||||
if (typeof loadActiveTasks === 'function') loadActiveTasks();
|
||||
if (typeof window.loadConversation === 'function' && window.currentConversationId === conversationId) {
|
||||
await window.loadConversation(conversationId);
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.warn('attachRunningTaskEventStream', e);
|
||||
clearCsTaskReplay();
|
||||
return false;
|
||||
}
|
||||
})();
|
||||
|
||||
taskEventReplayAttachState.conversationId = conversationId;
|
||||
taskEventReplayAttachState.inFlightPromise = attachPromise;
|
||||
return attachPromise;
|
||||
}
|
||||
|
||||
window.attachRunningTaskEventStream = attachRunningTaskEventStream;
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 442 KiB After Width: | Height: | Size: 85 KiB |
Reference in New Issue
Block a user