feat: 命令台子代理链路(主任务/模型子代理/委派子任务 + 过程调用实时同步)

host:链路条目带 thread 归属;delegate 向父链路文件写 agent-start/done 标记。服务端:GET /tasks/{id}/agents 聚合父链路 + 子链路文件成代理链(状态口径:委派以结束标记为准、无标记即进行中;模型子代理无结束信号时父任务运行中为进行中、否则未知)。命令台:新增子代理链路区,随 SSE 流防抖刷新;扁平链路也渲染 agent 标记。
This commit is contained in:
host committed 2026-10-27 11:51:11 +08:00
1 parent 34cd0eeba7
commit c4d3ec9bff
6 files changed
+366 -14

No files matched your search

+48 -1
View File
@@ -248,6 +248,8 @@
<div class="bar"><button id="btnContinueConfirm" class="primary">继续会话</button><span id="continueMsgSpan" class="muted"></span></div>
</div>
<pre id="detailBody" style="display:none"></pre>
<h3>子代理链路</h3>
<div id="agentBox" class="muted">点任务行查看详情后,这里显示子代理链路</div>
<h3>实时链路</h3>
<div id="traceBox" class="muted">点任务行查看详情后,这里显示过程链路</div>
<div id="costBox"></div>
@@ -269,7 +271,7 @@ var $ = function (s) { return document.querySelector(s); };
var KEY_STORE = "codex-console-key";
var currentTask = null;
var traceAbort = null;
var TRACE_KIND = { exec: "命令", file: "文件", call: "调用", tool: "工具", message: "消息", reasoning: "思考", plan: "计划" };
var TRACE_KIND = { exec: "命令", file: "文件", call: "调用", tool: "工具", message: "消息", reasoning: "思考", plan: "计划", "agent-start": "子代理", "agent-done": "子代理" };
var TRACE_LIMIT = 1000;
var TRACE_TERMINAL = ["completed", "failed", "interrupted", "merged", "merge-failed"];
@@ -485,6 +487,7 @@ async function loadTasks() {
function stopTrace() {
if (traceAbort) { try { traceAbort.abort(); } catch (e) {} traceAbort = null; }
if (agentTimer) { clearTimeout(agentTimer); agentTimer = null; }
}
function traceLine(e) {
var k = TRACE_KIND[e.kind] || e.kind || "?";
@@ -496,6 +499,8 @@ function traceLine(e) {
else if (e.kind === "tool") body = esc(e.server || "") + "/" + esc(e.tool || "") + " " + esc(e.status || "");
else if (e.kind === "call") body = esc(e.name || "");
else if (e.kind === "plan") body = esc(e.text || "");
else if (e.kind === "agent-start") body = "开始:" + esc(e.name || "") + (e.prompt ? "(" + esc(e.prompt) + ")" : "");
else if (e.kind === "agent-done") body = "结束:" + esc(e.name || "") + " " + esc(e.status || "");
else body = esc(JSON.stringify(e).slice(0, 300));
return "[" + k + "] " + body;
}
@@ -512,6 +517,44 @@ function renderTrace(entries, note, doneStatus) {
if (doneStatus) html += "<br><span class='muted'>链路已结束(" + esc(doneStatus) + ")</span>";
box.innerHTML = html;
}
var AGENT_STATUS = { running: "进行中", completed: "已完成", failed: "失败", interrupted: "已中断", unknown: "未知" };
var AGENT_KIND = { main: "主任务", delegate: "委派子任务", model: "模型子代理" };
var AGENT_CALL_SHOWN = 50;
var agentTimer = null;
function renderAgents(data) {
var box = $("#agentBox");
var html = "";
if (!data) { box.innerHTML = "(暂无子代理链路)"; return; }
if (data.note) html += "<span class='err'>" + esc(data.note) + "</span><br>";
var agents = data.agents || [];
if (!agents.length) html += "(暂无子代理)";
agents.forEach(function (a) {
var st = AGENT_STATUS[a.status] || a.status || "?";
html += "<div style='margin:6px 0;padding:8px 10px;border:1px solid var(--border);border-radius:var(--radius);background:#fff'>";
html += "<b>" + esc(AGENT_KIND[a.kind] || a.kind || "") + ":</b>" + esc(a.name || "") +
" <span class='st-" + esc(a.status) + "'>" + esc(st) + "</span>" +
" | 过程调用 " + (a.calls ? a.calls.length : 0) + " 条" +
(a.updated_ts ? " | 最近活动 " + fmt(a.updated_ts / 1000) : "") + "<br>";
if (a.prompt) html += "<span class='muted'>任务:" + esc(a.prompt) + "</span><br>";
if (a.calls && a.calls.length) {
var shown = a.calls.slice(-AGENT_CALL_SHOWN);
if (a.calls.length > AGENT_CALL_SHOWN) html += "(仅显示最近 " + AGENT_CALL_SHOWN + " 条,共 " + a.calls.length + " 条)<br>";
html += shown.map(traceLine).join("<br>");
} else html += "<span class='muted'>(暂无过程调用)</span>";
html += "</div>";
});
box.innerHTML = html;
}
async function loadAgents(taskId) {
try {
var d = await api("/tasks/" + encodeURIComponent(taskId) + "/agents", "GET");
renderAgents(d);
} catch (e) { $("#agentBox").innerHTML = "<span class='err'>子代理链路加载失败:" + esc(e.message) + "</span>"; }
}
function scheduleAgents(taskId) {
if (agentTimer) clearTimeout(agentTimer);
agentTimer = setTimeout(function () { loadAgents(taskId); }, 1000);
}
async function startTrace(taskId, status) {
stopTrace();
var box = $("#traceBox");
@@ -561,10 +604,12 @@ async function startTrace(taskId, status) {
entries.forEach(function (e, i) { index[e.id] = i; });
note = m.note || "";
renderTrace(entries, note, "");
scheduleAgents(taskId);
} else if (m.type === "entry") {
var e = m.entry;
if (e.id in index) entries[index[e.id]] = e; else { index[e.id] = entries.length; entries.push(e); }
renderTrace(entries, note, "");
scheduleAgents(taskId);
} else if (m.type === "done") {
done = true; doneStatus = m.status;
renderTrace(entries, note, doneStatus);
@@ -618,6 +663,8 @@ async function showDetail(taskId) {
$("#continueMsgSpan").textContent = "";
$("#continueForm").style.display = (TRACE_TERMINAL.indexOf(t.status) >= 0 && t.status !== "merged") ? "block" : "none";
startTrace(taskId, t.status);
$("#agentBox").innerHTML = "子代理链路加载中…";
loadAgents(taskId);
loadFollowups(taskId);
} catch (e) { $("#detail").innerHTML = "<span class='err'>加载失败:" + esc(e.message) + "</span>"; }
}