Show turn summaries only in the transcript, not as inbox reports

This commit is contained in:
Ian Gulliver
2026-10-15 20:01:53 -07:00
parent 4ea79a0c61
commit ccd55afe13
24 changed files with 269 additions and 385 deletions
-9
View File
@@ -182,12 +182,6 @@ function card(item) {
};
const renderActions = () => {
switch (item.kind) {
case "report": {
const ok = h("button", { type: "button", class: "primary" }, "OK");
ok.onclick = () => void submit({ item_id: item.id, type: "dismiss" });
replace(actions, h("div", { class: "ack" }, ok));
return;
}
case "question": {
const placeholder = "Your answer, a question back, or new instructions";
if (!multiple) {
@@ -226,9 +220,6 @@ function card(item) {
case "question":
replace(body, markdown(String(p["context"]), "md context", { choice, taskId: item.task_id }), markdown(String(p["question"]), "md question", { taskId: item.task_id }), h("p", { class: "why" }, h("strong", {}, "Blocked on: "), inlineMarkdown(String(p["why_blocked"]), { taskId: item.task_id })), attachments(p["attachments"] ?? [], item.task_id));
break;
case "report":
replace(body, markdown(String(p["text"] ?? ""), "md summary", { taskId: item.task_id }));
break;
case "session_error":
replace(body, h("p", { class: "error-text" }, String(p["error"])));
break;
+1 -1
View File
File diff suppressed because one or more lines are too long
+24 -16
View File
@@ -3,8 +3,6 @@ function wants(p, item) {
switch (item.kind) {
case "question":
return item.blocking ? p.blocking_questions : p.questions;
case "report":
return p.reports;
case "session_error":
return p.errors;
case "rate_limited":
@@ -13,25 +11,35 @@ function wants(p, item) {
}
function itemText(item) {
const p = item.payload;
const text = item.kind === "question" ? String(p["question"] ?? "")
: item.kind === "report" ? String(p["text"] ?? "")
: item.kind === "session_error" ? `Error: ${String(p["error"] ?? "")}`
: "Rate limited";
const line = text.trim().split("\n")[0] ?? "";
return line.length > 200 ? `${line.slice(0, 199)}…` : line;
return item.kind === "question" ? String(p["question"] ?? "")
: item.kind === "session_error" ? `Error: ${String(p["error"] ?? "")}`
: "Rate limited";
}
export function notifyInbox(item) {
if (!("Notification" in window) || Notification.permission !== "granted" || !state.me || !wants(state.me.notification_prefs, item)) {
return;
}
const title = findTask(item.task_id)?.title;
const n = new Notification(title ? `byre · ${title}` : "byre", { body: itemText(item), tag: item.id });
function show(taskId, tag, text) {
const line = text.trim().split("\n")[0] ?? "";
const title = findTask(taskId)?.title;
const n = new Notification(title ? `byre · ${title}` : "byre", { body: line.length > 200 ? `${line.slice(0, 199)}…` : line, tag });
n.onclick = () => {
window.focus();
if (item.task_id) {
focusTask(item.task_id);
if (taskId) {
focusTask(taskId);
}
n.close();
};
}
function allowed() {
return "Notification" in window && Notification.permission === "granted" && !!state.me;
}
export function notifyInbox(item) {
if (!allowed() || !wants(state.me.notification_prefs, item)) {
return;
}
show(item.task_id, item.id, itemText(item));
}
export function notifyTurn(ev) {
if (!allowed() || !state.me.notification_prefs.reports || ev.payload["summary"] !== true || !ev.task_id) {
return;
}
show(ev.task_id, `turn-${ev.seq}`, String(ev.payload["result"] ?? ""));
}
//# sourceMappingURL=notifications.js.map
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"notifications.js","sourceRoot":"","sources":["../src/notifications.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAExD,SAAS,KAAK,CAAC,CAAoB,EAAE,IAAe;IAClD,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;QAClB,KAAK,UAAU;YACb,OAAO,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC5D,KAAK,QAAQ;YACX,OAAO,CAAC,CAAC,OAAO,CAAC;QACnB,KAAK,eAAe;YAClB,OAAO,CAAC,CAAC,MAAM,CAAC;QAClB,KAAK,cAAc;YACjB,OAAO,CAAC,CAAC,WAAW,CAAC;IACzB,CAAC;AACH,CAAC;AAED,SAAS,QAAQ,CAAC,IAAe;IAC/B,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC;IACvB,MAAM,IAAI,GACR,IAAI,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;QACtD,CAAC,CAAC,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YAClD,CAAC,CAAC,IAAI,CAAC,IAAI,KAAK,eAAe,CAAC,CAAC,CAAC,UAAU,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,EAAE;gBACtE,CAAC,CAAC,cAAc,CAAC;IACnB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC9C,OAAO,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;AAC7D,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAAe;IACzC,IAAI,CAAC,CAAC,cAAc,IAAI,MAAM,CAAC,IAAI,YAAY,CAAC,UAAU,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,kBAAkB,EAAE,IAAI,CAAC,EAAE,CAAC;QACnI,OAAO;IACT,CAAC;IACD,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,KAAK,CAAC;IAC5C,MAAM,CAAC,GAAG,IAAI,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,KAAK,EAAE,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;IACvG,CAAC,CAAC,OAAO,GAAG,GAAG,EAAE;QACf,MAAM,CAAC,KAAK,EAAE,CAAC;QACf,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC1B,CAAC;QACD,CAAC,CAAC,KAAK,EAAE,CAAC;IACZ,CAAC,CAAC;AACJ,CAAC"}
{"version":3,"file":"notifications.js","sourceRoot":"","sources":["../src/notifications.ts"],"names":[],"mappings":"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"}
+4 -1
View File
@@ -1,5 +1,5 @@
import { get, put, subscribe } from "./api.js";
import { notifyInbox } from "./notifications.js";
import { notifyInbox, notifyTurn } from "./notifications.js";
export const tabs = ["transcript", "changes", "files", "terminal", "preview", "inbox", "reviews", "project"];
export const state = {
me: undefined,
@@ -265,6 +265,9 @@ export function handleEvent(ev) {
case "inbox.updated":
void loadInbox();
break;
case "turn.ended":
notifyTurn(ev);
break;
case "review.started":
case "review.finished":
if (state.inbox.some((i) => i.task_id === ev.task_id && i.kind === "question")) {
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -52
View File
@@ -140,57 +140,17 @@ export function mountChat(root, src, makeHead) {
resizes.observe(transcript);
const mutations = new MutationObserver(pin);
mutations.observe(transcript, { childList: true, subtree: true, characterData: true });
const visibleReports = new Map();
const acked = new Set();
const ackVisible = () => {
if (document.visibilityState !== "visible") {
return;
}
const ids = [...new Set(visibleReports.values())].filter((id) => !acked.has(id) && state.inbox.some((i) => i.id === id));
if (ids.length === 0) {
return;
}
for (const id of ids) {
acked.add(id);
}
post("/api/inbox/responses", { body: { responses: ids.map((id) => ({ item_id: id, type: "dismiss" })) } }).catch((err) => {
for (const id of ids) {
acked.delete(id);
}
console.error("acknowledge reports", err);
});
};
const observer = new IntersectionObserver((entries) => {
for (const e of entries) {
const id = e.target.dataset["inboxItem"];
if (!id) {
continue;
}
if (e.isIntersecting) {
visibleReports.set(e.target, id);
}
else {
visibleReports.delete(e.target);
}
}
ackVisible();
}, { root: transcript, threshold: 0.5 });
const newCtx = () => ({
tools: new Map(),
subagents: new Map(),
reviewers: new Set(),
reviewButtons: new Map(),
run: undefined,
lastMessage: undefined,
held: undefined,
quiet: false,
handoff: undefined,
root: src.root,
taskId: src.taskId(),
watchReport: (el, itemId) => {
el.dataset["inboxItem"] = itemId;
observer.observe(el);
},
});
let ctx = newCtx();
let context = new Map();
@@ -458,8 +418,6 @@ export function mountChat(root, src, makeHead) {
};
const renderAll = () => {
stuck = true;
observer.disconnect();
visibleReports.clear();
dropStream();
pending = [];
ctx = newCtx();
@@ -484,18 +442,14 @@ export function mountChat(root, src, makeHead) {
}),
onEach([src.transcriptTopic], (fresh) => appendEvents(fresh)),
onEach([src.streamTopic], (ev) => appendDelta(ev)),
onEach(["inbox"], ackVisible),
];
document.addEventListener("visibilitychange", ackVisible);
replace(root, h("div", { class: "transcript-view" }, head, transcript, composer));
renderAll();
return () => {
for (const off of offs) {
off();
}
document.removeEventListener("visibilitychange", ackVisible);
headImpl.dispose?.();
observer.disconnect();
resizes.disconnect();
mutations.disconnect();
dropStream();
@@ -825,7 +779,6 @@ function renderEvent(ev, ctx) {
const when = h("time", { dateTime: ev.created_at, title: ev.created_at }, ago(ev.created_at));
switch (ev.kind) {
case "user.message":
ctx.lastMessage = undefined;
ctx.quiet = false;
if (ev.actor === "byre") {
const text = String(p["text"]);
@@ -843,8 +796,7 @@ function renderEvent(ev, ctx) {
return null;
}
ctx.quiet = false;
ctx.lastMessage = h("div", { class: "msg agent" }, markdown(String(p["text"]), "text md", md));
return ctx.lastMessage;
return h("div", { class: "msg agent" }, markdown(String(p["text"]), "text md", md));
case "agent.thinking":
return h("details", { class: "thinking" }, h("summary", {}, subagentTag(parentOf(p), ctx), "thinking"), markdown(String(p["text"]), "text md", md));
case "agent.tool_result": {
@@ -858,9 +810,6 @@ function renderEvent(ev, ctx) {
return null;
}
case "turn.ended":
if (typeof p["inbox_item_id"] === "string" && p["inbox_item_id"] && ctx.lastMessage) {
ctx.watchReport(ctx.lastMessage, p["inbox_item_id"]);
}
if (p["handoff"] === true) {
return h("div", { class: "note" }, "handoff written; continuing in a new session with a fresh context ", when);
}
+1 -1
View File
File diff suppressed because one or more lines are too long
+4 -4
View File
@@ -747,13 +747,13 @@ export interface components {
git_email: string;
notification_prefs: components["schemas"]["NotificationPrefs"];
};
/** @description Which new inbox items notify this user in their open byre pages. */
/** @description Which new inbox items and finished turns notify this user in their open byre pages. */
NotificationPrefs: {
/** @description Questions asked with blocking: true. On by default. */
blocking_questions: boolean;
/** @description Non-blocking questions. */
questions: boolean;
/** @description Turn summaries. */
/** @description Task turns that end with a summary (turn.ended with summary: true). */
reports: boolean;
/** @description session_error items. */
errors: boolean;
@@ -1035,11 +1035,11 @@ export interface components {
agent_session_id: string;
thread_id: string;
/** @enum {string} */
kind: "question" | "report" | "session_error" | "rate_limited";
kind: "question" | "session_error" | "rate_limited";
/** @enum {string} */
priority: "blocked" | "gate" | "review" | "fyi";
blocking: boolean;
/** @description question: context (Markdown with the choices inline as `- ( ) text` / `- (*) text` pick-one or `- [ ] text` / `- [x] text` pick-any list items), question, why_blocked, options (each choice's text, in order), multiple (true for pick-any), recommended (the marked choices' text), attachments. report: text (the final message of a turn that ended normally). session_error: error. rate_limited: vendor, rate_limit (the vendor's rate-limit status), error. */
/** @description question: context (Markdown with the choices inline as `- ( ) text` / `- (*) text` pick-one or `- [ ] text` / `- [x] text` pick-any list items), question, why_blocked, options (each choice's text, in order), multiple (true for pick-any), recommended (the marked choices' text), attachments. session_error: error. rate_limited: vendor, rate_limit (the vendor's rate-limit status), error. */
payload: Record<string, never>;
state: components["schemas"]["InboxState"];
/** @description question: the review of the first snapshot the task took after the question was asked (the snapshot at the end of the turn that asked it), once that snapshot exists; empty for other items. */
-9
View File
@@ -211,12 +211,6 @@ function card(item: InboxItem): HTMLElement {
const renderActions = () => {
switch (item.kind) {
case "report": {
const ok = h("button", { type: "button", class: "primary" }, "OK");
ok.onclick = () => void submit({ item_id: item.id, type: "dismiss" });
replace(actions, h("div", { class: "ack" }, ok));
return;
}
case "question": {
const placeholder = "Your answer, a question back, or new instructions";
if (!multiple) {
@@ -262,9 +256,6 @@ function card(item: InboxItem): HTMLElement {
attachments((p["attachments"] as Attachment[] | undefined) ?? [], item.task_id),
);
break;
case "report":
replace(body, markdown(String(p["text"] ?? ""), "md summary", { taskId: item.task_id }));
break;
case "session_error":
replace(body, h("p", { class: "error-text" }, String(p["error"])));
break;
+26 -16
View File
@@ -1,12 +1,10 @@
import type { InboxItem, NotificationPrefs } from "./api.js";
import type { ByreEvent, InboxItem, NotificationPrefs } from "./api.js";
import { findTask, focusTask, state } from "./state.js";
function wants(p: NotificationPrefs, item: InboxItem): boolean {
switch (item.kind) {
case "question":
return item.blocking ? p.blocking_questions : p.questions;
case "report":
return p.reports;
case "session_error":
return p.errors;
case "rate_limited":
@@ -16,26 +14,38 @@ function wants(p: NotificationPrefs, item: InboxItem): boolean {
function itemText(item: InboxItem): string {
const p = item.payload;
const text =
item.kind === "question" ? String(p["question"] ?? "")
: item.kind === "report" ? String(p["text"] ?? "")
return item.kind === "question" ? String(p["question"] ?? "")
: item.kind === "session_error" ? `Error: ${String(p["error"] ?? "")}`
: "Rate limited";
const line = text.trim().split("\n")[0] ?? "";
return line.length > 200 ? `${line.slice(0, 199)}…` : line;
}
export function notifyInbox(item: InboxItem): void {
if (!("Notification" in window) || Notification.permission !== "granted" || !state.me || !wants(state.me.notification_prefs, item)) {
return;
}
const title = findTask(item.task_id)?.title;
const n = new Notification(title ? `byre · ${title}` : "byre", { body: itemText(item), tag: item.id });
function show(taskId: string, tag: string, text: string): void {
const line = text.trim().split("\n")[0] ?? "";
const title = findTask(taskId)?.title;
const n = new Notification(title ? `byre · ${title}` : "byre", { body: line.length > 200 ? `${line.slice(0, 199)}…` : line, tag });
n.onclick = () => {
window.focus();
if (item.task_id) {
focusTask(item.task_id);
if (taskId) {
focusTask(taskId);
}
n.close();
};
}
function allowed(): boolean {
return "Notification" in window && Notification.permission === "granted" && !!state.me;
}
export function notifyInbox(item: InboxItem): void {
if (!allowed() || !wants(state.me!.notification_prefs, item)) {
return;
}
show(item.task_id, item.id, itemText(item));
}
export function notifyTurn(ev: ByreEvent): void {
if (!allowed() || !state.me!.notification_prefs.reports || ev.payload["summary"] !== true || !ev.task_id) {
return;
}
show(ev.task_id, `turn-${ev.seq}`, String(ev.payload["result"] ?? ""));
}
+4 -1
View File
@@ -1,5 +1,5 @@
import { get, put, subscribe, type AgentSession, type ByreEvent, type Focus, type InboxItem, type Project, type Snapshot, type Task, type TaskTab, type User } from "./api.js";
import { notifyInbox } from "./notifications.js";
import { notifyInbox, notifyTurn } from "./notifications.js";
export type Topic =
| "me"
@@ -323,6 +323,9 @@ export function handleEvent(ev: ByreEvent): void {
case "inbox.updated":
void loadInbox();
break;
case "turn.ended":
notifyTurn(ev);
break;
case "review.started":
case "review.finished":
if (state.inbox.some((i) => i.task_id === ev.task_id && i.kind === "question")) {
+1 -57
View File
@@ -72,11 +72,9 @@ type Ctx = {
run: Run | undefined;
root: () => string;
taskId: string | undefined;
lastMessage: HTMLElement | undefined;
held: HTMLElement | undefined;
quiet: boolean;
handoff: { summary: HTMLElement; body: HTMLElement } | undefined;
watchReport: (el: HTMLElement, itemId: string) => void;
};
const taskSource: ChatSource = {
@@ -210,60 +208,17 @@ export function mountChat(root: HTMLElement, src: ChatSource, makeHead: (rerende
const mutations = new MutationObserver(pin);
mutations.observe(transcript, { childList: true, subtree: true, characterData: true });
const visibleReports = new Map<Element, string>();
const acked = new Set<string>();
const ackVisible = () => {
if (document.visibilityState !== "visible") {
return;
}
const ids = [...new Set(visibleReports.values())].filter((id) => !acked.has(id) && state.inbox.some((i) => i.id === id));
if (ids.length === 0) {
return;
}
for (const id of ids) {
acked.add(id);
}
post("/api/inbox/responses", { body: { responses: ids.map((id) => ({ item_id: id, type: "dismiss" as const })) } }).catch((err) => {
for (const id of ids) {
acked.delete(id);
}
console.error("acknowledge reports", err);
});
};
const observer = new IntersectionObserver(
(entries) => {
for (const e of entries) {
const id = (e.target as HTMLElement).dataset["inboxItem"];
if (!id) {
continue;
}
if (e.isIntersecting) {
visibleReports.set(e.target, id);
} else {
visibleReports.delete(e.target);
}
}
ackVisible();
},
{ root: transcript, threshold: 0.5 },
);
const newCtx = (): Ctx => ({
tools: new Map(),
subagents: new Map(),
reviewers: new Set(),
reviewButtons: new Map(),
run: undefined,
lastMessage: undefined,
held: undefined,
quiet: false,
handoff: undefined,
root: src.root,
taskId: src.taskId(),
watchReport: (el, itemId) => {
el.dataset["inboxItem"] = itemId;
observer.observe(el);
},
});
let ctx = newCtx();
let context = new Map<string, Usage>();
@@ -543,8 +498,6 @@ export function mountChat(root: HTMLElement, src: ChatSource, makeHead: (rerende
const renderAll = () => {
stuck = true;
observer.disconnect();
visibleReports.clear();
dropStream();
pending = [];
ctx = newCtx();
@@ -570,9 +523,7 @@ export function mountChat(root: HTMLElement, src: ChatSource, makeHead: (rerende
}),
onEach([src.transcriptTopic], (fresh) => appendEvents(fresh as ByreEvent[])),
onEach([src.streamTopic], (ev) => appendDelta(ev as ByreEvent)),
onEach(["inbox"], ackVisible),
];
document.addEventListener("visibilitychange", ackVisible);
replace(root, h("div", { class: "transcript-view" }, head, transcript, composer));
renderAll();
@@ -580,9 +531,7 @@ export function mountChat(root: HTMLElement, src: ChatSource, makeHead: (rerende
for (const off of offs) {
off();
}
document.removeEventListener("visibilitychange", ackVisible);
headImpl.dispose?.();
observer.disconnect();
resizes.disconnect();
mutations.disconnect();
dropStream();
@@ -963,7 +912,6 @@ function renderEvent(ev: ByreEvent, ctx: Ctx): HTMLElement | null {
switch (ev.kind) {
case "user.message":
ctx.lastMessage = undefined;
ctx.quiet = false;
if (ev.actor === "byre") {
const text = String(p["text"]);
@@ -993,8 +941,7 @@ function renderEvent(ev: ByreEvent, ctx: Ctx): HTMLElement | null {
return null;
}
ctx.quiet = false;
ctx.lastMessage = h("div", { class: "msg agent" }, markdown(String(p["text"]), "text md", md));
return ctx.lastMessage;
return h("div", { class: "msg agent" }, markdown(String(p["text"]), "text md", md));
case "agent.thinking":
return h("details", { class: "thinking" }, h("summary", {}, subagentTag(parentOf(p), ctx), "thinking"), markdown(String(p["text"]), "text md", md));
@@ -1011,9 +958,6 @@ function renderEvent(ev: ByreEvent, ctx: Ctx): HTMLElement | null {
}
case "turn.ended":
if (typeof p["inbox_item_id"] === "string" && p["inbox_item_id"] && ctx.lastMessage) {
ctx.watchReport(ctx.lastMessage, p["inbox_item_id"]);
}
if (p["handoff"] === true) {
return h("div", { class: "note" }, "handoff written; continuing in a new session with a fresh context ", when);
}
-5
View File
@@ -1481,11 +1481,6 @@ pre.file-text .line::before {
margin: 10px 0 4px;
}
.ack {
display: flex;
justify-content: flex-end;
}
.why {
color: var(--muted);
margin: 0 0 8px;