agent-Specialization/server/context/broadcast.py
JOJO d9bd599c1c feat(runtime): Gateway 独立启动与公共协议落地(改造第 0-3 步)
- 闭环修复:审批 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 项存量
2026-09-07 23:00:15 +08:00

48 lines
1.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""终端事件广播与回调包装(用户房间广播 + 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_idsetdefault不覆盖已有值
对话级 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