feat: 命令台子代理链路(主任务/模型子代理/委派子任务 + 过程调用实时同步)
host:链路条目带 thread 归属;delegate 向父链路文件写 agent-start/done 标记。服务端:GET /tasks/{id}/agents 聚合父链路 + 子链路文件成代理链(状态口径:委派以结束标记为准、无标记即进行中;模型子代理无结束信号时父任务运行中为进行中、否则未知)。命令台:新增子代理链路区,随 SSE 流防抖刷新;扁平链路也渲染 agent 标记。
This commit is contained in:
1 parent
34cd0eeba7
commit
c4d3ec9bff
6 files changed
+366
-14
No files matched your search
@@ -214,6 +214,8 @@ def _summarize_trace_item(method, params):
|
||||
返回 [] 表示该通知不进链路(userMessage 等)。
|
||||
ts 取通知的 completedAtMs/startedAtMs(毫秒 epoch),
|
||||
没有时为 None,服务端展示时按无时间处理。
|
||||
thread 取通知的 threadId(模型内子代理与主任务同文件时,
|
||||
服务端凭它把条目归属到不同代理;没有时为 None,归主任务)。
|
||||
"""
|
||||
item = (params or {}).get("item")
|
||||
if not isinstance(item, dict):
|
||||
@@ -221,8 +223,11 @@ def _summarize_trace_item(method, params):
|
||||
item_type = item.get("type")
|
||||
item_id = item.get("id") or ""
|
||||
ts = params.get("completedAtMs") or params.get("startedAtMs")
|
||||
thread = params.get("threadId")
|
||||
if not thread and isinstance(params.get("thread"), dict):
|
||||
thread = params["thread"].get("id")
|
||||
if item_type == "commandExecution":
|
||||
return [{"kind": "exec", "id": item_id, "ts": ts,
|
||||
return [{"kind": "exec", "id": item_id, "ts": ts, "thread": thread,
|
||||
"command": (item.get("command") or "")[:500],
|
||||
"cwd": item.get("cwd") or "",
|
||||
"exitCode": item.get("exitCode"),
|
||||
@@ -235,6 +240,7 @@ def _summarize_trace_item(method, params):
|
||||
path = ch.get("path") or ""
|
||||
entries.append({"kind": "file",
|
||||
"id": item_id + ":" + path, "ts": ts,
|
||||
"thread": thread,
|
||||
"path": path, "change": ch.get("kind") or "",
|
||||
"status": item.get("status") or ""})
|
||||
return entries
|
||||
@@ -242,32 +248,60 @@ def _summarize_trace_item(method, params):
|
||||
text = item.get("text") or ""
|
||||
if not text:
|
||||
return []
|
||||
return [{"kind": "message", "id": item_id, "ts": ts,
|
||||
return [{"kind": "message", "id": item_id, "ts": ts, "thread": thread,
|
||||
"phase": item.get("phase") or "", "text": text[:300]}]
|
||||
if item_type == "reasoning":
|
||||
summary = item.get("summary") or item.get("content") or []
|
||||
text = " ".join(s for s in summary if isinstance(s, str)).strip()
|
||||
if not text:
|
||||
return []
|
||||
return [{"kind": "reasoning", "id": item_id, "ts": ts,
|
||||
return [{"kind": "reasoning", "id": item_id, "ts": ts, "thread": thread,
|
||||
"summary": text[:200]}]
|
||||
if item_type == "mcpToolCall":
|
||||
return [{"kind": "tool", "id": item_id, "ts": ts,
|
||||
return [{"kind": "tool", "id": item_id, "ts": ts, "thread": thread,
|
||||
"server": item.get("server") or "",
|
||||
"tool": item.get("tool") or "",
|
||||
"status": item.get("status") or ""}]
|
||||
if item_type == "functionCallOutput":
|
||||
return [{"kind": "call", "id": item_id, "ts": ts,
|
||||
return [{"kind": "call", "id": item_id, "ts": ts, "thread": thread,
|
||||
"name": item.get("name") or ""}]
|
||||
if item_type == "plan":
|
||||
text = item.get("text") or ""
|
||||
if not text:
|
||||
return []
|
||||
return [{"kind": "plan", "id": item_id, "ts": ts,
|
||||
return [{"kind": "plan", "id": item_id, "ts": ts, "thread": thread,
|
||||
"text": text[:300]}]
|
||||
return []
|
||||
|
||||
|
||||
def _trace_file_path(state_path, task_name):
|
||||
"""链路文件路径:state 文件同目录下的 host-traces/<清洗后名字>.jsonl。
|
||||
|
||||
与 Server.set_trace_file、cloud 的 _trace_file_name 同款清洗,
|
||||
三边算出同一名字,delegate 的父链路标记才能落进父任务文件。
|
||||
"""
|
||||
safe = re.sub(r"[^A-Za-z0-9_.-]", "-", str(task_name))
|
||||
return os.path.join(
|
||||
os.path.dirname(os.path.abspath(state_path)),
|
||||
"host-traces", safe + ".jsonl")
|
||||
|
||||
|
||||
def _append_trace_marker(state_path, task_name, entry):
|
||||
"""往另一任务的链路文件追加一条标记(delegate 向父链路报到用)。
|
||||
|
||||
尽力而为:目录建不出、写失败都不影响回合本身。父链路的
|
||||
行数上限由属主进程自己的计数器管,这里不重复计数(标记
|
||||
一轮至多两条,超限风险可忽略)。
|
||||
"""
|
||||
try:
|
||||
path = _trace_file_path(state_path, task_name)
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
with open(path, "a", encoding="utf-8") as f:
|
||||
f.write(json.dumps(entry, ensure_ascii=False) + "\n")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
class Server:
|
||||
"""单个 app-server 进程,多 thread 并发。"""
|
||||
|
||||
@@ -302,12 +336,8 @@ class Server:
|
||||
时 trace_path 保持 None,不影响回合。
|
||||
"""
|
||||
try:
|
||||
safe = re.sub(r"[^A-Za-z0-9_.-]", "-", str(task_name))
|
||||
trace_dir = os.path.join(
|
||||
os.path.dirname(os.path.abspath(state_path)),
|
||||
"host-traces")
|
||||
os.makedirs(trace_dir, exist_ok=True)
|
||||
self.trace_path = os.path.join(trace_dir, safe + ".jsonl")
|
||||
self.trace_path = _trace_file_path(state_path, task_name)
|
||||
os.makedirs(os.path.dirname(self.trace_path), exist_ok=True)
|
||||
self._trace_lines = 0
|
||||
except Exception:
|
||||
self.trace_path = None
|
||||
@@ -2566,11 +2596,26 @@ def main():
|
||||
"created_at": time.time(), "turns": []}
|
||||
state["tasks"][args.name] = sub
|
||||
save_state(args.state, state)
|
||||
# 子代理链路:向父任务链路文件报到(开始 + 结束各一条),
|
||||
# 服务端凭标记把子链路文件拼进父任务的代理链;子任务自己
|
||||
# 的过程条目仍写它自己的链路文件(main 起动时按 --name 定)。
|
||||
child_trace = (os.path.basename(srv.trace_path)
|
||||
if srv.trace_path else None)
|
||||
_append_trace_marker(
|
||||
args.state, args.parent_task,
|
||||
{"kind": "agent-start", "id": "agent:" + args.name,
|
||||
"name": args.name, "prompt": (args.task or "")[:300],
|
||||
"trace": child_trace, "ts": int(time.time() * 1000)})
|
||||
result = run_task_turn(srv, state, sub, args.task,
|
||||
args.model, args.effort, args.sandbox,
|
||||
timeout=args.turn_timeout,
|
||||
interrupt_file=_interrupt_request_path(args.state, args.name))
|
||||
save_state(args.state, state)
|
||||
_append_trace_marker(
|
||||
args.state, args.parent_task,
|
||||
{"kind": "agent-done", "id": "agent:" + args.name + ":done",
|
||||
"name": args.name, "status": sub["status"],
|
||||
"trace": child_trace, "ts": int(time.time() * 1000)})
|
||||
print(json.dumps({"sub_task": args.name, "status": sub["status"],
|
||||
"text": result.get("text", "")},
|
||||
indent=2, ensure_ascii=False))
|
||||
|
||||
Reference in new issue
Block a user