Subagents by name; the activity panel as the reference: header button, compact, tasks
- "Subagents" everywhere a person reads it (chip, popover with Fewest/Most, scheduled task,
panel); offered per the server's `subagents` on a model or chat where it says it, else as
before; the fields' most is the server's cap, else 32. No subagent-model setting.
- Panel: always reachable from the header's button; the line above the composer only while
something runs; Environment for an agent chat; Context with "N until auto-compact" and a
Compact button (POST /chats/{chat}/compact; a server without it is said so); Background
tasks under Running with elapsed time and a stop square each, unfolding to kind, step,
live text and conversation, nested tasks folded under their parent, Finished N folded;
Room and Plan as sections.
- i18n: 24 new sentences x 8 languages, 9 retired; CHANGELOG [Unreleased].
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
10 files changed
+773
-236
No files matched your search
+32
-18
@@ -13,9 +13,9 @@ import { markdown } from "./markdown.js"
|
||||
import { openViewer, showKind } from "./viewer.js"
|
||||
import { readLocal } from "./explorer.js"
|
||||
import { menu, ask, showLine, popover, closePopover } from "./popup.js"
|
||||
import { avatar, agentsControl, teamPossible, levelsOf, levelWord, picture } from "./models.js"
|
||||
import { avatar, agentsControl, teamPossible, subagentsCap, SUBAGENTS_HINT, levelsOf, levelWord, picture } from "./models.js"
|
||||
import { readyIds, sending, sent, discard } from "./attach.js"
|
||||
import { watchActivity, activityChanged, planList } from "./activity.js"
|
||||
import { watchActivity, activityChanged, planList, activityButton } from "./activity.js"
|
||||
import { agentBlock, agentReady, agentState, onAgentChange } from "./agent.js"
|
||||
import { folderName } from "./list.js"
|
||||
|
||||
@@ -30,7 +30,7 @@ export function openChatId() {
|
||||
|
||||
// --- Reading -----------------------------------------------------------------------------------
|
||||
|
||||
function onThisComputer(chat) {
|
||||
export function onThisComputer(chat) {
|
||||
return !!chat?.device && ctx.thisDevices().includes(chat.device.token_id)
|
||||
}
|
||||
|
||||
@@ -552,6 +552,7 @@ function renderHead(chat) {
|
||||
)
|
||||
}
|
||||
}
|
||||
tools.append(activityButton())
|
||||
tools.append(h("button", { type: "button", class: "tool", "aria-label": t("More"), title: t("More"), onclick: (e) => chatMenu(e.currentTarget, open?.chat || chat) }, icon(ICONS.more)))
|
||||
}
|
||||
|
||||
@@ -564,13 +565,20 @@ async function chatModels(chat) {
|
||||
}
|
||||
}
|
||||
|
||||
/** Whether this chat may have a team (`GET /chats/{chat}/team`): only a chat on the server. */
|
||||
/**
|
||||
* Whether this chat is offered subagents, and the instance's most: the chat's own word where the
|
||||
* server gives one (`subagents` on the chat), else its team's (`GET /chats/{chat}/team`) -- only a
|
||||
* chat on the server. null where it is not offered.
|
||||
*/
|
||||
async function teamOffered(chat) {
|
||||
if (chat.kind !== "chat") return false
|
||||
const said = chat.subagents
|
||||
if (said !== undefined && said !== null) return (typeof said === "object" ? said.offered : said) ? { cap: subagentsCap(said, ctx.me()) } : null
|
||||
if (chat.kind !== "chat") return null
|
||||
try {
|
||||
return !!(await get(`/chats/${encodeURIComponent(chat.id)}/team`)).team?.offered
|
||||
const team = (await get(`/chats/${encodeURIComponent(chat.id)}/team`)).team
|
||||
return team?.offered ? { cap: subagentsCap(team, ctx.me()) } : null
|
||||
} catch {
|
||||
return false
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -640,26 +648,31 @@ function effortChip(model, value, onChange) {
|
||||
})
|
||||
}
|
||||
|
||||
/** The team's size: a chip saying it, opening the two fields. `value` { min, max } or empty. */
|
||||
function agentsChip(value, onChange) {
|
||||
/**
|
||||
* The subagents a chat may run: a chip saying the fewest and the most, opening the two fields.
|
||||
* `value` { min, max } or empty; `cap` the instance's most, where the server says it.
|
||||
*/
|
||||
function agentsChip(value, onChange, cap) {
|
||||
const said = value?.min || value?.max ? `${value.min ?? "…"}–${value.max ?? "…"}` : t("default")
|
||||
return chip({
|
||||
ico: ICONS.users,
|
||||
label: `${t("Agents")} ${said}`,
|
||||
title: t("Its own agent included. Above one, it can add members that do the work while it leads them."),
|
||||
label: `${t("Subagents")} ${said}`,
|
||||
title: SUBAGENTS_HINT(),
|
||||
cls: "cchip--subagents",
|
||||
onclick: (e) => {
|
||||
const fields = agentsControl({ value, id: "agents-min" })
|
||||
const fields = agentsControl({ value, id: "agents-min", cap })
|
||||
popover(
|
||||
e.currentTarget,
|
||||
h(
|
||||
"div",
|
||||
{ class: "pop__body" },
|
||||
h("label", { class: "pop__title", for: "agents-min", text: t("Agents") }),
|
||||
h("label", { class: "pop__title", for: "agents-min", text: t("Subagents") }),
|
||||
h("div", { class: "agents__heads", "aria-hidden": "true" }, h("span", { text: t("Fewest") }), h("span"), h("span", { text: t("Most") })),
|
||||
fields,
|
||||
h("p", { class: "hint", text: t("Its own agent included. Above one, it can add members that do the work while it leads them.") }),
|
||||
h("p", { class: "hint", text: SUBAGENTS_HINT() }),
|
||||
h("div", { class: "pop__actions" }, h("button", { type: "button", class: "btn btn--sm btn--primary", text: t("Save"), onclick: () => (closePopover(), onChange(fields.value)) })),
|
||||
),
|
||||
{ up: true, label: t("Agents") },
|
||||
{ up: true, label: t("Subagents") },
|
||||
)
|
||||
},
|
||||
})
|
||||
@@ -705,7 +718,7 @@ async function renderChips() {
|
||||
renderChips()
|
||||
}, mayPick))
|
||||
if (model) parts.push(effortChip(model, draft.effort, (l) => ((draft.effort = l), renderChips())))
|
||||
if (draft.kind === "chat" && teamPossible(model)) parts.push(agentsChip(draft.agents, (a) => ((draft.agents = a), renderChips())))
|
||||
if (draft.kind === "chat" && teamPossible(model)) parts.push(agentsChip(draft.agents, (a) => ((draft.agents = a), renderChips()), subagentsCap(model?.subagents, ctx.me())))
|
||||
box.replaceChildren(...parts.filter(Boolean))
|
||||
return
|
||||
}
|
||||
@@ -732,8 +745,9 @@ async function renderChips() {
|
||||
if (chat.model) parts.push(modelChip(models, chat.model, (id) => save({ model: id }), mayPick))
|
||||
parts.push(effortChip(current, chat.effort || levelsOf(current).start, (l) => save({ effort: l })))
|
||||
box.replaceChildren(...parts.filter(Boolean))
|
||||
if (!(await teamOffered(chat)) || drawing !== chipsDrawn || open?.chat?.id !== chat.id) return
|
||||
box.append(agentsChip(open.chat.agents || chat.agents, (a) => save({ agents: a })))
|
||||
const offer = await teamOffered(chat)
|
||||
if (!offer || drawing !== chipsDrawn || open?.chat?.id !== chat.id) return
|
||||
box.append(agentsChip(open.chat.agents || chat.agents, (a) => save({ agents: a }), offer.cap))
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in new issue
Block a user