highlight active subagents
This commit is contained in:
Vendored
+2
-1
@@ -57,7 +57,7 @@ export function currentSession(taskId) {
|
||||
return state.sessions.get(taskId)?.findLast((s) => s.state !== "completed") ?? state.sessions.get(taskId)?.at(-1);
|
||||
}
|
||||
const workingTitles = {
|
||||
running: "The agent is mid-turn",
|
||||
running: "The agent is mid-turn or its subagents are running",
|
||||
review_pending: "A review of the turn's work is about to start",
|
||||
reviewing: "A review is running",
|
||||
discarding: "Discarding",
|
||||
@@ -438,6 +438,7 @@ export function handleEvent(ev) {
|
||||
break;
|
||||
case "session.created":
|
||||
case "session.state":
|
||||
case "session.subagents":
|
||||
case "session.model":
|
||||
if (ev.owner) {
|
||||
void loadLeadSessions();
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+29
-6
@@ -231,7 +231,10 @@ export function mountChat(root, src, makeHead) {
|
||||
transcript.insertBefore(el, scanner);
|
||||
}
|
||||
};
|
||||
const syncRunning = () => setRunning(src.session()?.state === "running" && ctx.open);
|
||||
const syncRunning = () => {
|
||||
setRunning(src.session()?.state === "running" && ctx.open);
|
||||
transcript.classList.toggle("subagents", (src.session()?.subagents ?? 0) > 0);
|
||||
};
|
||||
const release = () => {
|
||||
const buffered = ctx.held;
|
||||
if (!buffered) {
|
||||
@@ -425,6 +428,16 @@ export function mountChat(root, src, makeHead) {
|
||||
claudeTaskStatus(p, ctx);
|
||||
continue;
|
||||
}
|
||||
if (ev.kind === "session.subagents") {
|
||||
if (p["subagents"] === 0) {
|
||||
for (const sub of ctx.subagents.values()) {
|
||||
if (sub.el.classList.contains("active")) {
|
||||
endSubagent(sub, "error", "ended with its process");
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!sink && ((ev.kind === "agent.message" && !parent) || ev.kind === "turn.ended" || ev.kind === "user.message")) {
|
||||
dropStream();
|
||||
}
|
||||
@@ -817,9 +830,10 @@ function renderSubagent(p, ctx) {
|
||||
const result = h("div", { class: "tool-result pending" }, "running…");
|
||||
const body = h("div", { class: "subagent-body" });
|
||||
const tag = subagentTag(parentOf(p), ctx);
|
||||
const el = h("details", { class: "tool subagent" }, h("summary", {}, tag, h("span", { class: "tool-name" }, `${p["name"]} ${type}`), " ", h("span", { class: "tool-brief" }, description), " ", status), markdown(String(input["prompt"] ?? ""), "text md subagent-prompt", { taskId: ctx.taskId }), body, result);
|
||||
ctx.tools.set(id, result);
|
||||
ctx.subagents.set(id, { type, description, status, body, run: undefined });
|
||||
return h("details", { class: "tool subagent" }, h("summary", {}, tag, h("span", { class: "tool-name" }, `${p["name"]} ${type}`), " ", h("span", { class: "tool-brief" }, description), " ", status), markdown(String(input["prompt"] ?? ""), "text md subagent-prompt", { taskId: ctx.taskId }), body, result);
|
||||
ctx.subagents.set(id, { el, type, description, status, body, run: undefined });
|
||||
return el;
|
||||
}
|
||||
function claudeTaskStatus(p, ctx) {
|
||||
if (p["type"] !== "system") {
|
||||
@@ -831,17 +845,26 @@ function claudeTaskStatus(p, ctx) {
|
||||
}
|
||||
switch (p["subtype"]) {
|
||||
case "task_started":
|
||||
sub.el.classList.add("active");
|
||||
replace(sub.status, p["is_backgrounded"] === true ? "running in the background" : "running");
|
||||
return;
|
||||
case "task_progress":
|
||||
replace(sub.status, String(p["description"] ?? "running"));
|
||||
return;
|
||||
case "task_notification":
|
||||
sub.status.className = `subagent-status ${p["status"] === "completed" ? "done" : "error"}`;
|
||||
replace(sub.status, String(p["status"]));
|
||||
endSubagent(sub, p["status"] === "completed" ? "done" : "error", String(p["status"]));
|
||||
return;
|
||||
}
|
||||
}
|
||||
function endSubagent(sub, kind, status) {
|
||||
sub.el.classList.remove("active");
|
||||
sub.status.className = `subagent-status ${kind}`;
|
||||
replace(sub.status, status);
|
||||
for (const result of sub.body.querySelectorAll(".tool-result.pending")) {
|
||||
result.className = "tool-result unfinished";
|
||||
replace(result, "no result");
|
||||
}
|
||||
}
|
||||
function markReview(button, r) {
|
||||
const n = findingCount(r);
|
||||
button.classList.toggle("attention", n > 0);
|
||||
@@ -953,7 +976,7 @@ function renderEvent(ev, ctx) {
|
||||
}
|
||||
case "turn.ended":
|
||||
for (const result of ctx.tools.values()) {
|
||||
if (result.classList.contains("pending")) {
|
||||
if (result.classList.contains("pending") && !result.closest(".tool.subagent.active")) {
|
||||
result.className = "tool-result unfinished";
|
||||
replace(result, "no result");
|
||||
}
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -1334,6 +1334,8 @@ export interface components {
|
||||
forked_from?: string;
|
||||
/** @description With forked_from: the vendor's id of the last thing the fork keeps, a message for Claude Code, a turn for Codex. */
|
||||
forked_at?: string;
|
||||
/** @description How many of the agent's subagents are running, background ones included after the turn that started them has ended. */
|
||||
subagents: number;
|
||||
};
|
||||
SessionCreate: {
|
||||
prompt: string;
|
||||
|
||||
+2
-1
@@ -89,7 +89,7 @@ export function currentSession(taskId: string): AgentSession | undefined {
|
||||
}
|
||||
|
||||
const workingTitles: Partial<Record<Task["state"], string>> = {
|
||||
running: "The agent is mid-turn",
|
||||
running: "The agent is mid-turn or its subagents are running",
|
||||
review_pending: "A review of the turn's work is about to start",
|
||||
reviewing: "A review is running",
|
||||
discarding: "Discarding",
|
||||
@@ -518,6 +518,7 @@ export function handleEvent(ev: ByreEvent): void {
|
||||
break;
|
||||
case "session.created":
|
||||
case "session.state":
|
||||
case "session.subagents":
|
||||
case "session.model":
|
||||
if (ev.owner) {
|
||||
void loadLeadSessions();
|
||||
|
||||
+32
-7
@@ -65,6 +65,7 @@ type Run = {
|
||||
};
|
||||
|
||||
type Subagent = {
|
||||
el: HTMLElement;
|
||||
type: string;
|
||||
description: string;
|
||||
status: HTMLElement;
|
||||
@@ -308,7 +309,10 @@ export function mountChat(root: HTMLElement, src: ChatSource, makeHead: (rerende
|
||||
transcript.insertBefore(el, scanner);
|
||||
}
|
||||
};
|
||||
const syncRunning = () => setRunning(src.session()?.state === "running" && ctx.open);
|
||||
const syncRunning = () => {
|
||||
setRunning(src.session()?.state === "running" && ctx.open);
|
||||
transcript.classList.toggle("subagents", (src.session()?.subagents ?? 0) > 0);
|
||||
};
|
||||
const release = () => {
|
||||
const buffered = ctx.held;
|
||||
if (!buffered) {
|
||||
@@ -515,6 +519,16 @@ export function mountChat(root: HTMLElement, src: ChatSource, makeHead: (rerende
|
||||
claudeTaskStatus(p, ctx);
|
||||
continue;
|
||||
}
|
||||
if (ev.kind === "session.subagents") {
|
||||
if (p["subagents"] === 0) {
|
||||
for (const sub of ctx.subagents.values()) {
|
||||
if (sub.el.classList.contains("active")) {
|
||||
endSubagent(sub, "error", "ended with its process");
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!sink && ((ev.kind === "agent.message" && !parent) || ev.kind === "turn.ended" || ev.kind === "user.message")) {
|
||||
dropStream();
|
||||
}
|
||||
@@ -947,9 +961,7 @@ function renderSubagent(p: Record<string, unknown>, ctx: Ctx): HTMLElement {
|
||||
const result = h("div", { class: "tool-result pending" }, "running…");
|
||||
const body = h("div", { class: "subagent-body" });
|
||||
const tag = subagentTag(parentOf(p), ctx);
|
||||
ctx.tools.set(id, result);
|
||||
ctx.subagents.set(id, { type, description, status, body, run: undefined });
|
||||
return h(
|
||||
const el = h(
|
||||
"details",
|
||||
{ class: "tool subagent" },
|
||||
h("summary", {}, tag, h("span", { class: "tool-name" }, `${p["name"]} ${type}`), " ", h("span", { class: "tool-brief" }, description), " ", status),
|
||||
@@ -957,6 +969,9 @@ function renderSubagent(p: Record<string, unknown>, ctx: Ctx): HTMLElement {
|
||||
body,
|
||||
result,
|
||||
);
|
||||
ctx.tools.set(id, result);
|
||||
ctx.subagents.set(id, { el, type, description, status, body, run: undefined });
|
||||
return el;
|
||||
}
|
||||
|
||||
function claudeTaskStatus(p: Record<string, unknown>, ctx: Ctx): void {
|
||||
@@ -969,18 +984,28 @@ function claudeTaskStatus(p: Record<string, unknown>, ctx: Ctx): void {
|
||||
}
|
||||
switch (p["subtype"]) {
|
||||
case "task_started":
|
||||
sub.el.classList.add("active");
|
||||
replace(sub.status, p["is_backgrounded"] === true ? "running in the background" : "running");
|
||||
return;
|
||||
case "task_progress":
|
||||
replace(sub.status, String(p["description"] ?? "running"));
|
||||
return;
|
||||
case "task_notification":
|
||||
sub.status.className = `subagent-status ${p["status"] === "completed" ? "done" : "error"}`;
|
||||
replace(sub.status, String(p["status"]));
|
||||
endSubagent(sub, p["status"] === "completed" ? "done" : "error", String(p["status"]));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function endSubagent(sub: Subagent, kind: string, status: string): void {
|
||||
sub.el.classList.remove("active");
|
||||
sub.status.className = `subagent-status ${kind}`;
|
||||
replace(sub.status, status);
|
||||
for (const result of sub.body.querySelectorAll(".tool-result.pending")) {
|
||||
result.className = "tool-result unfinished";
|
||||
replace(result as HTMLElement, "no result");
|
||||
}
|
||||
}
|
||||
|
||||
function markReview(button: HTMLElement, r: Review): void {
|
||||
const n = findingCount(r);
|
||||
button.classList.toggle("attention", n > 0);
|
||||
@@ -1111,7 +1136,7 @@ function renderEvent(ev: ByreEvent, ctx: Ctx): HTMLElement | null {
|
||||
|
||||
case "turn.ended":
|
||||
for (const result of ctx.tools.values()) {
|
||||
if (result.classList.contains("pending")) {
|
||||
if (result.classList.contains("pending") && !result.closest(".tool.subagent.active")) {
|
||||
result.className = "tool-result unfinished";
|
||||
replace(result, "no result");
|
||||
}
|
||||
|
||||
+9
-3
@@ -693,7 +693,9 @@ button.compact.danger {
|
||||
}
|
||||
|
||||
.spinner,
|
||||
.transcript.running :is(.tool, .tool-group):has(.tool-result.pending) > summary::after {
|
||||
.transcript.running :is(.tool, .tool-group):has(.tool-result.pending) > summary::after,
|
||||
.transcript:is(.running, .subagents) .tool.subagent.active > summary::after,
|
||||
.transcript.subagents .tool.subagent.active :is(.tool, .tool-group):has(.tool-result.pending) > summary::after {
|
||||
--frames: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='50' height='10'%3E%3Cg%3E%3Ccircle cx='1' cy='1' r='1'/%3E%3Ccircle cx='1' cy='5' r='1'/%3E%3Ccircle cx='4' cy='1' r='1'/%3E%3Ccircle cx='6' cy='1' r='1'/%3E%3Ccircle cx='9' cy='1' r='1'/%3E%3Ccircle cx='9' cy='5' r='1'/%3E%3Ccircle cx='11' cy='1' r='1'/%3E%3Ccircle cx='14' cy='1' r='1'/%3E%3Ccircle cx='14' cy='5' r='1'/%3E%3Ccircle cx='14' cy='9' r='1'/%3E%3Ccircle cx='19' cy='1' r='1'/%3E%3Ccircle cx='19' cy='5' r='1'/%3E%3Ccircle cx='19' cy='9' r='1'/%3E%3Ccircle cx='21' cy='9' r='1'/%3E%3Ccircle cx='24' cy='1' r='1'/%3E%3Ccircle cx='24' cy='5' r='1'/%3E%3Ccircle cx='24' cy='9' r='1'/%3E%3Ccircle cx='26' cy='9' r='1'/%3E%3Ccircle cx='29' cy='5' r='1'/%3E%3Ccircle cx='29' cy='9' r='1'/%3E%3Ccircle cx='31' cy='5' r='1'/%3E%3Ccircle cx='31' cy='9' r='1'/%3E%3Ccircle cx='34' cy='9' r='1'/%3E%3Ccircle cx='36' cy='1' r='1'/%3E%3Ccircle cx='36' cy='5' r='1'/%3E%3Ccircle cx='36' cy='9' r='1'/%3E%3Ccircle cx='39' cy='9' r='1'/%3E%3Ccircle cx='41' cy='1' r='1'/%3E%3Ccircle cx='41' cy='5' r='1'/%3E%3Ccircle cx='41' cy='9' r='1'/%3E%3Ccircle cx='46' cy='1' r='1'/%3E%3Ccircle cx='46' cy='5' r='1'/%3E%3Ccircle cx='46' cy='9' r='1'/%3E%3Ccircle cx='49' cy='1' r='1'/%3E%3C/g%3E%3C/svg%3E");
|
||||
flex-shrink: 0;
|
||||
width: 5px;
|
||||
@@ -1001,7 +1003,9 @@ button.compact.danger {
|
||||
}
|
||||
}
|
||||
|
||||
.transcript.running :is(.tool, .tool-group):has(.tool-result.pending) {
|
||||
.transcript.running :is(.tool, .tool-group):has(.tool-result.pending),
|
||||
.transcript:is(.running, .subagents) .tool.subagent.active,
|
||||
.transcript.subagents .tool.subagent.active :is(.tool, .tool-group):has(.tool-result.pending) {
|
||||
border-color: var(--task);
|
||||
|
||||
@starting-style {
|
||||
@@ -1009,7 +1013,9 @@ button.compact.danger {
|
||||
}
|
||||
}
|
||||
|
||||
.transcript.running :is(.tool, .tool-group):has(.tool-result.pending) > summary::after {
|
||||
.transcript.running :is(.tool, .tool-group):has(.tool-result.pending) > summary::after,
|
||||
.transcript:is(.running, .subagents) .tool.subagent.active > summary::after,
|
||||
.transcript.subagents .tool.subagent.active :is(.tool, .tool-group):has(.tool-result.pending) > summary::after {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
|
||||
Reference in New Issue
Block a user