- 闭环修复:审批 task_id 显式化(N2)、通知回退路径门闸 finally 释放(N1)、 personalization 原子写(S10,公共 atomic_write_json) - server.tasks 拆解:__init__ 轻量化、Blueprint/路由移至 blueprint.py/web.py, app_legacy 装配点惰性挂载;models.py 循环依赖死导入移除 - 执行链 Web 解耦:emit_event/run_background 安全包装(socketio 未绑定静默), 清除 4 处裸 socketio.emit(含 task_stopped 先写事件流再推送的顺序修复) - RuntimeService 补建对话下沉:chat 任务无 conversation_id 时服务层装配, 客户端无需复制 Web 两步流程 - 事件协议:get_task_events 返回 meta.window_start 缺口检测水位 - 验收:test_runtime_standalone_lifecycle 子进程隔离(config import 固化问题), 独立启动 + 协议全链路 + 审批语义全绿;全量 74 测试失败恰为 4 项存量
48 lines
1.8 KiB
Python
48 lines
1.8 KiB
Python
"""终端事件广播与回调包装(用户房间广播 + conversation_id 注入)。"""
|
||
from __future__ import annotations
|
||
|
||
from typing import Optional
|
||
|
||
from core.web_terminal import WebTerminal
|
||
from server.utils_common import debug_log
|
||
|
||
|
||
def make_terminal_callback(username: str):
|
||
"""生成面向指定用户的广播函数(独立 Gateway 进程下 socket 未初始化时静默跳过)"""
|
||
from server.extensions import emit_event
|
||
def _callback(event_type, data):
|
||
emit_event(event_type, data, room=f"user_{username}")
|
||
return _callback
|
||
|
||
|
||
def attach_user_broadcast(terminal: WebTerminal, username: str):
|
||
"""确保终端的广播函数指向当前用户的房间。
|
||
|
||
对话级 terminal 的回调会额外包装注入 conversation_id(见 _wrap_callback_with_conversation_id)。
|
||
"""
|
||
callback = make_terminal_callback(username)
|
||
callback = _wrap_callback_with_conversation_id(
|
||
callback, getattr(terminal, "_bound_conversation_id", None)
|
||
)
|
||
terminal.message_callback = callback
|
||
if terminal.terminal_manager:
|
||
terminal.terminal_manager.broadcast = callback
|
||
|
||
|
||
def _wrap_callback_with_conversation_id(callback, conversation_id: Optional[str]):
|
||
"""包装广播回调,为 dict 类型的事件数据注入 conversation_id(setdefault,不覆盖已有值)。
|
||
|
||
对话级 terminal 的广播(shell 输出、terminal 列表等)仍发到用户房间,
|
||
前端按 conversation_id 过滤,避免同工作区多个对话的终端事件互相串扰。
|
||
"""
|
||
if not callback or not conversation_id:
|
||
return callback
|
||
|
||
def _wrapped(event_type, data):
|
||
if isinstance(data, dict):
|
||
data = dict(data)
|
||
data.setdefault("conversation_id", conversation_id)
|
||
return callback(event_type, data)
|
||
|
||
return _wrapped
|