diff --git a/core/client.js b/core/client.js index 014f587a8..9079c4129 100644 --- a/core/client.js +++ b/core/client.js @@ -1513,11 +1513,14 @@ function defaultShape(id) { value = Math.imul(value ^ value >>> 13, 3266489909); return DEFAULT_SHAPES[((value ^ value >>> 16) >>> 0) % DEFAULT_SHAPES.length]; } -var colorOf = (bot) => isColor(bot?.color) ? bot.color : bot?.id ? defaultColor(String(bot.id)) : "black"; +var remoteSeed = (handle) => `remote:${String(handle ?? "").trim().replace(/^@+/, "").toLowerCase()}`; +var lookSeed = (bot) => typeof bot?.remote?.handle === "string" && bot.remote.handle.trim() !== "" ? remoteSeed(bot.remote.handle) : bot?.id !== void 0 ? String(bot.id) : void 0; +var colorOf = (bot) => isColor(bot?.color) ? bot.color : lookSeed(bot) !== void 0 ? defaultColor(lookSeed(bot)) : "black"; function lookOf(bot) { const avatar = bot?.avatar ?? {}; + const seed = lookSeed(bot); return { - shape: hasShape(avatar.shape) ? avatar.shape : bot?.id ? defaultShape(String(bot.id)) : FALLBACK_SHAPE, + shape: hasShape(avatar.shape) ? avatar.shape : seed !== void 0 ? defaultShape(seed) : FALLBACK_SHAPE, image: avatar.image, color: colorOf(bot) }; @@ -4406,7 +4409,7 @@ function remoteSpeaker(source, roster) { handle, isChief: member?.isChief ?? chat?.remote?.isChief }); - const bot = chat ?? { id: `remote:${handle}`, name, color: member?.color, shape: member?.shape }; + const bot = chat ?? { id: remoteSeed(handle), name, color: member?.color, shape: member?.shape }; return { bot, member, name, handle, chatId: chat ? currentPart(chat) : void 0 }; } function RemoteMessage({ source, text, roster, actions, sessionId, msgKey, useUi }) { diff --git a/core/src/client/cells.js b/core/src/client/cells.js index 32ef57c4c..496f47999 100644 --- a/core/src/client/cells.js +++ b/core/src/client/cells.js @@ -4,7 +4,7 @@ import { MarkdownText } from '@deepseek-ai/dsh-client-ui-primitives' import { inkText } from './inks.js' import { currentPart, linkMentions, mentionTarget, dayLabel, contentText, stripHeader, stripTrace, isHiddenTurn, isRelayContext, relaysBefore, relayPart, commRunOf, toolName, toolArgs, reminderPrompts } from './text.js' import { SmileIcon, ReplyIcon, DotsIcon, CheckIcon, DownloadIcon, CopyIcon, MemoryIcon } from './icons.js' -import { colorOf, toolState, BotMark, RoomAvatar } from './mark.js' +import { colorOf, remoteSeed, toolState, BotMark, RoomAvatar } from './mark.js' import { AnsweredCell, SettledQuestion, PendingQuestion } from './questions.js' import { SecretToolCell } from './secret-card.js' import { dateLocale, t } from './i18n.js' @@ -199,8 +199,9 @@ export function remoteSpeaker(source, roster) { handle, isChief: member?.isChief ?? chat?.remote?.isChief, }) - // The sidebar's Bot record when there is one; else a stable look derived from the handle. - const bot = chat ?? { id: `remote:${handle}`, name, color: member?.color, shape: member?.shape } + // The sidebar's Bot record when there is one; else a synthetic one whose id seeds the + // same handle-derived look the record would get (characters.js remoteSeed). + const bot = chat ?? { id: remoteSeed(handle), name, color: member?.color, shape: member?.shape } return { bot, member, name, handle, chatId: chat ? currentPart(chat) : undefined } } diff --git a/core/src/client/characters.js b/core/src/client/characters.js index 2824433cb..570396632 100644 --- a/core/src/client/characters.js +++ b/core/src/client/characters.js @@ -63,12 +63,22 @@ export function defaultShape(id) { return DEFAULT_SHAPES[((value ^ (value >>> 16)) >>> 0) % DEFAULT_SHAPES.length] } -// A stored shape or color wins; then the id. With no Bot it is a black gumdrop. -export const colorOf = bot => (isColor(bot?.color) ? bot.color : bot?.id ? defaultColor(String(bot.id)) : 'black') +// A remote Bot's default look is seeded by its handle, not its session id: the handle is +// the one identity both homes share, so one friend's Bot shows the same generated look on +// every surface, survives a delete/re-add, and looks the same to the friend's home too (#84). +export const remoteSeed = handle => `remote:${String(handle ?? '').trim().replace(/^@+/, '').toLowerCase()}` +const lookSeed = bot => (typeof bot?.remote?.handle === 'string' && bot.remote.handle.trim() !== '' + ? remoteSeed(bot.remote.handle) + : bot?.id !== undefined ? String(bot.id) : undefined) + +// A stored shape or color wins; then the seed (a remote Bot's handle, else its id). +// With no Bot it is a black gumdrop. +export const colorOf = bot => (isColor(bot?.color) ? bot.color : lookSeed(bot) !== undefined ? defaultColor(lookSeed(bot)) : 'black') export function lookOf(bot) { const avatar = bot?.avatar ?? {} + const seed = lookSeed(bot) return { - shape: hasShape(avatar.shape) ? avatar.shape : bot?.id ? defaultShape(String(bot.id)) : FALLBACK_SHAPE, + shape: hasShape(avatar.shape) ? avatar.shape : seed !== undefined ? defaultShape(seed) : FALLBACK_SHAPE, image: avatar.image, color: colorOf(bot), } diff --git a/core/src/client/mark.js b/core/src/client/mark.js index d357ca2f4..16a6a43f6 100644 --- a/core/src/client/mark.js +++ b/core/src/client/mark.js @@ -2,7 +2,7 @@ import { createElement as h, useEffect, useRef, useState } from 'react' import { fnv1a, lookOf, shapeId, shapeOf, MASK_ID, nextMarkSerial, characterMarkup } from './characters.js' import { t } from './i18n.js' -export { colorOf, lookOf } from './characters.js' +export { colorOf, lookOf, remoteSeed } from './characters.js' // ------------------------------------------------------------------------- // Bot mark: the Bot's character (or its uploaded photo), looked up by lookOf. diff --git a/core/test/inbound-ui.test.js b/core/test/inbound-ui.test.js index 39d54cd52..c25e41e1b 100644 --- a/core/test/inbound-ui.test.js +++ b/core/test/inbound-ui.test.js @@ -7,7 +7,7 @@ import { build } from 'esbuild' // The inbound friend conversation's display: trace lines, the thinking row, the remote speaker. const { outputFiles } = await build({ stdin: { - contents: "export { stripTrace, previewOf, nameMentions } from './src/client/text.js'\nexport { remoteSpeaker } from './src/client/cells.js'", + contents: "export { stripTrace, previewOf, nameMentions } from './src/client/text.js'\nexport { remoteSpeaker } from './src/client/cells.js'\nexport { lookOf } from './src/client/characters.js'", resolveDir: fileURLToPath(new URL('..', import.meta.url)), }, bundle: true, format: 'cjs', platform: 'browser', write: false, logLevel: 'silent', @@ -25,7 +25,7 @@ runInNewContext(outputFiles[0].text, { throw new Error(`Unexpected platform module ${name}`) }, }) -const { stripTrace, previewOf, nameMentions, remoteSpeaker } = module.exports +const { stripTrace, previewOf, nameMentions, remoteSpeaker, lookOf } = module.exports test('a reply opening with a delegation trace shows without it', () => { assert.equal(stripTrace('Chief → 助手\n\nHi Chief! Good to hear from you.'), 'Hi Chief! Good to hear from you.') @@ -81,3 +81,25 @@ test("a friend Bot's handle in a preview reads as its display name (#71)", () => ] } assert.equal(nameMentions('@b.x.home 你在?', nested), '@请找 @a.x.home 的主人 你在?') }) + +test("a friend Bot's generated look is seeded by its handle: with or without its chat record, one look (#84)", () => { + const source = { remote: { handle: '@bot-2a758c0f.danyang.home' }, senderName: 'X' } + const record = { id: 'session-y', name: 'X', remote: { handle: '@bot-2a758c0f.danyang.home' } } + const withRecord = remoteSpeaker(source, { bots: [record], remoteBots: [] }) + const without = remoteSpeaker(source, { bots: [], remoteBots: [] }) + assert.equal(withRecord.bot, record) + const kept = lookOf(withRecord.bot) + const fallback = lookOf(without.bot) + assert.equal(kept.shape, fallback.shape) + assert.equal(kept.color, fallback.color) +}) + +test('handles equal modulo @ and case share one look; a stored look still wins', () => { + const at = lookOf({ id: 'a', remote: { handle: '@Bot.X.home' } }) + const bare = lookOf({ id: 'b', remote: { handle: 'bot.x.home' } }) + assert.equal(at.shape, bare.shape) + assert.equal(at.color, bare.color) + const owned = lookOf({ id: 'c', remote: { handle: '@bot.x.home' }, color: 'green', avatar: { shape: 'kitty' } }) + assert.equal(owned.shape, 'kitty') + assert.equal(owned.color, 'green') +}) diff --git a/docs/subsystems/ds-bot-core.md b/docs/subsystems/ds-bot-core.md index e6e0b0437..e0ff38129 100644 --- a/docs/subsystems/ds-bot-core.md +++ b/docs/subsystems/ds-bot-core.md @@ -70,6 +70,7 @@ Patched upstream `ds-bot` that ships as `core/`. Never edit `ds-bot/` by hand; c 7. `remoteTurn` (`src/host/remote-chat.js`) finds what the user typed on a friend's remote chat by skipping host-appended `role:'user'` context: forms `snapshot`, `instructions` (the AGENTS.md baseline on a chat's first turn) and `notice`, and the time-context kinds. Match by form, not by a whitelist of senders, or the first message of a new chat is dropped (#66). 8. A friend's remote Bot is stored and delivered by its handle (`@.` — what the @ menu inserts and `sendUserMentions` matches). Where the user reads it — mention chips (`src/client/user-message.js`) and sidebar previews (`nameMentions` in `src/client/text.js`) — the client rewrites the handle to the display name taken from the local roster (`roster.bots[].remote`, `roster.remoteBots`), never from message text, so a remote-controlled name reaches the UI only through the sanitized roster records (#71; display names, #9). 9. `0002-flybot-api-host` `cleanRemote` sanitizes those peer-controlled `name` / `homeName` / `owner` labels (strip `@` and controls/newlines, 60-char cap) before they are stored. Details in [plugin-runtime](plugin-runtime.md) (#75). +10. A remote Bot's generated look is seeded by its handle (`remoteSeed` in `src/client/characters.js`), never by its session id: the chat record (`src/host/remote-chat.js`) carries no look, and every consumer funnels through `lookOf`/`colorOf`, so the record and `remoteSpeaker`'s no-record fallback (`src/client/cells.js`) render one stable look per handle — across surfaces, after a delete/re-add, and identically in the friend's home. A stored `color`/`avatar` on the record still wins. Carrying the friend side's real look over the wire needs Bot Social member-profile fields (#84). ## Tests diff --git a/patches/ds-bot/0003-flybot-api-client.patch b/patches/ds-bot/0003-flybot-api-client.patch index 28f78e98c..0e790b557 100644 --- a/patches/ds-bot/0003-flybot-api-client.patch +++ b/patches/ds-bot/0003-flybot-api-client.patch @@ -85,7 +85,7 @@ index 3651a710fe5fb33dfd2f056edbbb220a43bec732..65c268530255fa7465f4485cc11a9182 h('button', { type: 'button', className: 'bt-set-row bt-bs-row', onClick: duplicate }, h('span', { className: 'bt-set-copy' }, h('span', { className: 'bt-bs-label' }, t('Duplicate')), h('span', { className: 'bt-set-hint' }, t('A copy with the same job and model, and fresh memory.'))), diff --git a/src/client/cells.js b/src/client/cells.js -index a99dadcaf392ae86a22e5ba8fbd73a29e0e9003a..32ef57c4c70cd12c1c795c4a140607c48f5f3875 100644 +index a99dadcaf392ae86a22e5ba8fbd73a29e0e9003a..496f47999f8af691f1f8ad64258db09c3470477c 100644 --- a/src/client/cells.js +++ b/src/client/cells.js @@ -2,12 +2,16 @@ import { createElement as h, Fragment, useEffect, useLayoutEffect, useMemo, useR @@ -95,7 +95,8 @@ index a99dadcaf392ae86a22e5ba8fbd73a29e0e9003a..32ef57c4c70cd12c1c795c4a140607c4 -import { linkMentions, mentionTarget, dayLabel, contentText, stripHeader, isHiddenTurn, isRelayContext, relaysBefore, relayPart, commRunOf, toolName, toolArgs, reminderPrompts } from './text.js' +import { currentPart, linkMentions, mentionTarget, dayLabel, contentText, stripHeader, stripTrace, isHiddenTurn, isRelayContext, relaysBefore, relayPart, commRunOf, toolName, toolArgs, reminderPrompts } from './text.js' import { SmileIcon, ReplyIcon, DotsIcon, CheckIcon, DownloadIcon, CopyIcon, MemoryIcon } from './icons.js' - import { colorOf, toolState, BotMark, RoomAvatar } from './mark.js' +-import { colorOf, toolState, BotMark, RoomAvatar } from './mark.js' ++import { colorOf, remoteSeed, toolState, BotMark, RoomAvatar } from './mark.js' import { AnsweredCell, SettledQuestion, PendingQuestion } from './questions.js' import { SecretToolCell } from './secret-card.js' import { dateLocale, t } from './i18n.js' @@ -127,7 +128,7 @@ index a99dadcaf392ae86a22e5ba8fbd73a29e0e9003a..32ef57c4c70cd12c1c795c4a140607c4 const running = data.status === 'running' // A streaming reply stands in for the activity row. useLiveNote(actions, sessionId, `text:${node.key ?? data.seq}`, !hidden && running && texts.length > 0 ? { text: true } : null) -@@ -180,6 +186,71 @@ function GroupMessage({ bot, text, room, roster, actions, msgKey, useUi }) { +@@ -180,6 +186,72 @@ function GroupMessage({ bot, text, room, roster, actions, msgKey, useUi }) { h(Bubbles, { texts: [text], roster, selfId: bot.id, actions, sessionId: room?.id, msgKey, useUi, lead })) } @@ -144,8 +145,9 @@ index a99dadcaf392ae86a22e5ba8fbd73a29e0e9003a..32ef57c4c70cd12c1c795c4a140607c4 + handle, + isChief: member?.isChief ?? chat?.remote?.isChief, + }) -+ // The sidebar's Bot record when there is one; else a stable look derived from the handle. -+ const bot = chat ?? { id: `remote:${handle}`, name, color: member?.color, shape: member?.shape } ++ // The sidebar's Bot record when there is one; else a synthetic one whose id seeds the ++ // same handle-derived look the record would get (characters.js remoteSeed). ++ const bot = chat ?? { id: remoteSeed(handle), name, color: member?.color, shape: member?.shape } + return { bot, member, name, handle, chatId: chat ? currentPart(chat) : undefined } +} + @@ -199,7 +201,7 @@ index a99dadcaf392ae86a22e5ba8fbd73a29e0e9003a..32ef57c4c70cd12c1c795c4a140607c4 // A centered event line that names a group chat and opens it. function GroupEvent({ prefix, room, name, roster, actions }) { return h('div', { className: 'bt-event' }, -@@ -331,6 +402,11 @@ export function TriggerCell({ node, sessionId, useRoster, useSessions, useTurnCl +@@ -331,6 +403,11 @@ export function TriggerCell({ node, sessionId, useRoster, useSessions, useTurnCl : body function triggerBody() { @@ -211,7 +213,7 @@ index a99dadcaf392ae86a22e5ba8fbd73a29e0e9003a..32ef57c4c70cd12c1c795c4a140607c4 if (source.kind === 'bot') { // A part opens with its handoff package, so the earlier parts go right above it. if (source.handoff === true) return roster.roomsById[sessionId] ? null : h(EarlierParts, { sessionId, roster, actions }) -@@ -571,7 +647,10 @@ function GroupReplies({ sessionId, callId, done, content, roster, actions, useUi +@@ -571,7 +648,10 @@ function GroupReplies({ sessionId, callId, done, content, roster, actions, useUi replies.map((reply, index) => { const bot = roster.byId[reply.botId] ?? { id: reply.botId, name: reply.name, color: reply.color ?? 'gray' } if (reply.kind === 'user') { diff --git a/patches/ds-bot/0005-social-seams.patch b/patches/ds-bot/0005-social-seams.patch index 0ab3de507..a6b045de9 100644 --- a/patches/ds-bot/0005-social-seams.patch +++ b/patches/ds-bot/0005-social-seams.patch @@ -1,5 +1,48 @@ -FlyBot patch 0005-social-seams on Fei2411/DeepSeek-Bot@2b10c70 (ds-bot/). Generated by scripts/ds-bot-patches.mjs; 5 file(s). +FlyBot patch 0005-social-seams on Fei2411/DeepSeek-Bot@2b10c70 (ds-bot/). Generated by scripts/ds-bot-patches.mjs; 7 file(s). +diff --git a/src/client/characters.js b/src/client/characters.js +index 2824433cb4cdc5812e1bfce74c1151a10a75d938..5703966326d63fc5dff4447671ec172f9a11403f 100644 +--- a/src/client/characters.js ++++ b/src/client/characters.js +@@ -63,12 +63,22 @@ export function defaultShape(id) { + return DEFAULT_SHAPES[((value ^ (value >>> 16)) >>> 0) % DEFAULT_SHAPES.length] + } + +-// A stored shape or color wins; then the id. With no Bot it is a black gumdrop. +-export const colorOf = bot => (isColor(bot?.color) ? bot.color : bot?.id ? defaultColor(String(bot.id)) : 'black') ++// A remote Bot's default look is seeded by its handle, not its session id: the handle is ++// the one identity both homes share, so one friend's Bot shows the same generated look on ++// every surface, survives a delete/re-add, and looks the same to the friend's home too (#84). ++export const remoteSeed = handle => `remote:${String(handle ?? '').trim().replace(/^@+/, '').toLowerCase()}` ++const lookSeed = bot => (typeof bot?.remote?.handle === 'string' && bot.remote.handle.trim() !== '' ++ ? remoteSeed(bot.remote.handle) ++ : bot?.id !== undefined ? String(bot.id) : undefined) ++ ++// A stored shape or color wins; then the seed (a remote Bot's handle, else its id). ++// With no Bot it is a black gumdrop. ++export const colorOf = bot => (isColor(bot?.color) ? bot.color : lookSeed(bot) !== undefined ? defaultColor(lookSeed(bot)) : 'black') + export function lookOf(bot) { + const avatar = bot?.avatar ?? {} ++ const seed = lookSeed(bot) + return { +- shape: hasShape(avatar.shape) ? avatar.shape : bot?.id ? defaultShape(String(bot.id)) : FALLBACK_SHAPE, ++ shape: hasShape(avatar.shape) ? avatar.shape : seed !== undefined ? defaultShape(seed) : FALLBACK_SHAPE, + image: avatar.image, + color: colorOf(bot), + } +diff --git a/src/client/mark.js b/src/client/mark.js +index d357ca2f4f952f4e34aac5b99fa9fad2d5bc3351..16a6a43f644225aa3b830c363ff3943fbdf06075 100644 +--- a/src/client/mark.js ++++ b/src/client/mark.js +@@ -2,7 +2,7 @@ import { createElement as h, useEffect, useRef, useState } from 'react' + import { fnv1a, lookOf, shapeId, shapeOf, MASK_ID, nextMarkSerial, characterMarkup } from './characters.js' + import { t } from './i18n.js' + +-export { colorOf, lookOf } from './characters.js' ++export { colorOf, lookOf, remoteSeed } from './characters.js' + + // ------------------------------------------------------------------------- + // Bot mark: the Bot's character (or its uploaded photo), looked up by lookOf. diff --git a/src/host/channel.js b/src/host/channel.js index 40d43de581abae0bcc9a8fd485816117e48e8bea..2ef9ddeaae63b3c7a12806fc82ba75371345767c 100644 --- a/src/host/channel.js @@ -561,10 +604,10 @@ index 0000000000000000000000000000000000000000..93ee0120d8b88476eec2cec6a0196afe +} diff --git a/test/inbound-ui.test.js b/test/inbound-ui.test.js new file mode 100644 -index 0000000000000000000000000000000000000000..39d54cd523c8ec081a66c0a95393f62869d890cc +index 0000000000000000000000000000000000000000..c25e41e1b2f9126624f610fa00b515d1aba3180e --- /dev/null +++ b/test/inbound-ui.test.js -@@ -0,0 +1,83 @@ +@@ -0,0 +1,105 @@ +import { test } from 'node:test' +import assert from 'node:assert/strict' +import { fileURLToPath } from 'node:url' @@ -574,7 +617,7 @@ index 0000000000000000000000000000000000000000..39d54cd523c8ec081a66c0a95393f628 +// The inbound friend conversation's display: trace lines, the thinking row, the remote speaker. +const { outputFiles } = await build({ + stdin: { -+ contents: "export { stripTrace, previewOf, nameMentions } from './src/client/text.js'\nexport { remoteSpeaker } from './src/client/cells.js'", ++ contents: "export { stripTrace, previewOf, nameMentions } from './src/client/text.js'\nexport { remoteSpeaker } from './src/client/cells.js'\nexport { lookOf } from './src/client/characters.js'", + resolveDir: fileURLToPath(new URL('..', import.meta.url)), + }, + bundle: true, format: 'cjs', platform: 'browser', write: false, logLevel: 'silent', @@ -592,7 +635,7 @@ index 0000000000000000000000000000000000000000..39d54cd523c8ec081a66c0a95393f628 + throw new Error(`Unexpected platform module ${name}`) + }, +}) -+const { stripTrace, previewOf, nameMentions, remoteSpeaker } = module.exports ++const { stripTrace, previewOf, nameMentions, remoteSpeaker, lookOf } = module.exports + +test('a reply opening with a delegation trace shows without it', () => { + assert.equal(stripTrace('Chief → 助手\n\nHi Chief! Good to hear from you.'), 'Hi Chief! Good to hear from you.') @@ -648,6 +691,28 @@ index 0000000000000000000000000000000000000000..39d54cd523c8ec081a66c0a95393f628 + ] } + assert.equal(nameMentions('@b.x.home 你在?', nested), '@请找 @a.x.home 的主人 你在?') +}) ++ ++test("a friend Bot's generated look is seeded by its handle: with or without its chat record, one look (#84)", () => { ++ const source = { remote: { handle: '@bot-2a758c0f.danyang.home' }, senderName: 'X' } ++ const record = { id: 'session-y', name: 'X', remote: { handle: '@bot-2a758c0f.danyang.home' } } ++ const withRecord = remoteSpeaker(source, { bots: [record], remoteBots: [] }) ++ const without = remoteSpeaker(source, { bots: [], remoteBots: [] }) ++ assert.equal(withRecord.bot, record) ++ const kept = lookOf(withRecord.bot) ++ const fallback = lookOf(without.bot) ++ assert.equal(kept.shape, fallback.shape) ++ assert.equal(kept.color, fallback.color) ++}) ++ ++test('handles equal modulo @ and case share one look; a stored look still wins', () => { ++ const at = lookOf({ id: 'a', remote: { handle: '@Bot.X.home' } }) ++ const bare = lookOf({ id: 'b', remote: { handle: 'bot.x.home' } }) ++ assert.equal(at.shape, bare.shape) ++ assert.equal(at.color, bare.color) ++ const owned = lookOf({ id: 'c', remote: { handle: '@bot.x.home' }, color: 'green', avatar: { shape: 'kitty' } }) ++ assert.equal(owned.shape, 'kitty') ++ assert.equal(owned.color, 'green') ++}) diff --git a/test/remote-chat.test.js b/test/remote-chat.test.js new file mode 100644 index 0000000000000000000000000000000000000000..e96ef4d40dc3fa19344755d83db4d59b85144e0c diff --git a/patches/ds-bot/0008-client-bundle.patch b/patches/ds-bot/0008-client-bundle.patch index 53fc05ea1..0c4772c59 100644 --- a/patches/ds-bot/0008-client-bundle.patch +++ b/patches/ds-bot/0008-client-bundle.patch @@ -1,7 +1,7 @@ FlyBot patch 0008-client-bundle on Fei2411/DeepSeek-Bot@2b10c70 (ds-bot/). Generated by scripts/ds-bot-patches.mjs; 1 file(s). diff --git a/client.js b/client.js -index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafac0393cb3 100644 +index f46d64171400ccaa6c00e4577c308297a70a0b4d..9079c4129f54c8c14685868936b7b2b0175336a9 100644 --- a/client.js +++ b/client.js @@ -63,6 +63,21 @@ var ZH = { @@ -391,7 +391,24 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa // src/client/inks.js var INKS = { deepseek: ["#4D6BFE", "#6A84FF"], -@@ -1206,8 +1542,8 @@ function bodyOf(def, paint, flat = false) { +@@ -1177,11 +1513,14 @@ function defaultShape(id) { + value = Math.imul(value ^ value >>> 13, 3266489909); + return DEFAULT_SHAPES[((value ^ value >>> 16) >>> 0) % DEFAULT_SHAPES.length]; + } +-var colorOf = (bot) => isColor(bot?.color) ? bot.color : bot?.id ? defaultColor(String(bot.id)) : "black"; ++var remoteSeed = (handle) => `remote:${String(handle ?? "").trim().replace(/^@+/, "").toLowerCase()}`; ++var lookSeed = (bot) => typeof bot?.remote?.handle === "string" && bot.remote.handle.trim() !== "" ? remoteSeed(bot.remote.handle) : bot?.id !== void 0 ? String(bot.id) : void 0; ++var colorOf = (bot) => isColor(bot?.color) ? bot.color : lookSeed(bot) !== void 0 ? defaultColor(lookSeed(bot)) : "black"; + function lookOf(bot) { + const avatar = bot?.avatar ?? {}; ++ const seed = lookSeed(bot); + return { +- shape: hasShape(avatar.shape) ? avatar.shape : bot?.id ? defaultShape(String(bot.id)) : FALLBACK_SHAPE, ++ shape: hasShape(avatar.shape) ? avatar.shape : seed !== void 0 ? defaultShape(seed) : FALLBACK_SHAPE, + image: avatar.image, + color: colorOf(bot) + }; +@@ -1206,8 +1545,8 @@ function bodyOf(def, paint, flat = false) { return ""; } } @@ -402,7 +419,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa var maskBox = (size) => `maskUnits="userSpaceOnUse" x="${-size}" y="${-size}" width="${size * 3}" height="${size * 3}"`; var holes = (def, eyes) => `${eyes}`; var MASK_ID = "%MASK%"; -@@ -1262,7 +1598,7 @@ function createSource(initial) { +@@ -1262,7 +1601,7 @@ function createSource(initial) { } }; } @@ -411,7 +428,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa var isMainOf = (roster, id) => id !== void 0 && (roster.mainBotIds ?? []).includes(id); function indexRoster(value) { const byId = {}; -@@ -1353,7 +1689,7 @@ var shellTokens = ({ light, dark }) => ({ +@@ -1353,7 +1692,7 @@ var shellTokens = ({ light, dark }) => ({ "--dsw-alias-label-tertiary": { light: rgba(light.text, 0.6), dark: rgba(dark.text, 0.6) } }); var customThemes = {}; @@ -420,7 +437,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa var isPoint = (point) => Array.isArray(point) && point.length === 2 && point.every(Number.isFinite); function registerShape(id, shape) { if (typeof id !== "string" || !/^[a-z][\w-]{0,31}$/.test(id)) throw new Error("Shape ids are lowercase words"); -@@ -1374,7 +1710,7 @@ function registerShape(id, shape) { +@@ -1374,7 +1713,7 @@ function registerShape(id, shape) { const refresh = () => { markupCache.clear(); maskCache.delete(id); @@ -429,7 +446,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa }; refresh(); return () => { -@@ -1390,11 +1726,11 @@ function registerTheme(id, theme) { +@@ -1390,11 +1729,11 @@ function registerTheme(id, theme) { } const previous = customThemes[id]; customThemes[id] = { label: String(theme.label ?? id), light: theme.light, dark: theme.dark, css: typeof theme.css === "string" ? theme.css : "" }; @@ -443,7 +460,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa }; } var allThemes = () => ({ ...THEMES, ...customThemes }); -@@ -1463,6 +1799,19 @@ function mentionTarget(event) { +@@ -1463,6 +1802,19 @@ function mentionTarget(event) { const anchor = event.target instanceof Element ? event.target.closest(`a[href^="${MENTION_ORIGIN}"]`) : null; return anchor ? anchor.getAttribute("href").slice(MENTION_ORIGIN.length) : null; } @@ -463,7 +480,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa function clock(ms) { return new Date(ms).toLocaleTimeString(dateLocale(), { hour: "numeric", minute: "2-digit" }); } -@@ -1494,6 +1843,13 @@ function reminderPrompts(text) { +@@ -1494,6 +1846,13 @@ function reminderPrompts(text) { } } var stripHeader = (text) => text.replace(HEADER, ""); @@ -477,7 +494,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa function latestMemoryChange(news, ids) { const at = (id) => news?.[id]?.seen ? news[id].at ?? 0 : 0; return ids.reduce((best, id) => at(id) > at(best) ? id : best, ids[0] ?? null); -@@ -1604,7 +1960,7 @@ function previewOf(list, id, hiddenTurns = []) { +@@ -1604,7 +1963,7 @@ function previewOf(list, id, hiddenTurns = []) { const entry = outline[index]; if (hiddenTurns.includes(entry.turn)) continue; const text = entry.response || entry.prompt || ""; @@ -486,7 +503,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa } return ""; } -@@ -1626,7 +1982,7 @@ function usageOf(list, ids) { +@@ -1626,7 +1985,7 @@ function usageOf(list, ids) { } // src/client/motion.js @@ -495,7 +512,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa var reducedMotion = () => window.matchMedia?.("(prefers-reduced-motion: reduce)").matches === true; function springEasing(stiffness, damping) { const w0 = Math.sqrt(stiffness); -@@ -1659,12 +2015,12 @@ function liveSprings(apply2) { +@@ -1659,12 +2018,12 @@ function liveSprings(apply2) { const dt = Math.min(0.5, Math.max(1e-3, (now - last) / 1e3)); last = now; const steps = Math.ceil(dt * 240); @@ -511,7 +528,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa } if (Math.abs(value.x - value.to) < 0.05 && Math.abs(value.v) < 1) { value.x = value.to; -@@ -1699,10 +2055,10 @@ function liveSprings(apply2) { +@@ -1699,10 +2058,10 @@ function liveSprings(apply2) { } var CHOSEN = '[aria-pressed="true"],[aria-selected="true"],[aria-current="page"]'; function useGlide(key, { axis = "x", selector = CHOSEN, lead = SPRING_LEAD, trail = SPRING_TRAIL, box: given } = {}) { @@ -525,7 +542,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (own.current === null) { own.current = { key: void 0, -@@ -1736,7 +2092,7 @@ function useGlide(key, { axis = "x", selector = CHOSEN, lead = SPRING_LEAD, trai +@@ -1736,7 +2095,7 @@ function useGlide(key, { axis = "x", selector = CHOSEN, lead = SPRING_LEAD, trai springs.set({ start, end }, forward ? { start: trail, end: lead } : { start: lead, end: trail }, jump || hidden || before === null || reducedMotion()); container.dataset.glide = ""; }; @@ -534,7 +551,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const state = own.current; const node = box.current; if (node === state.node && key === state.key) return; -@@ -1750,7 +2106,7 @@ function useGlide(key, { axis = "x", selector = CHOSEN, lead = SPRING_LEAD, trai +@@ -1750,7 +2109,7 @@ function useGlide(key, { axis = "x", selector = CHOSEN, lead = SPRING_LEAD, trai } place(fresh); }); @@ -543,7 +560,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa own.current.observer?.disconnect(); own.current.springs.stop(); }, []); -@@ -1758,16 +2114,16 @@ function useGlide(key, { axis = "x", selector = CHOSEN, lead = SPRING_LEAD, trai +@@ -1758,16 +2117,16 @@ function useGlide(key, { axis = "x", selector = CHOSEN, lead = SPRING_LEAD, trai } function Segmented({ label, value, className, children }) { const glide = useGlide(value); @@ -564,7 +581,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const container = list.current; if (!container) return; const next = /* @__PURE__ */ new Map(); -@@ -1788,8 +2144,8 @@ function useFlip(list, key, selector, id = (node) => node.dataset.flip) { +@@ -1788,8 +2147,8 @@ function useFlip(list, key, selector, id = (node) => node.dataset.flip) { }, [key]); } function useMorph(box, key) { @@ -575,7 +592,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const node = box.current; const state = last.current; if (!node) { -@@ -1817,8 +2173,8 @@ function useMorph(box, key) { +@@ -1817,8 +2176,8 @@ function useMorph(box, key) { }); } function useLinger(value, ms) { @@ -586,7 +603,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (value) { setKept(value); return void 0; -@@ -1856,6 +2212,11 @@ body[data-bt-motion=lively] .bt-mark[data-live][data-state=idle][data-fidget=loo +@@ -1856,6 +2215,11 @@ body[data-bt-motion=lively] .bt-mark[data-live][data-state=idle][data-fidget=loo @keyframes bt-fidget-look{0%,22%,100%{transform:none}3%,9%{transform:translateX(-14%)}12%,19%{transform:translateX(14%)}} body[data-bt-motion=lively] .bt-badge-alert{right:auto;bottom:auto;left:50%;top:calc(var(--bt-size,36px) * -.14);translate:-50% 0;animation:bt-q-ride 1.3s cubic-bezier(.3,.7,.4,1) infinite} body[data-bt-motion=lively] :is(.bt-row,.bt-tile)>.bt-mark:hover .bt-badge-alert{animation:none} @@ -598,7 +615,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa @keyframes bt-q-ride{0%,55%,100%{transform:translateY(0)}20%{transform:translateY(calc(var(--bt-size,36px) * -.14))}40%{transform:translateY(0)}} /* Petting: with the pointer on a sidebar avatar or just around it (a ring a fifth of its size wide), a resting mark leans into the pointer and keeps nuzzling it, its eyes -@@ -1950,21 +2311,21 @@ body[data-bt-motion=quiet] .bt-mark[data-poke]{cursor:default} +@@ -1950,21 +2314,21 @@ body[data-bt-motion=quiet] .bt-mark[data-poke]{cursor:default} .bt-row-body{display:flex;flex-direction:column;min-width:0;flex:1 1 auto} .bt-row-name{display:flex;align-items:center;gap:6px;min-width:0;font-size:14px;line-height:20px;font-weight:400} .bt-row-title{min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} @@ -624,7 +641,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa .bt-head{position:relative;display:flex;justify-content:center;align-items:center;height:40px;flex:none;pointer-events:none} .bt-pill{pointer-events:auto;display:inline-flex;align-items:center;gap:8px;height:40px;padding:7.5px 13.5px 7.5px 7.5px;border-radius:999px;background:var(--bt-main);border:.5px solid var(--bt-line-2);box-shadow:0 2px 8px -1px rgba(0,0,0,.06),0 1px 2px rgba(0,0,0,.04);color:var(--bt-ink);font-family:inherit;font-size:14px;line-height:20px;cursor:pointer;max-width:70%;box-sizing:border-box;transition:background-color .12s ease,transform ${SPRING_TAP.duration}ms ${SPRING_TAP.easing}} -@@ -2599,6 +2960,9 @@ var CONVERSATION_CSS = ` +@@ -2599,6 +2963,9 @@ var CONVERSATION_CSS = ` .bt-think{font-size:13px;line-height:18px;color:var(--bt-ink-2);margin:2px 0 4px;max-width:min(560px,78%)} .bt-think summary{cursor:pointer;list-style:none;display:inline-flex;align-items:center;gap:6px} .bt-think summary::-webkit-details-marker{display:none} @@ -634,7 +651,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa .bt-think-text{white-space:pre-wrap;margin-top:6px;padding-left:10px;border-left:2px solid var(--bt-line-2)} .bt-event{display:flex;justify-content:center;align-items:center;gap:2px;min-height:24px;margin:2px 0;font-size:12px;line-height:16px;color:var(--bt-ink-2);flex-wrap:wrap} .bt-event>span{padding:0 2px} -@@ -2856,12 +3220,12 @@ function installCompiledStyles(text, tag) { +@@ -2856,12 +3223,12 @@ function installCompiledStyles(text, tag) { } // src/client/mark.js @@ -649,7 +666,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (!enabled) return void 0; let frame = 0; let point = null; -@@ -2895,10 +3259,10 @@ function useGaze(ref, enabled) { +@@ -2895,10 +3262,10 @@ function useGaze(ref, enabled) { var FIDGETS = ["hop", "sway", "stretch", "look"]; function BotMark({ bot, look: given, size = 36, state = "idle", live = false, gaze = false, pokeable = false, label }) { const look = given ?? lookOf(bot); @@ -663,7 +680,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa useGaze(ref, gaze); const common = { ref, -@@ -2918,31 +3282,31 @@ function BotMark({ bot, look: given, size = 36, state = "idle", live = false, ga +@@ -2918,31 +3285,31 @@ function BotMark({ bot, look: given, size = 36, state = "idle", live = false, ga if (/^bt-poke/.test(event.animationName)) setPoked(false); } : void 0 }; @@ -705,7 +722,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const was = prev.current; prev.current = state; if (state !== "idle") { -@@ -2958,16 +3322,16 @@ function useSettled(state) { +@@ -2958,16 +3325,16 @@ function useSettled(state) { } function BotAvatar({ bot, size, state = "idle", main, admin, badge = true, live = true, gaze = false, pokeable = false }) { const settled = useSettled(state); @@ -729,7 +746,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ); } function clusterSeats(count, frame) { -@@ -2984,21 +3348,21 @@ function clusterSeats(count, frame) { +@@ -2984,21 +3351,21 @@ function clusterSeats(count, frame) { function RoomAvatar({ room: room2, roster, size = 36 }) { const members = room2.members.map((id) => roster.byId[id]).filter(Boolean); const people = members.length === 1 ? [members[0], null] : members; @@ -756,7 +773,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa "span", { className: "bt-cluster", style: { width: size, height: size } }, seats.map(([x, y, s], index) => { -@@ -3006,75 +3370,75 @@ function RoomAvatar({ room: room2, roster, size = 36 }) { +@@ -3006,75 +3373,75 @@ function RoomAvatar({ room: room2, roster, size = 36 }) { const style = { left: x, top: y, width: s, height: s }; if (holes2.length > 0) Object.assign(style, { WebkitMaskImage: holes2.join(","), maskImage: holes2.join(","), WebkitMaskComposite: "source-in", maskComposite: "intersect" }); if (overflow > 0 && index === 3) { @@ -857,7 +874,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ); } var SmileIcon = lineIcon(["M10 3.5a6.5 6.5 0 1 1 0 13 6.5 6.5 0 0 1 0-13z", "M7.3 11.6a3.4 3.4 0 0 0 5.4 0", "M7.75 8.25h.01M12.25 8.25h.01"]); -@@ -3093,14 +3457,14 @@ var ClockIcon = lineIcon(["M10 3.5a6.5 6.5 0 1 1 0 13 6.5 6.5 0 0 1 0-13z", "M10 +@@ -3093,14 +3460,14 @@ var ClockIcon = lineIcon(["M10 3.5a6.5 6.5 0 1 1 0 13 6.5 6.5 0 0 1 0-13z", "M10 var GearIcon = lineIcon(["M10 7.4a2.6 2.6 0 1 1 0 5.2 2.6 2.6 0 0 1 0-5.2z", "M7.85 4.4l.29-1.87a7.7 7.7 0 0 1 3.72 0l.29 1.87a6 6 0 0 1 1.63.94l1.76-.69a7.7 7.7 0 0 1 1.86 3.23l-1.47 1.18a6 6 0 0 1 0 1.88l1.47 1.18a7.7 7.7 0 0 1-1.86 3.23l-1.76-.69a6 6 0 0 1-1.63.94l-.29 1.87a7.7 7.7 0 0 1-3.72 0l-.29-1.87a6 6 0 0 1-1.63-.94l-1.76.69a7.7 7.7 0 0 1-1.86-3.23l1.47-1.18a6 6 0 0 1 0-1.88L2.6 7.88a7.7 7.7 0 0 1 1.86-3.23l1.76.69a6 6 0 0 1 1.63-.94z"]); var PinIcon = lineIcon(["M12.5 3l4.5 4.5-3 1.5-2.5 2.5.5 3.5-1.5 1.5L7 13 3.5 16.5", "M7 13l-3.5-3.5L5 8l3.5.5L11 6l1.5-3"]); function PinMarkIcon() { @@ -876,7 +893,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ) ); } -@@ -3112,6 +3476,7 @@ var CopyIcon = lineIcon(["M8.5 7h6A1.5 1.5 0 0 1 16 8.5v6a1.5 1.5 0 0 1-1.5 1.5h +@@ -3112,6 +3479,7 @@ var CopyIcon = lineIcon(["M8.5 7h6A1.5 1.5 0 0 1 16 8.5v6a1.5 1.5 0 0 1-1.5 1.5h var HideIcon = lineIcon(["M3 3l14 14", "M8.6 5.2A7.6 7.6 0 0 1 10 5c4.5 0 7 5 7 5a12.6 12.6 0 0 1-2 2.6M12.2 14.5A6.8 6.8 0 0 1 10 15c-4.5 0-7-5-7-5a12.4 12.4 0 0 1 3-3.4", "M8.5 8.5a2 2 0 0 0 3 3"]); var TrashIcon = lineIcon(["M3.5 5.5h13", "M8 5.5V4a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v1.5", "M5 5.5l.7 10A1.5 1.5 0 0 0 7.2 17h5.6a1.5 1.5 0 0 0 1.5-1.5l.7-10", "M8.5 9v4.5M11.5 9v4.5"]); var DuplicateIcon = lineIcon(["M7 7h8.5v9.5H7z", "M4.5 13V3.5H13", "M11.25 9.5v4.5M9 11.75h4.5"]); @@ -884,7 +901,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa var SlidersIcon = lineIcon(["M4 6.5h5M12.5 6.5H16M4 13.5h4M11.5 13.5H16", "M10.5 4.5v4M9.5 11.5v4"]); var MemoryIcon = lineIcon(["M5.5 3.5h8A1.5 1.5 0 0 1 15 5v10a1.5 1.5 0 0 1-1.5 1.5h-8z", "M5.5 3.5v13", "M8.5 7.5h3.5M8.5 10.5h2.5", "M12 3.5v4l1-.8 1 .8"]); var FolderIcon = lineIcon(["M3 6A1.5 1.5 0 0 1 4.5 4.5h3.2l1.8 2h6A1.5 1.5 0 0 1 17 8v6.5a1.5 1.5 0 0 1-1.5 1.5h-11A1.5 1.5 0 0 1 3 14.5z"]); -@@ -3119,14 +3484,14 @@ var ChatsIcon = lineIcon(["M3.5 5.5A1.5 1.5 0 0 1 5 4h7a1.5 1.5 0 0 1 1.5 1.5V10 +@@ -3119,14 +3487,14 @@ var ChatsIcon = lineIcon(["M3.5 5.5A1.5 1.5 0 0 1 5 4h7a1.5 1.5 0 0 1 1.5 1.5V10 var SETTINGS_ICONS = { general: GearIcon, bots: PeopleIcon, groups: ChatsIcon, usage: GaugeIcon }; // src/client/questions.js @@ -902,7 +919,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (count === 0) return void 0; const onKey = (event) => { if (event.defaultPrevented || event.metaKey || event.ctrlKey || event.altKey || event.shiftKey || event.key.length !== 1) return; -@@ -3146,10 +3511,10 @@ function useOptionKeys(count, onPick, ownRef) { +@@ -3146,10 +3514,10 @@ function useOptionKeys(count, onPick, ownRef) { }, [count, ownRef]); } function QuestionView({ question, onAnswer, onDismiss, ownAnswer = false, raised = false }) { @@ -917,7 +934,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const options = optionsOf(question); const multi = question.multiSelect === true; const custom = own.trim(); -@@ -3167,7 +3532,7 @@ function QuestionView({ question, onAnswer, onDismiss, ownAnswer = false, raised +@@ -3167,7 +3535,7 @@ function QuestionView({ question, onAnswer, onDismiss, ownAnswer = false, raised }; useOptionKeys(options.length, (index) => choose(options[index].label), ownRef); const ownLetter = options.length > 0 ? keyLetter(options.length) : void 0; @@ -926,7 +943,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ref: ownRef, rows: 1, value: own, -@@ -3186,7 +3551,7 @@ function QuestionView({ question, onAnswer, onDismiss, ownAnswer = false, raised +@@ -3186,7 +3554,7 @@ function QuestionView({ question, onAnswer, onDismiss, ownAnswer = false, raised }) : null; const rows = options.map((option, optionIndex) => { const selected = multi && picked.includes(option.label); @@ -935,7 +952,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa "button", { key: option.label, -@@ -3196,36 +3561,36 @@ function QuestionView({ question, onAnswer, onDismiss, ownAnswer = false, raised +@@ -3196,36 +3564,36 @@ function QuestionView({ question, onAnswer, onDismiss, ownAnswer = false, raised "aria-keyshortcuts": keyLetter(optionIndex).toLowerCase(), onClick: () => choose(option.label) }, @@ -985,7 +1002,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa "form", { className: raised ? "bt-q bt-q-raised" : "bt-q", -@@ -3235,24 +3600,24 @@ function QuestionView({ question, onAnswer, onDismiss, ownAnswer = false, raised +@@ -3235,24 +3603,24 @@ function QuestionView({ question, onAnswer, onDismiss, ownAnswer = false, raised submit(); } }, @@ -1020,7 +1037,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ) : null, foot ); -@@ -3262,29 +3627,29 @@ function SettledQuestion({ question, answer }) { +@@ -3262,29 +3630,29 @@ function SettledQuestion({ question, answer }) { const prompt = question?.question ?? ""; const chosen = [...answer.selected ?? [], ...answer.custom ? [answer.custom] : []]; if (chosen.length === 0) { @@ -1061,7 +1078,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ); })) ); -@@ -3292,8 +3657,8 @@ function SettledQuestion({ question, answer }) { +@@ -3292,8 +3660,8 @@ function SettledQuestion({ question, answer }) { function QuestionCard({ matched }) { const pending = matched; const questions = pending.questions ?? []; @@ -1072,7 +1089,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const question = questions[index]; if (question === void 0) return null; const onAnswer = (answer) => { -@@ -3305,10 +3670,10 @@ function QuestionCard({ matched }) { +@@ -3305,10 +3673,10 @@ function QuestionCard({ matched }) { } pending.answer({ answers: next }); }; @@ -1085,7 +1102,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (answered === question.id) return null; const onAnswer = (answer) => { setAnswered(question.id); -@@ -3318,10 +3683,10 @@ function PendingQuestion({ sessionId, question, actions, inline }) { +@@ -3318,10 +3686,10 @@ function PendingQuestion({ sessionId, question, actions, inline }) { setAnswered(question.id); void actions.dismissQuestion(sessionId); }; @@ -1098,7 +1115,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ); } function QuestionDock({ sessionId, useRoster, useCards, useSession, actions }) { -@@ -3329,16 +3694,16 @@ function QuestionDock({ sessionId, useRoster, useCards, useSession, actions }) { +@@ -3329,16 +3697,16 @@ function QuestionDock({ sessionId, useRoster, useCards, useSession, actions }) { const drawn = useCards?.((state) => state[sessionId]?.[question?.callId] > 0) === true; const loading = useSession?.(historyLoading) === true; if (question === void 0 || question.callId && (drawn || loading)) return null; @@ -1118,7 +1135,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa key: question.id, question, answer: answers.find((answer) => answer.id === question.id) ?? { selected: [] } -@@ -3346,26 +3711,26 @@ function AnsweredCell({ node }) { +@@ -3346,26 +3714,26 @@ function AnsweredCell({ node }) { } // src/client/secret-card.js @@ -1155,7 +1172,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ); var MASK_BY_CSS = typeof CSS !== "undefined" && typeof CSS.supports === "function" && CSS.supports("-webkit-text-security", "disc"); var insecure = () => typeof location !== "undefined" && location.protocol === "http:" && !/^(localhost|127(?:\.\d{1,3}){3}|\[::1\])$/.test(location.hostname); -@@ -3376,7 +3741,7 @@ var scopeLabel = (scope, roster) => { +@@ -3376,7 +3744,7 @@ var scopeLabel = (scope, roster) => { }; var when = (time) => Number.isFinite(time) ? new Date(time).toLocaleString(dateLocale(), { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" }) : ""; function MaskedInput({ value, onChange, onEnter, label, autoFocus = false }) { @@ -1164,7 +1181,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa className: "bt-secret-input", type: MASK_BY_CSS ? "text" : "password", "data-masked": MASK_BY_CSS ? "" : void 0, -@@ -3402,11 +3767,11 @@ function MaskedInput({ value, onChange, onEnter, label, autoFocus = false }) { +@@ -3402,11 +3770,11 @@ function MaskedInput({ value, onChange, onEnter, label, autoFocus = false }) { } function ScopeSwitch({ value, onChange, bot }) { const options = [["all", t("All Bots")], ["bot", t("Only {name}", { name: bot.name })]]; @@ -1179,7 +1196,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa key: id, type: "button", role: "radio", -@@ -3417,26 +3782,26 @@ function ScopeSwitch({ value, onChange, bot }) { +@@ -3417,26 +3785,26 @@ function ScopeSwitch({ value, onChange, bot }) { ); } function SecretHead({ bot, title, detail, name, onClose }) { @@ -1218,7 +1235,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const secret = (roster.secrets ?? []).find((entry) => entry.name === request.name); const run = (promise) => { setBusy(true); -@@ -3452,73 +3817,73 @@ function SecretCard({ bot, request, roster, actions }) { +@@ -3452,73 +3820,73 @@ function SecretCard({ bot, request, roster, actions }) { if (!busy) void run(actions.secretAllow(request.id)); }; const filling = request.kind === "fill" || replacing; @@ -1315,7 +1332,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa } return null; } -@@ -3531,44 +3896,44 @@ function SecretDock({ sessionId, useRoster, useCards, useSession, actions }) { +@@ -3531,44 +3899,44 @@ function SecretDock({ sessionId, useRoster, useCards, useSession, actions }) { const drawn = (entry) => entry.callId && entry.sessionId === sessionId && (loading || shown[entry.callId] > 0); const waiting = (roster.secretRequests?.[bot.id] ?? []).filter((entry) => entry.status === "pending" && !drawn(entry)); if (waiting.length === 0) return null; @@ -1374,7 +1391,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const long = value.trim().length >= MIN_VALUE; const save = () => { if (!long) return; -@@ -3583,66 +3948,66 @@ function ValueEditor({ secret, onSave }) { +@@ -3583,66 +3951,66 @@ function ValueEditor({ secret, onSave }) { setValue(next); setReplaced(false); }; @@ -1467,7 +1484,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const ready = /^[A-Za-z][\w-]{0,63}$/.test(name.trim()) && value.trim().length >= MIN_VALUE; const save = () => { if (!ready) return; -@@ -3653,43 +4018,43 @@ function AddSecret({ actions, run, onDone }) { +@@ -3653,43 +4021,43 @@ function AddSecret({ actions, run, onDone }) { } }); }; @@ -1529,7 +1546,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ]; } var SECRET_CSS = ` -@@ -3756,6 +4121,47 @@ var SECRET_CSS = ` +@@ -3756,6 +4124,47 @@ var SECRET_CSS = ` @media (prefers-reduced-motion:reduce){.bt-secret-glide{transition:none}.bt-secret-check,.bt-secret-done,.bt-secret-ok,.bt-secret-edit{animation:none}} `; @@ -1577,7 +1594,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa // src/client/turn-reasoning.js function turnReasoning(steps, current) { const members = steps.length ? steps : [current]; -@@ -3767,7 +4173,7 @@ var thinkingLive = (step) => step.status === "running" && texts(step).length === +@@ -3767,7 +4176,7 @@ var thinkingLive = (step) => step.status === "running" && texts(step).length === var stepReasoning = (step) => step.blocks.filter((block) => block.kind === "reasoning").map((block) => block.text).join("\n").trim(); // src/client/cells.js @@ -1586,7 +1603,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa var import_react_dom = require("react-dom"); var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives"); var mdLabels = () => ({ code: { copyLabel: t("Copy"), copiedLabel: t("Copied") }, footnotes: t("Footnotes") }); -@@ -3780,7 +4186,7 @@ function Bubbles({ texts: texts2, roster, selfId, actions, streaming, sessionId, +@@ -3780,7 +4189,7 @@ function Bubbles({ texts: texts2, roster, selfId, actions, streaming, sessionId, actions.openSession(target); }; const reactions = useUi ? useUi((state) => msgKey ? state.reactions?.[msgKey] ?? null : null) : null; @@ -1595,7 +1612,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa "div", { className: "bt-astack", "data-lead": lead ? "" : void 0, onClickCapture: onClick }, texts2.map((text, index) => { -@@ -3788,21 +4194,21 @@ function Bubbles({ texts: texts2, roster, selfId, actions, streaming, sessionId, +@@ -3788,21 +4197,21 @@ function Bubbles({ texts: texts2, roster, selfId, actions, streaming, sessionId, const picked = key ? reactions?.[index] ?? [] : []; const last = index === texts2.length - 1; const live = streaming && last; @@ -1623,7 +1640,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa key: emoji, type: "button", className: "bt-react", -@@ -3815,7 +4221,7 @@ function Bubbles({ texts: texts2, roster, selfId, actions, streaming, sessionId, +@@ -3815,7 +4224,7 @@ function Bubbles({ texts: texts2, roster, selfId, actions, streaming, sessionId, } var REACTIONS = ["👍", "❤️", "😂", "😮", "😢", "🙏"]; function useDismiss(open, close, refs) { @@ -1632,7 +1649,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (!open) return void 0; const onDown = (event) => { if (refs.some((ref) => ref.current?.contains(event.target))) return; -@@ -3836,11 +4242,11 @@ function useDismiss(open, close, refs) { +@@ -3836,11 +4245,11 @@ function useDismiss(open, close, refs) { }, [open]); } function MessageTools({ text, name, sessionId, react, actions }) { @@ -1649,7 +1666,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const close = () => setPanel(null); useDismiss(panel !== null, close, [tools, pop]); const open = (kind, event) => { -@@ -3865,25 +4271,25 @@ function MessageTools({ text, name, sessionId, react, actions }) { +@@ -3865,25 +4274,25 @@ function MessageTools({ text, name, sessionId, react, actions }) { link.click(); setTimeout(() => URL.revokeObjectURL(url), 1e3); }; @@ -1684,7 +1701,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa "div", { ref: pop, className: "bt-menu", role: "menu", "aria-label": t("Message actions"), style: place }, item(t("Copy"), CopyIcon, copy2), -@@ -3892,11 +4298,12 @@ function MessageTools({ text, name, sessionId, react, actions }) { +@@ -3892,11 +4301,12 @@ function MessageTools({ text, name, sessionId, react, actions }) { ); } function ThinkingRow({ text }) { @@ -1700,7 +1717,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ); } function useFileMentions({ node, openFile, fileMentions, useTurnData }) { -@@ -3904,7 +4311,7 @@ function useFileMentions({ node, openFile, fileMentions, useTurnData }) { +@@ -3904,7 +4314,7 @@ function useFileMentions({ node, openFile, fileMentions, useTurnData }) { const location2 = node.location; const turn = location2?.kind === "turn" || location2?.kind === "step" ? location2.turn : void 0; const seq = node.data.finalNode?.seq; @@ -1709,7 +1726,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (typeof fileMentions !== "function" || turn?.status !== "closed" || seq === void 0) return void 0; return tail?.closing?.finalNode.seq === seq ? fileMentions({ turn, seq, openFile }) : void 0; }, [fileMentions, openFile, seq, tail, turn]); -@@ -3916,7 +4323,7 @@ function AssistantCell({ node, groupPart, sessionId, openFile, fileMentions, use +@@ -3916,7 +4326,7 @@ function AssistantCell({ node, groupPart, sessionId, openFile, fileMentions, use const contexts = useTurnContext(String(data.turn)); const mentions = useFileMentions({ node, openFile, fileMentions, useTurnData }); const hidden = Boolean(roster.roomsById[sessionId]) || isHiddenTurn(roster, sessionId, data.turn); @@ -1718,7 +1735,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const running = data.status === "running"; useLiveNote(actions, sessionId, `text:${node.key ?? data.seq}`, !hidden && running && texts2.length > 0 ? { text: true } : null); if (hidden) return null; -@@ -3924,13 +4331,13 @@ function AssistantCell({ node, groupPart, sessionId, openFile, fileMentions, use +@@ -3924,13 +4334,13 @@ function AssistantCell({ node, groupPart, sessionId, openFile, fileMentions, use const relays = relayPart(data, groupPart) ? relaysBefore(contexts, steps, data) : []; if (texts2.length === 0 && !reasoning && relays.length === 0) return null; const selfId = roster.byId[sessionId]?.id ?? sessionId; @@ -1735,7 +1752,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa key: relay.seq, prefix: source.role === "stopped" ? t("Stopped by") : source.role === "reply" ? t("Reply from") : t("Message from"), bots: [sender], -@@ -3940,61 +4347,131 @@ function AssistantCell({ node, groupPart, sessionId, openFile, fileMentions, use +@@ -3940,61 +4350,131 @@ function AssistantCell({ node, groupPart, sessionId, openFile, fileMentions, use whole: true }); }), @@ -1815,7 +1832,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa + handle, + isChief: member?.isChief ?? chat?.remote?.isChief + }); -+ const bot = chat ?? { id: `remote:${handle}`, name, color: member?.color, shape: member?.shape }; ++ const bot = chat ?? { id: remoteSeed(handle), name, color: member?.color, shape: member?.shape }; + return { bot, member, name, handle, chatId: chat ? currentPart(chat) : void 0 }; +} +function RemoteMessage({ source, text, roster, actions, sessionId, msgKey, useUi }) { @@ -1888,7 +1905,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ); } function MemoryEvent({ sessionId, scope, result, roster, actions, findBot }) { -@@ -4005,14 +4482,14 @@ function MemoryEvent({ sessionId, scope, result, roster, actions, findBot }) { +@@ -4005,14 +4485,14 @@ function MemoryEvent({ sessionId, scope, result, roster, actions, findBot }) { const target = other && other.id !== self?.id ? other : self; if (!target) return null; const label = team ? t("Team memory updated") : target === self ? t("Memory updated") : t("Updated {name}'s memory", { name: target.name }); @@ -1906,7 +1923,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa } var scrollParent = (node) => { for (let at = node.parentElement; at; at = at.parentElement) { -@@ -4021,11 +4498,11 @@ var scrollParent = (node) => { +@@ -4021,11 +4501,11 @@ var scrollParent = (node) => { return null; }; function EarlierParts({ sessionId, roster, actions }) { @@ -1923,7 +1940,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const load = (cursor) => { if (busy.current) return; busy.current = true; -@@ -4041,19 +4518,19 @@ function EarlierParts({ sessionId, roster, actions }) { +@@ -4041,19 +4521,19 @@ function EarlierParts({ sessionId, roster, actions }) { if (alive.current) setView((current) => ({ ...current, loading: false, failed: true })); }); }; @@ -1946,7 +1963,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const node = top.current; if (!node || view.loading || view.failed || !view.cursor || typeof IntersectionObserver !== "function") return void 0; const observer = new IntersectionObserver((entries2) => { -@@ -4069,35 +4546,35 @@ function EarlierParts({ sessionId, roster, actions }) { +@@ -4069,35 +4549,35 @@ function EarlierParts({ sessionId, roster, actions }) { if (item.kind === "bot" && last?.kind === "bot") last.texts.push(item.text); else rows.push(item.kind === "bot" ? { kind: "bot", texts: [item.text] } : item); } @@ -1993,7 +2010,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (selfId && other.id) actions.openExchange(selfId, other.id); }, whole: true }); } -@@ -4105,7 +4582,7 @@ function EarlierParts({ sessionId, roster, actions }) { +@@ -4105,7 +4585,7 @@ function EarlierParts({ sessionId, roster, actions }) { return null; } }), @@ -2002,7 +2019,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ); } function TriggerCell({ node, sessionId, useRoster, useSessions, useTurnClock, useUi, actions }) { -@@ -4116,7 +4593,7 @@ function TriggerCell({ node, sessionId, useRoster, useSessions, useTurnClock, us +@@ -4116,7 +4596,7 @@ function TriggerCell({ node, sessionId, useRoster, useSessions, useTurnClock, us const deferred = source.kind === "bot" && source.deferred === true; const relayed = isRelayContext(data); const turn = node.location?.turn?.turn ?? data.turn; @@ -2011,7 +2028,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (relayed) actions.noteTurnPeer(sessionId, turn, source.senderSessionId); }, [relayed, sessionId, turn, source.senderSessionId]); const order = useSessions((list) => { -@@ -4135,23 +4612,27 @@ function TriggerCell({ node, sessionId, useRoster, useSessions, useTurnClock, us +@@ -4135,23 +4615,27 @@ function TriggerCell({ node, sessionId, useRoster, useSessions, useTurnClock, us return first?.turn === turn && unseenSince(visit, first.start) ? clockLabel(first.start) : ""; }); const body = triggerBody(); @@ -2045,7 +2062,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const run = relayed ? commRunOf( order.split(",").filter(Boolean).map(Number), Object.fromEntries(peers.split(",").filter(Boolean).map((pair) => [Number(pair.slice(0, pair.indexOf(":"))), pair.slice(pair.indexOf(":") + 1)])), -@@ -4165,7 +4646,7 @@ function TriggerCell({ node, sessionId, useRoster, useSessions, useTurnClock, us +@@ -4165,7 +4649,7 @@ function TriggerCell({ node, sessionId, useRoster, useSessions, useTurnClock, us for (const peer of (run?.peerIds ?? []).map((id) => roster.byId[id]).filter(Boolean)) { if (!bots.some((bot) => bot.id === peer.id)) bots.push(peer); } @@ -2054,7 +2071,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa prefix: source.role === "stopped" ? t("Stopped by") : count > 1 ? t("{count} messages with", { count }) : t("Message from"), bots, onOpen: (bot) => actions.openExchange(selfId, bot.id), -@@ -4174,24 +4655,24 @@ function TriggerCell({ node, sessionId, useRoster, useSessions, useTurnClock, us +@@ -4174,24 +4658,24 @@ function TriggerCell({ node, sessionId, useRoster, useSessions, useTurnClock, us } if (source.kind === "schedule") { const prompts = reminderPrompts(contentText(data.content)); @@ -2084,7 +2101,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa actions.noteTurnTime(sessionId, turn, "first", start); }, [sessionId, turn, start]); const visit = useUi((state) => state.visits?.[sessionId]); -@@ -4199,7 +4680,7 @@ function TurnProcessCell({ node, sessionId, turnProcess, useRoster, useUi, useTu +@@ -4199,7 +4683,7 @@ function TurnProcessCell({ node, sessionId, turnProcess, useRoster, useUi, useTu const own = state[sessionId]?.[turn]; return !hidden && typeof visit?.seen === "number" && own?.first !== void 0 && own.first <= visit.seen && own.end !== void 0 && own.end > visit.seen && own.end <= visit.entered; }); @@ -2093,7 +2110,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa } function Nothing() { return null; -@@ -4236,7 +4717,7 @@ var unseenSince = (visit, time) => typeof visit?.seen === "number" && time > vis +@@ -4236,7 +4720,7 @@ var unseenSince = (visit, time) => typeof visit?.seen === "number" && time > vis function TurnTailCell({ turn: location2, sessionId, useTurnClock, useUi, actions }) { const turn = location2.turn; const time = location2.end.time; @@ -2102,7 +2119,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa actions.noteTurnTime(sessionId, turn, "end", time); }, [sessionId, turn, time]); const label = useTurnClock((state) => { -@@ -4250,15 +4731,15 @@ function TurnTailCell({ turn: location2, sessionId, useTurnClock, useUi, actions +@@ -4250,15 +4734,15 @@ function TurnTailCell({ turn: location2, sessionId, useTurnClock, useUi, actions return start !== void 0 && start > visit.seen && start <= visit.entered; }); if (!label && !fresh) return null; @@ -2123,7 +2140,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa } var PHRASES = { thinking: ["Thinking about it", "Weighing the options", "Working out a plan", "Sorting out the details"], -@@ -4324,15 +4805,15 @@ function workPhrase(tool) { +@@ -4324,15 +4808,15 @@ function workPhrase(tool) { } function useLiveNote(actions, sessionId, id, entry) { const key = entry ? JSON.stringify(entry) : ""; @@ -2142,7 +2159,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (done) return void 0; let stop = false; let timer; -@@ -4366,66 +4847,72 @@ function GroupReplies({ sessionId, callId, done, content, roster, actions, useUi +@@ -4366,66 +4850,72 @@ function GroupReplies({ sessionId, callId, done, content, roster, actions, useUi const speakerTool = useWork?.((work) => work.bots?.[live.speaking]?.tool) ?? void 0; if (replies.length === 0 && pending.length === 0) return null; const room2 = roster.roomsById[sessionId]; @@ -2234,7 +2251,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ); }) ); -@@ -4435,52 +4922,52 @@ var CARDS = /* @__PURE__ */ new Set(["ask_user", "request_secret"]); +@@ -4435,52 +4925,52 @@ var CARDS = /* @__PURE__ */ new Set(["ask_user", "request_secret"]); function callTree(root) { const calls = []; const seen = /* @__PURE__ */ new Set(); @@ -2303,7 +2320,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa case "create_group": case "update_group": case "delete_group": -@@ -4492,41 +4979,41 @@ function renderCall(call2, { sessionId, roster, actions, useUi, useWork, findBot +@@ -4492,41 +4982,41 @@ function renderCall(call2, { sessionId, roster, actions, useUi, useWork, findBot post_to_group: ["Posted in", "Couldn't post in the group"] }[tool]; if (!done) return null; @@ -2355,7 +2372,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa default: return null; } -@@ -4538,18 +5025,18 @@ function ToolCell({ node, sessionId, useRoster, useUi, useWork, actions }) { +@@ -4538,18 +5028,18 @@ function ToolCell({ node, sessionId, useRoster, useUi, useWork, actions }) { const hidden = isHiddenTurn(roster, sessionId, turn); const findBot = (nameOrId) => roster.byId[nameOrId] ?? roster.bots.find((bot) => bot.name.toLowerCase() === String(nameOrId ?? "").toLowerCase()); const calls = hidden ? [] : callTree(root); @@ -2379,7 +2396,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa var import_react_dom2 = require("react-dom"); var PHRASE_HOLD_MS = 800; var drawPhrase = (pool) => t(pool[Math.min(pool.length - 1, Math.floor(Math.random() * pool.length))]); -@@ -4560,8 +5047,8 @@ function elapsedLabel(ms) { +@@ -4560,8 +5050,8 @@ function elapsedLabel(ms) { return minutes % 60 === 0 ? `${hours}h` : `${hours}h ${minutes % 60}m`; } function useRunningHost(active) { @@ -2390,7 +2407,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (!active) { setHost(null); return void 0; -@@ -4585,9 +5072,9 @@ function useRunningHost(active) { +@@ -4585,9 +5075,9 @@ function useRunningHost(active) { return host; } function useRowEntrances(replacing) { @@ -2402,7 +2419,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const seen = /* @__PURE__ */ new WeakSet(); const started = Date.now(); let column = null; -@@ -4638,21 +5125,21 @@ function ActivityIndicator({ sessionId, useRoster, useSessionStatus, useLive, us +@@ -4638,21 +5128,21 @@ function ActivityIndicator({ sessionId, useRoster, useSessionStatus, useLive, us const pendingInput = useSession((snapshot) => Math.max(0, ...(snapshot?.pendingSubmissions ?? []).filter((sub) => sub.placement !== "queued").map((sub) => sub.time))); const bot = roster.byId[sessionId]; const room2 = roster.roomsById[sessionId]; @@ -2429,7 +2446,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (wantKey === null || shown?.key === wantKey) return void 0; const make = () => setShown({ key: wantKey, text: want.label ?? drawPhrase(want.pool), at: Date.now() }); const wait = shown ? shown.at + PHRASE_HOLD_MS - Date.now() : 0; -@@ -4663,12 +5150,12 @@ function ActivityIndicator({ sessionId, useRoster, useSessionStatus, useLive, us +@@ -4663,12 +5153,12 @@ function ActivityIndicator({ sessionId, useRoster, useSessionStatus, useLive, us const timer = setTimeout(make, wait); return () => clearTimeout(timer); }, [wantKey, shown]); @@ -2447,7 +2464,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (visible) { setPhase("in"); return void 0; -@@ -4680,7 +5167,7 @@ function ActivityIndicator({ sessionId, useRoster, useSessionStatus, useLive, us +@@ -4680,7 +5170,7 @@ function ActivityIndicator({ sessionId, useRoster, useSessionStatus, useLive, us }, 140); return () => clearTimeout(timer); }, [visible]); @@ -2456,7 +2473,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (phase !== null) return; endedAt.current = Date.now(); setShown(null); -@@ -4691,12 +5178,12 @@ function ActivityIndicator({ sessionId, useRoster, useSessionStatus, useLive, us +@@ -4691,12 +5181,12 @@ function ActivityIndicator({ sessionId, useRoster, useSessionStatus, useLive, us const delta = before - scroller.scrollTop; if (delta > 0) scroller.querySelector("[data-chat-flow]")?.animate([{ transform: `translateY(${-delta}px)` }, { transform: "none" }], { duration: 140, easing: "cubic-bezier(.23,1,.32,1)" }); }, [phase]); @@ -2472,7 +2489,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (phase !== "in") return void 0; const timer = setInterval(() => setNow(Date.now()), 1e4); return () => clearInterval(timer); -@@ -4704,18 +5191,18 @@ function ActivityIndicator({ sessionId, useRoster, useSessionStatus, useLive, us +@@ -4704,18 +5194,18 @@ function ActivityIndicator({ sessionId, useRoster, useSessionStatus, useLive, us const host = useRunningHost(running); if (!host || phase === null || !shown) return null; const elapsed = now - shown.at >= 6e4 ? elapsedLabel(now - shown.at) : null; @@ -2497,7 +2514,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ) ) ), host); -@@ -4732,32 +5219,32 @@ function showsDshRow(dsh, app) { +@@ -4732,32 +5222,32 @@ function showsDshRow(dsh, app) { } // src/client/sidebar.js @@ -2537,7 +2554,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const approval = useApproval(useWork, (entry) => entry.botId === id); const alert = roster.questions?.[part] !== void 0 || approval; const doing = useActivity((map) => map[id]); -@@ -4771,9 +5258,10 @@ function BotRow({ bot, roster, actions, useSessions, useSessionStatus, useSessio +@@ -4771,9 +5261,10 @@ function BotRow({ bot, roster, actions, useSessions, useSessionStatus, useSessio actions.openMenu({ kind: "bot", id, x: event.clientX, y: event.clientY }); }; const pinned = bot.pinned === true && !tile; @@ -2550,7 +2567,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa "button", { type: "button", -@@ -4785,12 +5273,12 @@ function BotRow({ bot, roster, actions, useSessions, useSessionStatus, useSessio +@@ -4785,12 +5276,12 @@ function BotRow({ bot, roster, actions, useSessions, useSessionStatus, useSessio onClick: () => actions.openSession(id), onContextMenu }, @@ -2567,7 +2584,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa "button", { type: "button", -@@ -4802,21 +5290,33 @@ function BotRow({ bot, roster, actions, useSessions, useSessionStatus, useSessio +@@ -4802,21 +5293,33 @@ function BotRow({ bot, roster, actions, useSessions, useSessionStatus, useSessio onClick: () => actions.openSession(id), onContextMenu }, @@ -2609,7 +2626,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const active = useSessionRetainInfo(id, (info) => (info?.retainedBy?.mainView ?? 0) > 0); const unread = useSessionStatus((map) => map.get(id)?.completionUnread === true) || marked && !active; useMarkedUnread(id, active, marked, actions); -@@ -4826,8 +5326,10 @@ function RoomRow({ room: room2, roster, actions, useSessions, useSessionStatus, +@@ -4826,8 +5329,10 @@ function RoomRow({ room: room2, roster, actions, useSessions, useSessionStatus, const workingLine = first ? workLine(first.name, members[0].tool, members.length - 1) : null; if (room2.hidden && !active) return null; const pinned = room2.pinned === true; @@ -2622,7 +2639,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa "button", { type: "button", -@@ -4841,20 +5343,20 @@ function RoomRow({ room: room2, roster, actions, useSessions, useSessionStatus, +@@ -4841,20 +5346,20 @@ function RoomRow({ room: room2, roster, actions, useSessions, useSessionStatus, actions.openMenu({ kind: "room", id, x: event.clientX, y: event.clientY }); } }, @@ -2651,7 +2668,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (!renaming) return; input.current?.focus(); input.current?.select(); -@@ -4875,10 +5377,10 @@ function AgentSessionRow({ session, agent, actions, renaming, index }) { +@@ -4875,10 +5380,10 @@ function AgentSessionRow({ session, agent, actions, renaming, index }) { } })(); }; @@ -2664,7 +2681,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ref: input, defaultValue: session.title, "aria-label": t("Rename"), -@@ -4891,7 +5393,7 @@ function AgentSessionRow({ session, agent, actions, renaming, index }) { +@@ -4891,7 +5396,7 @@ function AgentSessionRow({ session, agent, actions, renaming, index }) { }) ); } @@ -2673,7 +2690,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa "button", { type: "button", -@@ -4905,8 +5407,8 @@ function AgentSessionRow({ session, agent, actions, renaming, index }) { +@@ -4905,8 +5410,8 @@ function AgentSessionRow({ session, agent, actions, renaming, index }) { actions.openMenu({ kind: "agent-session", id: session.id, agentId: agent.id, x: event.clientX, y: event.clientY }); } }, @@ -2684,7 +2701,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ); } function AgentRow({ agent, actions, useSessions, useSessionStatus, useUi, wide, marked }) { -@@ -4941,10 +5443,10 @@ function AgentRow({ agent, actions, useSessions, useSessionStatus, useUi, wide, +@@ -4941,10 +5446,10 @@ function AgentRow({ agent, actions, useSessions, useSessionStatus, useUi, wide, const preview = sessions[0]?.title ?? t("New session"); const label = ["DSH Agent", running ? t("Working") : "", unread ? t("Unread activity") : ""].filter(Boolean).join(", "); const open = active && wide; @@ -2697,7 +2714,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa "button", { type: "button", -@@ -4959,30 +5461,30 @@ function AgentRow({ agent, actions, useSessions, useSessionStatus, useUi, wide, +@@ -4959,30 +5464,30 @@ function AgentRow({ agent, actions, useSessions, useSessionStatus, useUi, wide, actions.openMenu({ kind: "agent", id: agent.id, x: event.clientX, y: event.clientY }); } }, @@ -2739,7 +2756,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa key: session.id, session, agent, -@@ -4996,8 +5498,8 @@ function AgentRow({ agent, actions, useSessions, useSessionStatus, useUi, wide, +@@ -4996,8 +5501,8 @@ function AgentRow({ agent, actions, useSessions, useSessionStatus, useUi, wide, } var GLIDE = springEasing(1e3, 63); function useListGlide(listRef, layout, key) { @@ -2750,7 +2767,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const list = listRef.current; if (!list) return; const next = /* @__PURE__ */ new Map(); -@@ -5014,9 +5516,40 @@ function useListGlide(listRef, layout, key) { +@@ -5014,9 +5519,40 @@ function useListGlide(listRef, layout, key) { tops.current = { layout, map: next }; }, [layout, key]); } @@ -2792,7 +2809,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const roster = useRoster((value) => value); const markedUnread = useUi((value) => value.unread); props.useRegistry((value) => value); -@@ -5025,10 +5558,11 @@ function TeamSidebar(props) { +@@ -5025,10 +5561,11 @@ function TeamSidebar(props) { for (const id of list.ids) stamps[id] = list.byId[id]?.updatedAt ?? 0; return JSON.stringify(stamps); }); @@ -2806,7 +2823,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ...roster.rooms.map((room2) => ({ kind: "room", item: room2, at: stamps[room2.id] ?? room2.createdAt })), // The Agent sorts by its most recent Session, falling back to when it was added. ...(roster.agents ?? []).map((agent) => ({ kind: "agent", item: agent, at: Math.max(0, ...agent.sessions.map((id) => stamps[id] ?? 0)) || agent.createdAt })) -@@ -5036,6 +5570,10 @@ function TeamSidebar(props) { +@@ -5036,6 +5573,10 @@ function TeamSidebar(props) { return entries2.sort((a, b) => Number(Boolean(b.item.pinned)) - Number(Boolean(a.item.pinned)) || b.at - a.at); }, [roster, updated]); const main = roster.byId[roster.mainBotId]; @@ -2817,7 +2834,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa useListGlide(listRef, wide, order.map((entry) => entry.item.id).join(",")); useLandOnMainBot(roster, useSessions, actions); const rowProps = { roster, actions, useSessions, useUi, useSessionStatus: props.useSessionStatus, useSessionRetainInfo: props.useSessionRetainInfo, useActivity: props.useActivity, useWork: props.useWork, wide }; -@@ -5045,39 +5583,40 @@ function TeamSidebar(props) { +@@ -5045,39 +5586,40 @@ function TeamSidebar(props) { event.preventDefault(); actions.openPalette(event.key); }; @@ -2878,7 +2895,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (!open) return void 0; const onDown = (event) => { if (event.target instanceof Element && (event.target.closest(".bt-update-pop") || button.current?.contains(event.target))) return; -@@ -5120,10 +5659,10 @@ function UpdateButton({ useUpdate, actions }) { +@@ -5120,10 +5662,10 @@ function UpdateButton({ useUpdate, actions }) { const message = error || (phase === "failed" ? update.error : ""); const dshMessage = dshError || (dshPhase === "failed" ? dsh?.error : ""); const dshText = dshPhase === "installed" ? t("Restart DSH to start using DSH {version}.", { version: dsh.installed ?? dsh.verified }) : dsh?.available === false && dsh?.blockedBy ? t("Update DS Bot {version} first.", { version: dsh.blockedBy }) : t("You have {version}.", { version: dsh?.current }); @@ -2892,7 +2909,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ref: button, type: "button", className: "bt-round bt-update", -@@ -5133,21 +5672,21 @@ function UpdateButton({ useUpdate, actions }) { +@@ -5133,21 +5675,21 @@ function UpdateButton({ useUpdate, actions }) { "aria-haspopup": "dialog", "aria-expanded": open, onClick: toggle @@ -2924,7 +2941,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa type: "button", className: "bt-update-go", disabled: phase === "installing", -@@ -5155,17 +5694,17 @@ function UpdateButton({ useUpdate, actions }) { +@@ -5155,17 +5697,17 @@ function UpdateButton({ useUpdate, actions }) { }, phase === "installing" ? t("Updating…") : phase === "failed" ? t("Try again") : t("Update")) ) ) : null, @@ -2949,7 +2966,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa type: "button", className: "bt-update-go", disabled: dshPhase === "installing", -@@ -5177,60 +5716,24 @@ function UpdateButton({ useUpdate, actions }) { +@@ -5177,60 +5719,24 @@ function UpdateButton({ useUpdate, actions }) { ); } function ConnectPlugins({ wide, actions }) { @@ -3019,7 +3036,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ); } var openShellSettings = null; -@@ -5288,32 +5791,32 @@ function pressSettingsShortcut() { +@@ -5288,32 +5794,32 @@ function pressSettingsShortcut() { })); } function AccountLauncher({ seat = true, settingsOpen, openSettings, openOnboarding, actions }) { @@ -3060,7 +3077,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (!open) return void 0; const onDown = (event) => { if (event.target instanceof Element && (event.target.closest(".bt-account-menu") || button.current?.contains(event.target))) return; -@@ -5349,22 +5852,22 @@ function AccountLauncher({ seat = true, settingsOpen, openSettings, openOnboardi +@@ -5349,22 +5855,22 @@ function AccountLauncher({ seat = true, settingsOpen, openSettings, openOnboardi [t("Switch to classic Agent mode"), SwitchIcon, () => actions.setSurface("agent")] ] ]; @@ -3092,7 +3109,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa key: label, type: "button", role: "menuitem", -@@ -5372,7 +5875,7 @@ function AccountLauncher({ seat = true, settingsOpen, openSettings, openOnboardi +@@ -5372,7 +5878,7 @@ function AccountLauncher({ seat = true, settingsOpen, openSettings, openOnboardi setOpen(false); void Promise.resolve(run()).catch((error) => actions.reportError(error, "menu")); } @@ -3101,7 +3118,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa )) ), document.body) : null ); -@@ -5382,8 +5885,8 @@ function useLandOnMainBot(roster, useSessions, actions) { +@@ -5382,8 +5888,8 @@ function useLandOnMainBot(roster, useSessions, actions) { const row = Object.values(list.byId).find((session) => (session?.retainedBy?.mainView ?? 0) > 0); return row ? `${row.id}|${row.blank ? 1 : 0}` : ""; }); @@ -3112,7 +3129,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (!roster.ready || !roster.mainBotId) return void 0; const [id, blank3] = mainView.split("|"); const bot = id === "" ? void 0 : roster.byId[id]; -@@ -5404,16 +5907,16 @@ function useLandOnMainBot(roster, useSessions, actions) { +@@ -5404,16 +5910,16 @@ function useLandOnMainBot(roster, useSessions, actions) { function FooterAccount(props) { const seated = props.useUi((value) => value.accountSeated === true); if (seated) return null; @@ -3133,7 +3150,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ); } var RETURN_CSS = ` -@@ -5427,10 +5930,10 @@ var RETURN_CSS = ` +@@ -5427,10 +5933,10 @@ var RETURN_CSS = ` .bt-return-name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} `; function BrandMark({ size = 24 }) { @@ -3146,7 +3163,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa } var SIDEBAR_CSS = ` .bt-side-top .bt-brand{cursor:default;padding-left:6px} -@@ -5494,15 +5997,15 @@ var SIDEBAR_CSS = ` +@@ -5494,15 +6000,15 @@ var SIDEBAR_CSS = ` `; // src/client/header.js @@ -3166,7 +3183,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const left = seen + MEMORY_NOTICE_MS - Date.now(); setShown(seen > 0 && left > 0); if (seen === 0 || left <= 0) return void 0; -@@ -5512,16 +6015,16 @@ function useMemoryNotice(ids, useMemoryNews) { +@@ -5512,16 +6018,16 @@ function useMemoryNotice(ids, useMemoryNews) { return shown; } function MemoryNotice({ shown }) { @@ -3188,7 +3205,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ); } function HeaderPill({ sessionId, useRoster, useSessions, useSessionStatus, useSession, useActivity, useMemoryNews, useTurnClock, useUi, useLive, actions }) { -@@ -5537,7 +6040,7 @@ function HeaderPill({ sessionId, useRoster, useSessions, useSessionStatus, useSe +@@ -5537,7 +6043,7 @@ function HeaderPill({ sessionId, useRoster, useSessions, useSessionStatus, useSe const first = openingTurn(state[sessionId], firstTurns, hiddenTurns); return first && !unseenSince(visit, first.start) ? clockLabel(first.start) : ""; }); @@ -3197,7 +3214,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (opening) document.body.style.setProperty("--bt-first-sep", JSON.stringify(opening)); else document.body.style.removeProperty("--bt-first-sep"); return () => document.body.style.removeProperty("--bt-first-sep"); -@@ -5548,19 +6051,19 @@ function HeaderPill({ sessionId, useRoster, useSessions, useSessionStatus, useSe +@@ -5548,19 +6054,19 @@ function HeaderPill({ sessionId, useRoster, useSessions, useSessionStatus, useSe const bot = roster.byId[sessionId]; const room2 = roster.roomsById[sessionId]; const detailsOpen = useUi((state) => state.details === sessionId); @@ -3221,7 +3238,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (room2) document.body.dataset.btRoom = ""; else delete document.body.dataset.btRoom; return () => { -@@ -5568,10 +6071,10 @@ function HeaderPill({ sessionId, useRoster, useSessions, useSessionStatus, useSe +@@ -5568,10 +6074,10 @@ function HeaderPill({ sessionId, useRoster, useSessions, useSessionStatus, useSe }; }, [Boolean(room2)]); const memoryNotice = notice && changed !== null && roster.memory !== false; @@ -3234,7 +3251,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa "button", { type: "button", -@@ -5581,19 +6084,19 @@ function HeaderPill({ sessionId, useRoster, useSessions, useSessionStatus, useSe +@@ -5581,19 +6087,19 @@ function HeaderPill({ sessionId, useRoster, useSessions, useSessionStatus, useSe "aria-label": memoryNotice ? t("Memory updated. Open {name}'s memory", { name: roster.byId[changed]?.name ?? name }) : t("View conversation details"), onClick: () => memoryNotice ? actions.openMemory(changed) : actions.toggleDetails(sessionId) }, @@ -3263,7 +3280,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa let frame = 0; let watched = null; const observer = new MutationObserver(() => schedule()); -@@ -5658,18 +6161,18 @@ function NewMessagePills({ sessionId, useSessions, useUi, useTurnClock }) { +@@ -5658,18 +6164,18 @@ function NewMessagePills({ sessionId, useSessions, useUi, useTurnClock }) { }); const view = useTranscriptView(); const unpinned = view?.unpinned === true; @@ -3287,7 +3304,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const scroller = document.querySelector("[data-conversation-scroll]"); if (!scroller) return void 0; scroller.toggleAttribute("data-bt-news", showDown); -@@ -5677,16 +6180,16 @@ function NewMessagePills({ sessionId, useSessions, useUi, useTurnClock }) { +@@ -5677,16 +6183,16 @@ function NewMessagePills({ sessionId, useSessions, useUi, useTurnClock }) { }, [showDown]); if (!view || !showDown && !showUp) return null; const label = (count) => count === 1 ? t("1 new message") : count > 0 ? t("{count} new messages", { count }) : t("New messages"); @@ -3308,7 +3325,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa type: "button", className: "bt-news-x", "aria-label": t("Dismiss new messages"), -@@ -5694,7 +6197,7 @@ function NewMessagePills({ sessionId, useSessions, useUi, useTurnClock }) { +@@ -5694,7 +6200,7 @@ function NewMessagePills({ sessionId, useSessions, useUi, useTurnClock }) { event.stopPropagation(); onDismiss(); } @@ -3317,7 +3334,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ); const toLatest = () => { const scroller = document.querySelector("[data-conversation-scroll]"); -@@ -5704,8 +6207,8 @@ function NewMessagePills({ sessionId, useSessions, useUi, useTurnClock }) { +@@ -5704,8 +6210,8 @@ function NewMessagePills({ sessionId, useSessions, useUi, useTurnClock }) { }; const toDivider = () => document.querySelector("[data-conversation-scroll] .bt-new-sep")?.scrollIntoView({ block: "center", behavior: "smooth" }); return (0, import_react_dom4.createPortal)( @@ -3328,7 +3345,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa null, showUp ? pill("up", unread, { left: view.center, top: view.top }, toDivider, () => setAbove((state) => ({ ...state, dismissed: true }))) : null, showDown ? pill("down", fresh, { left: view.center, bottom: view.bottom }, toLatest, () => setRelease((state) => ({ ...state, dismissed: true }))) : null -@@ -5715,18 +6218,18 @@ function NewMessagePills({ sessionId, useSessions, useUi, useTurnClock }) { +@@ -5715,18 +6221,18 @@ function NewMessagePills({ sessionId, useSessions, useUi, useTurnClock }) { } // src/client/composer.js @@ -3354,7 +3371,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (!note) return void 0; const timer = setTimeout(() => setNote(""), 2600); return () => clearTimeout(timer); -@@ -5774,12 +6277,31 @@ function useDictation(onText) { +@@ -5774,12 +6280,31 @@ function useDictation(onText) { const stop = () => recognition.current?.stop(); return { listening, note, start, stop }; } @@ -3387,7 +3404,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (!quote || !inputActions) return; actions.clearQuote(quote.token); const excerpt = quote.text.replace(/\s+/g, " ").trim(); -@@ -5797,10 +6319,10 @@ function ComposerVoice({ sessionId, inputActions, actions, useUi }) { +@@ -5797,10 +6322,10 @@ function ComposerVoice({ sessionId, inputActions, actions, useUi }) { }, [quote?.token]); if (sessionId === void 0) return null; const pluginMic = actions.speech() !== void 0; @@ -3401,7 +3418,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa type: "button", className: "bt-mic", "aria-label": dictation.listening ? t("Stop dictation") : t("Dictate"), -@@ -5808,16 +6330,16 @@ function ComposerVoice({ sessionId, inputActions, actions, useUi }) { +@@ -5808,16 +6333,16 @@ function ComposerVoice({ sessionId, inputActions, actions, useUi }) { title: dictation.listening ? t("Stop dictation") : t("Dictate"), onMouseDown: keepComposerFocus, onClick: () => dictation.listening ? dictation.stop() : dictation.start() @@ -3422,7 +3439,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ); } function menuIcons(roster) { -@@ -5825,17 +6347,17 @@ function menuIcons(roster) { +@@ -5825,17 +6350,17 @@ function menuIcons(roster) { return (row) => { if (!row.bot) return PeopleIcon; const id = row.bot.id; @@ -3445,7 +3462,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (!open) return void 0; const onDown = (event) => { if (![button.current, menu.current].some((node) => node?.contains(event.target))) setOpen(null); -@@ -5876,10 +6398,10 @@ function ComposerPlus({ sessionId, inputActions, useRoster, actions }) { +@@ -5876,10 +6401,10 @@ function ComposerPlus({ sessionId, inputActions, useRoster, actions }) { actions.referenceChat(sessionId, inputActions?.captureInsertion?.()); }; const canReference = typeof inputActions?.captureInsertion === "function" && actions.canReferenceChat(); @@ -3459,7 +3476,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ref: button, type: "button", className: "bt-plus", -@@ -5889,8 +6411,8 @@ function ComposerPlus({ sessionId, inputActions, useRoster, actions }) { +@@ -5889,8 +6414,8 @@ function ComposerPlus({ sessionId, inputActions, useRoster, actions }) { "aria-expanded": Boolean(open), onMouseDown: keepComposerFocus, onClick: toggle @@ -3470,7 +3487,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa "div", { ref: menu, -@@ -5900,9 +6422,9 @@ function ComposerPlus({ sessionId, inputActions, useRoster, actions }) { +@@ -5900,9 +6425,9 @@ function ComposerPlus({ sessionId, inputActions, useRoster, actions }) { style: { left: shown.left, bottom: shown.bottom }, "data-leaving": leaving || void 0 }, @@ -3483,7 +3500,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ), document.body) : null ); } -@@ -5922,13 +6444,18 @@ var conversationOf = (roster, sessionId) => { +@@ -5922,13 +6447,18 @@ var conversationOf = (roster, sessionId) => { function mentionRows(roster, sessionId, query = "") { const here = conversationOf(roster, sessionId); if (here === null) return null; @@ -3503,7 +3520,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa return rows; } function chatRows(roster, list, sessionId) { -@@ -5959,14 +6486,26 @@ function atSources({ roster, sessions, launched, iconFor }) { +@@ -5959,14 +6489,26 @@ function atSources({ roster, sessions, launched, iconFor }) { order: -20, showGroupTitle: false, async candidates(session, { query }) { @@ -3532,7 +3549,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa }, onPick: ({ candidate }) => candidate.value ? { text: candidate.value } : void 0 }; -@@ -6122,15 +6661,15 @@ function turnDetector({ loud = 0.12, quiet = 0.06, quietMs = 1200 } = {}) { +@@ -6122,15 +6664,15 @@ function turnDetector({ loud = 0.12, quiet = 0.06, quietMs = 1200 } = {}) { } // src/client/overlay-hooks.js @@ -3551,7 +3568,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (escapeLayers.length === 0) window.addEventListener("keydown", onEscapeKey); escapeLayers.push(ref); return () => { -@@ -6140,8 +6679,8 @@ function useEscape(onClose) { +@@ -6140,8 +6682,8 @@ function useEscape(onClose) { }, []); } function usePaneLeft() { @@ -3562,7 +3579,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const column = document.querySelector('[class*="_footArea"]')?.parentElement; if (column) setPaneLeft(Math.round(column.getBoundingClientRect().right)); }, []); -@@ -6149,13 +6688,13 @@ function usePaneLeft() { +@@ -6149,13 +6691,13 @@ function usePaneLeft() { } // src/client/palette.js @@ -3580,7 +3597,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const wanted = query.trim().toLowerCase(); const close = () => { if (leaving) return; -@@ -6191,7 +6730,7 @@ function CommandPalette({ start, token, roster, actions, useSessions }) { +@@ -6191,7 +6733,7 @@ function CommandPalette({ start, token, roster, actions, useSessions }) { const people = [ ...roster.bots.filter((bot) => matchesName(bot.name, bot.role)).map((bot) => ({ key: bot.id, @@ -3589,7 +3606,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa title: bot.name, subtitle: [bot.role, isMainOf(roster, bot.id) ? t("Main Bot") : ""].filter(Boolean).join(" · "), badge: bot.hidden ? t("Hidden") : void 0, -@@ -6202,7 +6741,7 @@ function CommandPalette({ start, token, roster, actions, useSessions }) { +@@ -6202,7 +6744,7 @@ function CommandPalette({ start, token, roster, actions, useSessions }) { })), ...roster.rooms.filter((room2) => matchesName(room2.name)).map((room2) => ({ key: room2.id, @@ -3598,7 +3615,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa title: room2.name, subtitle: t("{count} members", { count: room2.members.length }), badge: room2.hidden ? t("Hidden") : void 0, -@@ -6215,7 +6754,7 @@ function CommandPalette({ start, token, roster, actions, useSessions }) { +@@ -6215,7 +6757,7 @@ function CommandPalette({ start, token, roster, actions, useSessions }) { if (people.length) sections.push([t("Bots"), people]); const agents = (roster.agents ?? []).filter(() => matchesName("DSH Agent")).map((agent) => ({ key: agent.id, @@ -3607,7 +3624,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa title: "DSH Agent", subtitle: t("Plain dsh session"), run: () => actions.openAgent(agent.id) -@@ -6228,16 +6767,16 @@ function CommandPalette({ start, token, roster, actions, useSessions }) { +@@ -6228,16 +6770,16 @@ function CommandPalette({ start, token, roster, actions, useSessions }) { key: `${id}:${turn}`, title: bot?.name ?? room2?.name ?? t("Bot"), subtitle: snippet, @@ -3629,7 +3646,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ].filter((command) => matchesName(command.title)); if (commands.length) sections.push([t("Commands"), commands]); const rows = sections.flatMap(([, items]) => items); -@@ -6247,7 +6786,7 @@ function CommandPalette({ start, token, roster, actions, useSessions }) { +@@ -6247,7 +6789,7 @@ function CommandPalette({ start, token, roster, actions, useSessions }) { ${shown}`, { axis: "y", ...QUICK_EDGES }); const listRef = glide.box; useFlip(listRef, shown, "[data-flip]"); @@ -3638,7 +3655,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa listRef.current?.querySelector("[aria-selected=true]")?.scrollIntoView({ block: "nearest" }); }, [active, query]); const choose = (row) => { -@@ -6256,18 +6795,18 @@ ${shown}`, { axis: "y", ...QUICK_EDGES }); +@@ -6256,18 +6798,18 @@ ${shown}`, { axis: "y", ...QUICK_EDGES }); close(); }; let index = -1; @@ -3663,7 +3680,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa autoFocus: true, value: query, placeholder: t("Search Bots, chats and messages"), -@@ -6293,29 +6832,29 @@ ${shown}`, { axis: "y", ...QUICK_EDGES }); +@@ -6293,29 +6835,29 @@ ${shown}`, { axis: "y", ...QUICK_EDGES }); } } }), @@ -3705,7 +3722,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa "button", { key: row.key, -@@ -6329,14 +6868,14 @@ ${shown}`, { axis: "y", ...QUICK_EDGES }); +@@ -6329,14 +6871,14 @@ ${shown}`, { axis: "y", ...QUICK_EDGES }); }, onClick: () => choose(row) }, @@ -3725,7 +3742,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ); }) )) -@@ -6346,7 +6885,7 @@ ${shown}`, { axis: "y", ...QUICK_EDGES }); +@@ -6346,7 +6888,7 @@ ${shown}`, { axis: "y", ...QUICK_EDGES }); } // src/client/model-menu.js @@ -3734,7 +3751,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa var import_react_dom6 = require("react-dom"); var MENU_EXIT_MS = 120; function groupModels(models) { -@@ -6376,8 +6915,8 @@ function modelOf(bot, roster) { +@@ -6376,8 +6918,8 @@ function modelOf(bot, roster) { } var effortCache = /* @__PURE__ */ new Map(); function useEfforts(served, ref, actions) { @@ -3745,7 +3762,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa setFetched(void 0); if (ref === void 0 || served?.reasoning !== void 0 || actions?.efforts === void 0) return void 0; let live = true; -@@ -6402,12 +6941,12 @@ function effortRows(reasoning) { +@@ -6402,12 +6944,12 @@ function effortRows(reasoning) { ]; } function EffortRows({ reasoning, current, onPick }) { @@ -3763,7 +3780,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa "button", { key: row.effort ?? "default", -@@ -6416,17 +6955,17 @@ function EffortRows({ reasoning, current, onPick }) { +@@ -6416,17 +6958,17 @@ function EffortRows({ reasoning, current, onPick }) { "aria-checked": row.effort === current, onClick: () => onPick(row.effort) }, @@ -3787,7 +3804,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa "button", { key: model.ref, -@@ -6436,20 +6975,20 @@ function ModelRows({ roster, current, onPick }) { +@@ -6436,20 +6978,20 @@ function ModelRows({ roster, current, onPick }) { title: model.ref, onClick: () => onPick(model.ref) }, @@ -3816,7 +3833,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (place === null) return void 0; const onDown = (event) => { if (menu.current?.contains(event.target) || button.current?.contains(event.target)) return; -@@ -6484,6 +7023,7 @@ function useAnchoredMenu() { +@@ -6484,6 +7026,7 @@ function useAnchoredMenu() { return { isOpen: place !== null, shownPlace, leaving, button, menu, open, close }; } function ModelMenu({ bot, roster, actions, run, wide = false, onPick, onPickEffort }) { @@ -3824,7 +3841,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const { isOpen, shownPlace, leaving, button, menu, open, close } = useAnchoredMenu(); const shown = modelOf(bot, roster); const current = bot.model ?? bot.appliedModel; -@@ -6511,10 +7051,10 @@ function ModelMenu({ bot, roster, actions, run, wide = false, onPick, onPickEffo +@@ -6511,10 +7054,10 @@ function ModelMenu({ bot, roster, actions, run, wide = false, onPick, onPickEffo else if (effort !== bot.effort) void run(actions.updateBot(bot.id, { effort: effort ?? null })); }; const models = roster.models ?? []; @@ -3838,7 +3855,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa "button", { ref: button, -@@ -6527,37 +7067,38 @@ function ModelMenu({ bot, roster, actions, run, wide = false, onPick, onPickEffo +@@ -6527,37 +7070,38 @@ function ModelMenu({ bot, roster, actions, run, wide = false, onPick, onPickEffo "data-missing": shown !== void 0 && !shown.served && models.length > 0 ? "" : void 0, onClick: toggle }, @@ -3890,7 +3907,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const models = roster.models ?? []; const lost = bot.model ? !models.some((model) => model.ref === bot.model) : false; if (bot.model && (!lost || models.length === 0)) return null; -@@ -6570,12 +7111,12 @@ function ModelChoiceDock({ sessionId, useRoster, actions }) { +@@ -6570,12 +7114,12 @@ function ModelChoiceDock({ sessionId, useRoster, actions }) { }).finally(() => setBusy("")); }; const text = lost ? t("{model} is not available anymore. Pick another model for me.", { model: bot.model.split("/").slice(1).join("/") }) : models.length === 0 ? t("Hi, I'm {name}. I don't have a model yet. How do you want to set one up?", { name: bot.name }) : t("Hi, I'm {name}. Pick a model for me first. You can change it later in my details.", { name: bot.name }); @@ -3907,7 +3924,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa key: model.ref, type: "button", className: "bt-model-chip", -@@ -6584,29 +7125,49 @@ function ModelChoiceDock({ sessionId, useRoster, actions }) { +@@ -6584,29 +7128,49 @@ function ModelChoiceDock({ sessionId, useRoster, actions }) { "aria-label": `${model.name} · ${provider}`, title: model.ref === roster.defaultModel ? `${model.ref} · ${t("Harness default")}` : model.ref, onClick: () => pick2(model.ref) @@ -3967,7 +3984,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ); } function setupChoices(actions) { -@@ -6620,14 +7181,14 @@ function DeepSeekSignIn({ useUi, actions }) { +@@ -6620,14 +7184,14 @@ function DeepSeekSignIn({ useUi, actions }) { const open = useUi((state) => state.signIn); const entry = open ? actions.signInEntry?.() : null; if (!entry) return null; @@ -3987,7 +4004,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ...ops, ...hooks, t: actions.accountT(), -@@ -6676,17 +7237,17 @@ var MODEL_CSS = ` +@@ -6676,17 +7240,17 @@ var MODEL_CSS = ` `; // src/client/folder-menu.js @@ -4012,7 +4029,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const fallback = group ? t("Each Bot's own folder") : t("Team workspace"); const toggle = () => { if (isOpen) { -@@ -6719,35 +7280,35 @@ function FolderMenu({ value, group = false, actions, run, onPick, wide = false } +@@ -6719,35 +7283,35 @@ function FolderMenu({ value, group = false, actions, run, onPick, wide = false } }; const known = info?.folders ?? []; const folders = value && !known.some((folder) => folder.path === value) ? [{ path: value, title: folderName(value) }, ...known] : known; @@ -4064,7 +4081,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa className: "bt-folder-input", autoFocus: true, value: draft, -@@ -6759,33 +7320,33 @@ function FolderMenu({ value, group = false, actions, run, onPick, wide = false } +@@ -6759,33 +7323,33 @@ function FolderMenu({ value, group = false, actions, run, onPick, wide = false } if (event.key === "Enter" && !event.nativeEvent.isComposing) typed(); } }), @@ -4114,7 +4131,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa "button", { ref: button, -@@ -6798,19 +7359,19 @@ function FolderMenu({ value, group = false, actions, run, onPick, wide = false } +@@ -6798,19 +7362,19 @@ function FolderMenu({ value, group = false, actions, run, onPick, wide = false } title: value, onClick: toggle }, @@ -4140,7 +4157,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ), document.body) : null ); } -@@ -6830,8 +7391,8 @@ var FOLDER_CSS = ` +@@ -6830,8 +7394,8 @@ var FOLDER_CSS = ` `; // src/client/schedules.js @@ -4151,7 +4168,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa var shared = null; var listeners = /* @__PURE__ */ new Set(); function publish(view) { -@@ -6839,8 +7400,8 @@ function publish(view) { +@@ -6839,8 +7403,8 @@ function publish(view) { for (const listener of listeners) listener(view); } function useSchedules(actions) { @@ -4162,7 +4179,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa listeners.add(setView); let stopped = false; let timer; -@@ -6848,7 +7409,7 @@ function useSchedules(actions) { +@@ -6848,7 +7412,7 @@ function useSchedules(actions) { if (!stopped) publish(next); }).catch(() => { }).finally(() => { @@ -4171,7 +4188,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa }); load(); return () => { -@@ -6898,9 +7459,9 @@ function timingText(task) { +@@ -6898,9 +7462,9 @@ function timingText(task) { } var nextText = (task) => task.status === "active" ? t("Next: {time}", { time: dayAndTime(Date.parse(task.scheduledAt)) }) : t("Finished"); function TaskRow({ task, actions, onView, showBot, roster }) { @@ -4184,7 +4201,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (!confirming) return void 0; const timer = setTimeout(() => setConfirming(false), 3e3); return () => clearTimeout(timer); -@@ -6914,19 +7475,19 @@ function TaskRow({ task, actions, onView, showBot, roster }) { +@@ -6914,19 +7478,19 @@ function TaskRow({ task, actions, onView, showBot, roster }) { void actions.scheduleDelete(task.sessionId, task.id).then(onView, () => setBusy(false)); }; const room2 = task.roomId ? roster.roomsById[task.roomId] : void 0; @@ -4212,7 +4229,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa type: "button", className: "bt-mini bt-task-remove", "data-confirm": confirming || void 0, -@@ -6934,7 +7495,7 @@ function TaskRow({ task, actions, onView, showBot, roster }) { +@@ -6934,7 +7498,7 @@ function TaskRow({ task, actions, onView, showBot, roster }) { "aria-label": confirming ? t("Click again to delete") : t("Delete task"), title: confirming ? void 0 : t("Delete task"), onClick: remove @@ -4221,7 +4238,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ); } var pad = (number) => String(number).padStart(2, "0"); -@@ -6946,17 +7507,17 @@ function inAnHour() { +@@ -6946,17 +7510,17 @@ function inAnHour() { var KINDS = [["at", "Once"], ["daily", "Every day"], ["weekly", "Every week"], ["every", "Repeat"]]; var UNITS = [["minutes", 1, "minutes"], ["hours", 60, "hours"], ["days", 1440, "days"]]; function TaskForm({ roster, actions, start, onDone, onCancel }) { @@ -4250,7 +4267,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const timing = () => { if (kind === "at") return { kind, at: new Date(at).toISOString() }; if (kind === "every") return { kind, minutes: Math.round(Number(count) * UNITS.find(([id]) => id === unit)[1]) }; -@@ -6981,101 +7542,101 @@ function TaskForm({ roster, actions, start, onDone, onCancel }) { +@@ -6981,101 +7545,101 @@ function TaskForm({ roster, actions, start, onDone, onCancel }) { }); }; const toggleDay = (day) => setWeekdays((days) => days.includes(day) ? days.filter((item) => item !== day) : [...days, day].sort()); @@ -4395,7 +4412,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa )) ]; } -@@ -7083,21 +7644,21 @@ function BotSchedules({ bot, roster, actions }) { +@@ -7083,21 +7647,21 @@ function BotSchedules({ bot, roster, actions }) { const view = useSchedules(actions); if (view === null) return null; const tasks = view.available ? view.tasks.filter((task) => task.botId === bot.id) : []; @@ -4425,7 +4442,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ] ) ); -@@ -7105,16 +7666,16 @@ function BotSchedules({ bot, roster, actions }) { +@@ -7105,16 +7669,16 @@ function BotSchedules({ bot, roster, actions }) { function ScheduleSummary({ bot, actions }) { const view = useSchedules(actions); const tasks = view?.available ? view.tasks.filter((task) => task.botId === bot.id && task.status === "active") : []; @@ -4448,7 +4465,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ))) ); } -@@ -7158,7 +7719,7 @@ var SCHEDULE_CSS = ` +@@ -7158,7 +7722,7 @@ var SCHEDULE_CSS = ` `; // src/client/capability-settings.js @@ -4457,7 +4474,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa var blank = () => ({ name: "", kind: "mcp", description: "", transport: "stdio", command: "", argsText: "", envText: "{}", url: "", headersText: "{}", content: "", items: [] }); var parseMap = (text, label) => { let value; -@@ -7172,11 +7733,11 @@ var parseMap = (text, label) => { +@@ -7172,11 +7736,11 @@ var parseMap = (text, label) => { }; var statusText = (status) => !status ? void 0 : status.state === "ready" ? t("Ready") : status.state === "needs-approval" ? t("Needs approval") : status.state === "error" ? t("Could not connect: {error}", { error: status.error ?? "" }) : t("Connecting…"); function Field({ label, value, onChange, multiline = false, disabled = false, placeholder }) { @@ -4472,7 +4489,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa className: multiline ? "bt-input bt-input-area" : "bt-input", value, disabled, -@@ -7188,7 +7749,7 @@ function Field({ label, value, onChange, multiline = false, disabled = false, pl +@@ -7188,7 +7752,7 @@ function Field({ label, value, onChange, multiline = false, disabled = false, pl ); } function CapabilityForm({ draft, setDraft, items, actions, onClose }) { @@ -4481,7 +4498,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const set = (key, value) => setDraft((previous) => ({ ...previous, [key]: value })); const submit = async (event) => { event.preventDefault(); -@@ -7219,95 +7780,95 @@ function CapabilityForm({ draft, setDraft, items, actions, onClose }) { +@@ -7219,95 +7783,95 @@ function CapabilityForm({ draft, setDraft, items, actions, onClose }) { } }; const available = items.filter((item) => item.name !== draft.name && ["mcp", "skill"].includes(item.kind)); @@ -4617,7 +4634,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (!confirming) return void 0; const timer = setTimeout(() => setConfirming(""), 4e3); return () => clearTimeout(timer); -@@ -7330,20 +7891,20 @@ function CapabilitiesPage({ roster, actions, run }) { +@@ -7330,20 +7894,20 @@ function CapabilitiesPage({ roster, actions, run }) { setConfirming(""); void run(actions.deleteCapability(item.name)); }; @@ -4645,7 +4662,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa key: item.name, item, confirming: confirming === item.name, -@@ -7359,10 +7920,10 @@ function BotCapabilitiesCard({ bot, roster, actions, run, Card: Card2, SwitchRow +@@ -7359,10 +7923,10 @@ function BotCapabilitiesCard({ bot, roster, actions, run, Card: Card2, SwitchRow const attached = new Set(bot.capabilities ?? []); const statuses = roster.capabilityStatuses?.[bot.id] ?? {}; if (items.length === 0) return null; @@ -4658,7 +4675,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa key: item.name, label: item.name, hint: attached.has(item.name) ? statusText(statuses[item.name]) : void 0, -@@ -7373,8 +7934,8 @@ function BotCapabilitiesCard({ bot, roster, actions, run, Card: Card2, SwitchRow +@@ -7373,8 +7937,8 @@ function BotCapabilitiesCard({ bot, roster, actions, run, Card: Card2, SwitchRow } // src/client/memory-panel.js @@ -4669,7 +4686,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa var MENU_EXIT_MS2 = 120; var counts = /* @__PURE__ */ new Map(); var countListeners = /* @__PURE__ */ new Set(); -@@ -7384,8 +7945,8 @@ function noteCount(botId, count) { +@@ -7384,8 +7948,8 @@ function noteCount(botId, count) { for (const listener of countListeners) listener(); } function useMemoryCount(botId, actions) { @@ -4680,7 +4697,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const listener = () => setCount(counts.get(botId) ?? null); countListeners.add(listener); listener(); -@@ -7404,16 +7965,16 @@ function useMemoryCount(botId, actions) { +@@ -7404,16 +7968,16 @@ function useMemoryCount(botId, actions) { function MemoryRow({ bot, actions }) { const count = useMemoryCount(bot.id, actions); const label = count === null ? t("Loading…") : count === 0 ? t("Nothing saved yet") : count === 1 ? t("1 entry") : t("{count} entries", { count }); @@ -4703,7 +4720,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ) ); } -@@ -7428,45 +7989,45 @@ function ago(at, now) { +@@ -7428,45 +7992,45 @@ function ago(at, now) { return format.format(-Math.round(hours / 24), "day"); } function Skeleton({ label }) { @@ -4768,7 +4785,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ) ); } -@@ -7478,9 +8039,9 @@ var groupEntries = (entries2) => entries2.reduce((groups, entry) => { +@@ -7478,9 +8042,9 @@ var groupEntries = (entries2) => entries2.reduce((groups, entry) => { return groups; }, []); function EntryRow({ entry, remove }) { @@ -4781,7 +4798,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (!confirming) return void 0; const timer = setTimeout(() => setConfirming(false), 3e3); return () => clearTimeout(timer); -@@ -7495,16 +8056,16 @@ function EntryRow({ entry, remove }) { +@@ -7495,16 +8059,16 @@ function EntryRow({ entry, remove }) { setBusy(true); if (!await remove(entry)) setBusy(false); }; @@ -4803,7 +4820,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa type: "button", className: "bt-mem-remove", "data-confirm": confirming || void 0, -@@ -7512,7 +8073,7 @@ function EntryRow({ entry, remove }) { +@@ -7512,7 +8076,7 @@ function EntryRow({ entry, remove }) { "aria-label": confirming ? t("Click again to remove") : t("Remove from memory"), title: confirming ? void 0 : t("Remove from memory"), onClick: click @@ -4812,7 +4829,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ); } function Entries({ bot, view, remove }) { -@@ -7521,25 +8082,25 @@ function Entries({ bot, view, remove }) { +@@ -7521,25 +8085,25 @@ function Entries({ bot, view, remove }) { { scope: "own", title: t("{name}'s memory", { name: bot.name }), size: view.sizes.own }, { scope: "team", title: t("Team memory"), hint: t("Every Bot reads it"), size: view.sizes.team } ]; @@ -4849,7 +4866,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa )) ); } -@@ -7551,52 +8112,52 @@ function changeLine(change) { +@@ -7551,52 +8115,52 @@ function changeLine(change) { return change.scope === "team" ? t("Added to team memory: {text}", { text }) : t("Added: {text}", { text }); } function Reply({ answer, dismiss }) { @@ -4921,7 +4938,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa alive.current = true; void load({ summarize: true }); return () => { -@@ -7607,8 +8168,8 @@ function useMemoryView(botId, actions) { +@@ -7607,8 +8171,8 @@ function useMemoryView(botId, actions) { return { view, error, load, show }; } function useNow(every) { @@ -4932,7 +4949,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const id = setInterval(() => setNow(Date.now()), every); return () => clearInterval(id); }, [every]); -@@ -7616,18 +8177,18 @@ function useNow(every) { +@@ -7616,18 +8180,18 @@ function useNow(every) { } function MemoryDialog({ bot, actions, leaving = false }) { const { view, error, load, show } = useMemoryView(bot.id, actions); @@ -4960,7 +4977,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (!menu) return void 0; const onDown = (event) => { if (menuRef.current?.contains(event.target) || dotsRef.current?.contains(event.target)) return; -@@ -7689,52 +8250,52 @@ function MemoryDialog({ bot, actions, leaving = false }) { +@@ -7689,52 +8253,52 @@ function MemoryDialog({ bot, actions, leaving = false }) { const when3 = view?.summary ? ago(view.summary.at, now) : null; const status = view === null ? "" : page === "entries" ? count === 1 ? t("1 entry") : t("{count} entries", { count }) : view.summarizing && view.summary ? t("Updating…") : view.summary && view.fresh ? when3 ? t("Updated {time}", { time: when3 }) : t("Updated just now") : ""; const busy = answer?.pending === true; @@ -5036,7 +5053,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ref: inputRef, className: "bt-mem-input", value: text, -@@ -7744,10 +8305,10 @@ function MemoryDialog({ bot, actions, leaving = false }) { +@@ -7744,10 +8308,10 @@ function MemoryDialog({ bot, actions, leaving = false }) { "aria-label": t("Ask about or update {name}'s memory", { name: bot.name }), onChange: (event) => setText(event.target.value) }), @@ -5049,7 +5066,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ) ) ) -@@ -7756,7 +8317,7 @@ function MemoryDialog({ bot, actions, leaving = false }) { +@@ -7756,7 +8320,7 @@ function MemoryDialog({ bot, actions, leaving = false }) { } // src/client/bot-settings.js @@ -5058,7 +5075,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa var capitalize = (word) => word[0].toUpperCase() + word.slice(1); async function avatarImage(file) { if (!/^image\/(png|jpeg|webp|gif)$/.test(file.type)) throw new Error(t("Choose a PNG, JPEG, WebP, or GIF image")); -@@ -7780,8 +8341,8 @@ async function avatarImage(file) { +@@ -7780,8 +8344,8 @@ async function avatarImage(file) { throw new Error(t("That image is too large")); } function InlineText({ label, value, placeholder, required = false, maxLength, multiline = false, onSave }) { @@ -5069,7 +5086,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const commit = () => { const next = draft?.trim(); setDraft(null); -@@ -7792,7 +8353,7 @@ function InlineText({ label, value, placeholder, required = false, maxLength, mu +@@ -7792,7 +8356,7 @@ function InlineText({ label, value, placeholder, required = false, maxLength, mu if (next === void 0 || next === (value ?? "") || required && next === "") return; void onSave(next); }; @@ -5078,7 +5095,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa className: multiline ? "bt-bs-input bt-bs-area" : "bt-bs-input", value: draft ?? value ?? "", placeholder, -@@ -7813,29 +8374,29 @@ function InlineText({ label, value, placeholder, required = false, maxLength, mu +@@ -7813,29 +8377,29 @@ function InlineText({ label, value, placeholder, required = false, maxLength, mu }); } function Row({ label, hint, stack = false, children }) { @@ -5116,7 +5133,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ); } function modelHint(bot, roster) { -@@ -7846,22 +8407,22 @@ function modelHint(bot, roster) { +@@ -7846,22 +8410,22 @@ function modelHint(bot, roster) { return t("Applies from the next message."); } function ShapeRing({ shape, size }) { @@ -5144,7 +5161,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa key: id, type: "button", className: "bt-shape", -@@ -7869,8 +8430,8 @@ function CharacterGrid({ look, hasPhoto = false, onShape, onColor }) { +@@ -7869,8 +8433,8 @@ function CharacterGrid({ look, hasPhoto = false, onShape, onColor }) { title: t(shapeOf(id).label), "aria-pressed": !hasPhoto && look.shape === id, onClick: () => onShape(id) @@ -5155,7 +5172,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa key: color, type: "button", className: "bt-color", -@@ -7878,19 +8439,19 @@ function CharacterGrid({ look, hasPhoto = false, onShape, onColor }) { +@@ -7878,19 +8442,19 @@ function CharacterGrid({ look, hasPhoto = false, onShape, onColor }) { title: t(capitalize(color)), "aria-pressed": look.color === color, onClick: () => onColor(color) @@ -5181,7 +5198,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const onDown = (event) => { if (!(event.target instanceof Element) || ref.current?.contains(event.target) || event.target.closest(".bt-avatar-trigger")) return; onClose(); -@@ -7908,7 +8469,7 @@ function AvatarEditor({ bot, actions, run, onClose }) { +@@ -7908,7 +8472,7 @@ function AvatarEditor({ bot, actions, run, onClose }) { window.removeEventListener("keydown", onKey, true); }; }, [onClose]); @@ -5190,7 +5207,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (tab !== "upload") return void 0; const onPaste = (event) => { const file = [...event.clipboardData?.files ?? []].find((item) => item.type.startsWith("image/")); -@@ -7922,32 +8483,32 @@ function AvatarEditor({ bot, actions, run, onClose }) { +@@ -7922,32 +8486,32 @@ function AvatarEditor({ bot, actions, run, onClose }) { }, [tab, bot]); const hasPhoto = Boolean(look.image); const glide = useGlide(tab); @@ -5231,7 +5248,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa "div", { className: "bt-dropzone", -@@ -7963,15 +8524,15 @@ function AvatarEditor({ bot, actions, run, onClose }) { +@@ -7963,15 +8527,15 @@ function AvatarEditor({ bot, actions, run, onClose }) { upload(event.dataTransfer.files?.[0]); } }, @@ -5253,7 +5270,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const file = event.target.files?.[0]; event.target.value = ""; upload(file); -@@ -7982,22 +8543,22 @@ function AvatarEditor({ bot, actions, run, onClose }) { +@@ -7982,22 +8546,22 @@ function AvatarEditor({ bot, actions, run, onClose }) { function MemoryCard({ bot, actions }) { const count = useMemoryCount(bot.id, actions); const label = count === null ? t("Loading…") : count === 0 ? t("Nothing saved yet") : count === 1 ? t("1 entry") : t("{count} entries", { count }); @@ -5284,7 +5301,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (!confirming) return void 0; const timer = setTimeout(() => setConfirming(false), 4e3); return () => clearTimeout(timer); -@@ -8011,112 +8572,113 @@ function BotSettingsForm({ bot, roster, actions, run }) { +@@ -8011,112 +8575,113 @@ function BotSettingsForm({ bot, roster, actions, run }) { if (confirming) void run(actions.deleteBot(bot.id)); else setConfirming(true); }; @@ -5445,7 +5462,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ) ) ); -@@ -8176,7 +8738,7 @@ function createPayload({ name, look, image, model, effort, brief }) { +@@ -8176,7 +8741,7 @@ function createPayload({ name, look, image, model, effort, brief }) { } // src/client/create-bot.js @@ -5454,7 +5471,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa var pick = (list) => list[Math.floor(Math.random() * list.length)]; var randomLook = (avoid = {}) => { const shapes = DEFAULT_SHAPES.filter((shape) => shape !== avoid.shape); -@@ -8184,17 +8746,17 @@ var randomLook = (avoid = {}) => { +@@ -8184,17 +8749,17 @@ var randomLook = (avoid = {}) => { return { shape: pick(shapes), color: pick(colors) }; }; function CreateBot({ roster, actions, start, brief = "", onBack, canGoBack, onCreated, inline = false }) { @@ -5482,7 +5499,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (inline) frame.current?.scrollIntoView({ block: "start", behavior: "smooth" }); }, []); const fallbackName = freshBotName(roster, t("New Bot")); -@@ -8219,25 +8781,25 @@ function CreateBot({ roster, actions, start, brief = "", onBack, canGoBack, onCr +@@ -8219,25 +8784,25 @@ function CreateBot({ roster, actions, start, brief = "", onBack, canGoBack, onCr avatarImage(file).then(setImage, (failure) => setError(failure?.message ?? String(failure))); }; const previewLook = image ? { ...look, image } : look; @@ -5516,7 +5533,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa look, hasPhoto: Boolean(image), onShape: (shape) => { -@@ -8249,27 +8811,27 @@ function CreateBot({ roster, actions, start, brief = "", onBack, canGoBack, onCr +@@ -8249,27 +8814,27 @@ function CreateBot({ roster, actions, start, brief = "", onBack, canGoBack, onCr setLook((current) => ({ ...current, color })); } }), @@ -5552,7 +5569,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa className: "bt-input", autoFocus: !inline, value: name, -@@ -8284,55 +8846,55 @@ function CreateBot({ roster, actions, start, brief = "", onBack, canGoBack, onCr +@@ -8284,55 +8849,55 @@ function CreateBot({ roster, actions, start, brief = "", onBack, canGoBack, onCr } } }), @@ -5635,7 +5652,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const close = () => actions.closeOverlay(); useEscape(close); const paneLeft = usePaneLeft(); -@@ -8418,14 +8980,14 @@ function NewChat({ mode, roster, actions }) { +@@ -8418,14 +8983,14 @@ function NewChat({ mode, roster, actions }) { }); const options = []; if (!group && picked.length === 0 && mode !== "search" && wanted === "") { @@ -5653,7 +5670,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa await actions.addAgent(); close(); }) }); -@@ -8434,19 +8996,19 @@ function NewChat({ mode, roster, actions }) { +@@ -8434,19 +8999,19 @@ function NewChat({ mode, roster, actions }) { for (const bot of matches) { options.push({ key: bot.id, bot, label: bot.name, hint: choosingAdmin ? t("Make admin") : group || picked.length > 0 ? t("Add to group chat") : void 0, run: () => openBot(bot) }); } @@ -5677,7 +5694,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa roster, actions, start: draft, -@@ -8467,33 +9029,33 @@ ${listed}`, { axis: "y", ...QUICK_EDGES }); +@@ -8467,33 +9032,33 @@ ${listed}`, { axis: "y", ...QUICK_EDGES }); if (picked.length > 0) setPicked((current) => current.slice(0, -1)); else if (admin !== null) setAdmin(null); }; @@ -5724,7 +5741,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ref: inputRef, autoFocus: true, value: query, -@@ -8517,20 +9079,20 @@ ${listed}`, { axis: "y", ...QUICK_EDGES }); +@@ -8517,20 +9082,20 @@ ${listed}`, { axis: "y", ...QUICK_EDGES }); } else if (event.key === "Backspace" && query === "") unpick(); } }), @@ -5750,7 +5767,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa "button", { key: option.key, -@@ -8542,21 +9104,21 @@ ${listed}`, { axis: "y", ...QUICK_EDGES }); +@@ -8542,21 +9107,21 @@ ${listed}`, { axis: "y", ...QUICK_EDGES }); onMouseEnter: () => setCursor(index), onClick: option.run }, @@ -5781,7 +5798,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa rows: 1, value: message, placeholder, -@@ -8569,7 +9131,7 @@ ${listed}`, { axis: "y", ...QUICK_EDGES }); +@@ -8569,7 +9134,7 @@ ${listed}`, { axis: "y", ...QUICK_EDGES }); } } }), @@ -5790,7 +5807,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ) ) ) -@@ -8577,18 +9139,18 @@ ${listed}`, { axis: "y", ...QUICK_EDGES }); +@@ -8577,18 +9142,18 @@ ${listed}`, { axis: "y", ...QUICK_EDGES }); } // src/client/exchange.js @@ -5813,7 +5830,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa let stop = false; let timer; let seen = null; -@@ -8612,48 +9174,48 @@ function ExchangeDialog({ pair, roster, actions }) { +@@ -8612,48 +9177,48 @@ function ExchangeDialog({ pair, roster, actions }) { const right = roster.byId[pair[1]]; let lastDay = 0; const paneLeft = usePaneLeft(); @@ -5878,7 +5895,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa var plainSpeech = (text) => stripHeader(text).replace(/```[\s\S]*?```/g, " ").replace(/\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/[*_`#>|~-]+/g, " ").replace(/\s+/g, " ").trim(); var VOICE_LABELS = { listening: "Listening", -@@ -8665,8 +9227,8 @@ var VOICE_LABELS = { +@@ -8665,8 +9230,8 @@ var VOICE_LABELS = { }; var CAPTURE_NOTES = { blocked: "Microphone access is blocked", unavailable: "Voice chat is not available in this browser", "too-long": "That was too long to recognize" }; function useMicLevel(enabled) { @@ -5889,7 +5906,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (!enabled || !navigator.mediaDevices?.getUserMedia) return void 0; let stream = null; let audio = null; -@@ -8716,18 +9278,18 @@ function VoiceMode({ sessionId, roster, actions, useSessions, useSessionStatus } +@@ -8716,18 +9281,18 @@ function VoiceMode({ sessionId, roster, actions, useSessions, useSessionStatus } const entry = Array.isArray(outline) ? outline[outline.length - 1] : void 0; return entry ? `${outline.length}|${entry.response ?? ""}` : "0|"; }); @@ -5919,7 +5936,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const paneLeft = usePaneLeft(); const close = () => { turn.current += 1; -@@ -8747,12 +9309,12 @@ function VoiceMode({ sessionId, roster, actions, useSessions, useSessionStatus } +@@ -8747,12 +9312,12 @@ function VoiceMode({ sessionId, roster, actions, useSessions, useSessionStatus } dictation.stop(); submit(text); }); @@ -5934,7 +5951,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (!speech) return void 0; let live = true; speechCatalog(speech).then(({ catalog: next, usable }) => { -@@ -8767,16 +9329,16 @@ function VoiceMode({ sessionId, roster, actions, useSessions, useSessionStatus } +@@ -8767,16 +9332,16 @@ function VoiceMode({ sessionId, roster, actions, useSessions, useSessionStatus } }; }, [speech]); const listen = phase === "listening"; @@ -5954,7 +5971,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (!speech || !catalog || !listen) return void 0; const own = turn.current; let capture = null; -@@ -8838,7 +9400,7 @@ function VoiceMode({ sessionId, roster, actions, useSessions, useSessionStatus } +@@ -8838,7 +9403,7 @@ function VoiceMode({ sessionId, roster, actions, useSessions, useSessionStatus } setServiceLevel(0); }; }, [speech, catalog, listen, cycle]); @@ -5963,7 +5980,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (phase !== "thinking" || running || waiting.current === null) return; const [count, text] = [lastReply.slice(0, lastReply.indexOf("|")), lastReply.slice(lastReply.indexOf("|") + 1)]; if (count === waiting.current || !text.trim()) return; -@@ -8858,7 +9420,7 @@ function VoiceMode({ sessionId, roster, actions, useSessions, useSessionStatus } +@@ -8858,7 +9423,7 @@ function VoiceMode({ sessionId, roster, actions, useSessions, useSessionStatus } window.speechSynthesis.cancel(); window.speechSynthesis.speak(utterance); }, [phase, running, lastReply]); @@ -5972,7 +5989,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const micLevel = useMicLevel(!speech && phase === "listening" && dictation.listening); const level = speech ? serviceLevel : micLevel; const markState = phase === "thinking" || phase === "transcribing" ? "thinking" : phase === "speaking" ? "sending" : "idle"; -@@ -8875,50 +9437,50 @@ function VoiceMode({ sessionId, roster, actions, useSessions, useSessionStatus } +@@ -8875,50 +9440,50 @@ function VoiceMode({ sessionId, roster, actions, useSessions, useSessionStatus } const openSetup = phase === "setup" ? actions.voiceSetup() : null; const caption = phase === "speaking" ? said : heard; const shownNote = note || dictation.note; @@ -6040,7 +6057,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa var ROOM_MODES = [ ["admin", "Admin leads", "Plain messages go to the admin, who calls others with @Name."], ["everyone", "Everyone", "Every member answers in turn, admin first."] -@@ -8929,15 +9491,26 @@ function roomSubtitle(room2, roster) { +@@ -8929,15 +9494,26 @@ function roomSubtitle(room2, roster) { return [t("{count} members", { count: room2.members.length }), admin ? t("Admin {name}", { name: admin.name }) : t("No admin")].join(" · "); } function GroupSettingsForm({ room: room2, roster, actions, run, onDeleted }) { @@ -6071,7 +6088,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const admin = roster.byId[room2.admin]; const mode = shownMode(room2, roster); const modeHint = ROOM_MODES.find(([id]) => id === mode)?.[2] ?? ""; -@@ -8955,41 +9528,41 @@ function GroupSettingsForm({ room: room2, roster, actions, run, onDeleted }) { +@@ -8955,41 +9531,41 @@ function GroupSettingsForm({ room: room2, roster, actions, run, onDeleted }) { if (done) onDeleted?.(); }); }; @@ -6127,7 +6144,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa key: id, type: "button", "aria-pressed": mode === id, -@@ -8999,86 +9572,128 @@ function GroupSettingsForm({ room: room2, roster, actions, run, onDeleted }) { +@@ -8999,86 +9575,128 @@ function GroupSettingsForm({ room: room2, roster, actions, run, onDeleted }) { if (mode !== id) void update({ mode: id }); } }, t(label)))), @@ -6290,7 +6307,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const toggle = (id) => setMembers((list) => list.includes(id) ? list.filter((item) => item !== id) : [...list, id]); const pickAdmin = (id) => { setAdmin(id); -@@ -9094,48 +9709,48 @@ function NewGroupForm({ roster, actions, run, onCreated, onCancel }) { +@@ -9094,48 +9712,48 @@ function NewGroupForm({ roster, actions, run, onCreated, onCancel }) { if (done && room2) onCreated(room2); else setBusy(false); }; @@ -6354,7 +6371,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ) ); } -@@ -9294,7 +9909,7 @@ function percent(part, whole) { +@@ -9294,7 +9912,7 @@ function percent(part, whole) { } // src/client/usage-page.js @@ -6363,7 +6380,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa var HEAT_WEEKS = 53; var TOP_SERIES = 7; var RING_R = 42; -@@ -9341,9 +9956,9 @@ function titleOf(range, bucket) { +@@ -9341,9 +9959,9 @@ function titleOf(range, bucket) { return dates({ year: "numeric", month: "long" }).format(bucket.start); } function useTween(value) { @@ -6376,7 +6393,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (reducedMotion() || typeof requestAnimationFrame !== "function") { from.current = value; setShown(value); -@@ -9363,9 +9978,9 @@ function useTween(value) { +@@ -9363,9 +9981,9 @@ function useTween(value) { }, [value]); return shown; } @@ -6388,7 +6405,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa className: "bt-us-model", "aria-hidden": true, style: { width: size, height: size, fontSize: Math.round(size * 0.42), background: `color-mix(in srgb, ${paint} 20%, var(--bt-card))`, color: `color-mix(in srgb, ${paint} 72%, var(--bt-ink))` } -@@ -9382,7 +9997,7 @@ function ownerFace(owner, roster) { +@@ -9382,7 +10000,7 @@ function ownerFace(owner, roster) { sub: owner.deleted ? t("Deleted") : role.toLowerCase() === name.toLowerCase() ? "" : role, paint: paintCss(colorOf(bot)), stops: GRADIENTS[colorOf(bot)], @@ -6397,7 +6414,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa }; } if (owner.kind === "room") { -@@ -9392,7 +10007,7 @@ function ownerFace(owner, roster) { +@@ -9392,7 +10010,7 @@ function ownerFace(owner, roster) { name: owner.name || t("Deleted group chat"), sub: owner.deleted ? t("Deleted") : t("Group chat"), paint: "var(--bt-us-rest)", @@ -6406,7 +6423,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa }; } if (owner.kind === "agent") { -@@ -9414,30 +10029,30 @@ function Tabs({ label, value, options, onChange, boxRef }) { +@@ -9414,30 +10032,30 @@ function Tabs({ label, value, options, onChange, boxRef }) { const names = options.map(([, text]) => text).join("\n"); const glide = useGlide(`${value} ${names}`, { box: boxRef }); @@ -6447,7 +6464,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa "span", { className: "bt-us-delta", title: compareText(range) }, `${change >= 0 ? "↑" : "↓"} ${Math.abs(change)}%` -@@ -9455,83 +10070,83 @@ function niceTop(max) { +@@ -9455,83 +10073,83 @@ function niceTop(max) { return [1, 1.5, 2, 2.5, 3, 4, 5, 6, 8, 10].map((scale) => scale * step).find((value) => value >= max); } function Chart({ range, columns, series, tipSeries, by, onBy, onPick }) { @@ -6558,7 +6575,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa onClick ? "button" : "div", { className: "bt-us-row", -@@ -9540,69 +10155,69 @@ function Row2({ kind, glyph, name, sub, sum, whole, fill: fill2, index, onClick +@@ -9540,69 +10158,69 @@ function Row2({ kind, glyph, name, sub, sum, whole, fill: fill2, index, onClick title: t("{tokens} tokens · {calls} calls", { tokens: full(sum.total), calls: full(sum.calls) }), ...onClick ? { type: "button", onClick } : {} }, @@ -6655,7 +6672,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const onDown = (event) => { if (![box, near].some((ref) => ref.current?.contains(event.target))) onClose(); }; -@@ -9628,17 +10243,17 @@ function SpanPicker({ span, today, oldest, active, near, onPick, onClose }) { +@@ -9628,17 +10246,17 @@ function SpanPicker({ span, today, oldest, active, near, onPick, onClose }) { const [from, to] = [anchor, key].sort(); onPick(from, to); }; @@ -6676,7 +6693,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa key, type: "button", className: "bt-us-day", -@@ -9654,15 +10269,15 @@ function SpanPicker({ span, today, oldest, active, near, onPick, onClose }) { +@@ -9654,15 +10272,15 @@ function SpanPicker({ span, today, oldest, active, near, onPick, onClose }) { "data-has": flag(active.has(key)), onMouseEnter: () => setHover(key), onClick: () => press(key) @@ -6698,7 +6715,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ); }; const shortcuts = [ -@@ -9671,30 +10286,30 @@ function SpanPicker({ span, today, oldest, active, near, onPick, onClose }) { +@@ -9671,30 +10289,30 @@ function SpanPicker({ span, today, oldest, active, near, onPick, onClose }) { ["year", t("This year"), `${today.slice(0, 4)}-01-01`, today] ]; const count = low === null ? 0 : Math.round((hourMs(high) - hourMs(low)) / DAY_MS) + 1; @@ -6737,7 +6754,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const today = hourMs(`${view.now.slice(0, 10)} 00`); const first = mondayOf(today) - (HEAT_WEEKS - 1) * 7 * DAY_MS; const from = dayKey(first); -@@ -9710,13 +10325,13 @@ function Heat({ view, keep, paint, index }) { +@@ -9710,13 +10328,13 @@ function Heat({ view, keep, paint, index }) { for (let week = 0; week < HEAT_WEEKS; week += 1) { const monday = first + week * 7 * DAY_MS; if (week > 0 && new Date(monday).getUTCMonth() !== new Date(monday - 7 * DAY_MS).getUTCMonth()) { @@ -6753,7 +6770,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa key: ms, className: "bt-us-cell", style: { "--w": week }, -@@ -9724,7 +10339,7 @@ function Heat({ view, keep, paint, index }) { +@@ -9724,7 +10342,7 @@ function Heat({ view, keep, paint, index }) { "data-future": ms > today ? "" : void 0, "data-today": ms === today ? "" : void 0, onMouseEnter: ms > today ? void 0 : () => setHover({ ms, week, day }) @@ -6762,7 +6779,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa } } const tipSide = hover ? { -@@ -9733,61 +10348,61 @@ function Heat({ view, keep, paint, index }) { +@@ -9733,61 +10351,61 @@ function Heat({ view, keep, paint, index }) { } : null; const active = [...daily.values()].filter((value) => value > 0).length; const summary = active === 1 ? t("Active on 1 day in the last year") : t("Active on {count} days in the last year", { count: active }); @@ -6850,7 +6867,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa let stopped = false; let timer; const resized = Promise.all((document.querySelector(".bt-settings")?.getAnimations() ?? []).filter((animation) => animation.transitionProperty === "width" || animation.transitionProperty === "height").map((animation) => animation.finished.catch(() => { -@@ -9797,7 +10412,7 @@ function UsagePage({ roster, actions, focusId }) { +@@ -9797,7 +10415,7 @@ function UsagePage({ roster, actions, focusId }) { const value = await actions.usage(); await resized; if (stopped) return; @@ -6859,7 +6876,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa setError(""); timer = setTimeout(load, value.scanning ? 3e3 : 2e4); } catch (failure) { -@@ -9814,7 +10429,7 @@ function UsagePage({ roster, actions, focusId }) { +@@ -9814,7 +10432,7 @@ function UsagePage({ roster, actions, focusId }) { }, []); const today = view?.now.slice(0, 10); const range = rangeId === "custom" && span ? { ...spanRange(span.from, span.to), ends: span.to >= today } : RANGES.find((item) => item.id === rangeId); @@ -6868,7 +6885,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (!view) return null; const faces2 = view.owners.map((owner) => ownerFace(owner, roster)); const lifetime = /* @__PURE__ */ new Map(); -@@ -9823,7 +10438,7 @@ function UsagePage({ roster, actions, focusId }) { +@@ -9823,7 +10441,7 @@ function UsagePage({ roster, actions, focusId }) { const models2 = view.models.map((model, at) => { const name = model.name || model.model || t("Unknown model"); const paint = modelPaint.get(at) ?? "var(--bt-us-rest)"; @@ -6877,7 +6894,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa }); const ownerAt2 = focus?.kind === "owner" ? view.owners.findIndex((owner) => owner.key === focus.key) : -1; const modelAt = focus?.kind === "model" ? view.models.findIndex((model) => model.key === focus.key) : -1; -@@ -9871,23 +10486,23 @@ function UsagePage({ roster, actions, focusId }) { +@@ -9871,23 +10489,23 @@ function UsagePage({ roster, actions, focusId }) { }); return { faces: faces2, models: models2, ownerAt: ownerAt2, modelAt, face: face2, keep: keep2, all: all2, part: part2, series: series2, tipSeries: tipSeries2, fillOf: fillOf2, columns: columns2, ranked: ranked2, isBot: isBot2, activeDays: activeDays2, oldest: oldest2 }; }, [view, rangeId, span, by, focusId, roster]); @@ -6909,7 +6926,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ) ); } -@@ -9912,7 +10527,7 @@ function UsagePage({ roster, actions, focusId }) { +@@ -9912,7 +10530,7 @@ function UsagePage({ roster, actions, focusId }) { ["custom", rangeId === "custom" && span ? spanText(span.from, span.to) : t("Custom"), CalendarIcon] ]; const whole = part.total.total; @@ -6918,7 +6935,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa key: `o${at}`, kind: faces[at].kind, glyph: faces[at].glyph(32), -@@ -9924,7 +10539,7 @@ function UsagePage({ roster, actions, focusId }) { +@@ -9924,7 +10542,7 @@ function UsagePage({ roster, actions, focusId }) { fill: face ? soften(face.paint, 0.8) : fillOf.get(`o${at}`) ?? soften(faces[at].paint), onClick: () => pick2({ kind: "owner", key: view.owners[at].key }) }); @@ -6927,7 +6944,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa key: `m${at}`, kind: "model", glyph: models[at].glyph(32), -@@ -9936,30 +10551,30 @@ function UsagePage({ roster, actions, focusId }) { +@@ -9936,30 +10554,30 @@ function UsagePage({ roster, actions, focusId }) { fill: face ? soften(face.paint, 0.8) : fillOf.get(`m${at}`) ?? soften(models[at].paint), onClick: () => pick2({ kind: "model", key: view.models[at].key }) }); @@ -6968,7 +6985,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ) ]; } else { -@@ -9968,39 +10583,39 @@ function UsagePage({ roster, actions, focusId }) { +@@ -9968,39 +10586,39 @@ function UsagePage({ roster, actions, focusId }) { const rest = owners.filter(([at]) => !isBot(at)); const usedModels = ranked(all.models); body = [ @@ -7022,7 +7039,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa body ); } -@@ -10157,12 +10772,12 @@ button.bt-us-row:hover{background:var(--bt-hover)} +@@ -10157,12 +10775,12 @@ button.bt-us-row:hover{background:var(--bt-hover)} `; // src/client/connectors.js @@ -7038,7 +7055,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ); var MARKS = { github: GitHubMark }; var ABOUT = { github: () => t("Repositories, issues, and pull requests") }; -@@ -10173,10 +10788,10 @@ function statusText2(connector) { +@@ -10173,10 +10791,10 @@ function statusText2(connector) { return ABOUT[connector.id]?.() ?? ""; } function ConnectorRow({ connector, actions }) { @@ -7053,7 +7070,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const run = (task, after) => { setBusy(true); setError(null); -@@ -10193,63 +10808,63 @@ function ConnectorRow({ connector, actions }) { +@@ -10193,63 +10811,63 @@ function ConnectorRow({ connector, actions }) { }; const on = connector.state !== "off"; const Mark = MARKS[connector.id]; @@ -7140,7 +7157,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ); function download(name, text) { const url = URL.createObjectURL(new Blob([text], { type: "text/plain;charset=utf-8" })); -@@ -10272,16 +10887,16 @@ function entrySummary(entry) { +@@ -10272,16 +10890,16 @@ function entrySummary(entry) { } var SOURCES = { turn: "Model call", host: "DS Bot", api: "DS Bot", browser: "This window" }; function FeedbackPage({ actions, run, Section, Row: Row3 }) { @@ -7162,7 +7179,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa void run(load()); }, []); const copyReport = () => run(fetchReport().then(async (next) => { -@@ -10289,7 +10904,7 @@ function FeedbackPage({ actions, run, Section, Row: Row3 }) { +@@ -10289,7 +10907,7 @@ function FeedbackPage({ actions, run, Section, Row: Row3 }) { setNote(copied ? t("Copied the diagnostics.") : t("Could not copy. Select the text below and copy it.")); if (!copied) setPreview(true); })); @@ -7171,7 +7188,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const openIssue = () => { const win = window.open("", "_blank"); if (win !== null) win.opener = null; -@@ -10317,59 +10932,59 @@ function FeedbackPage({ actions, run, Section, Row: Row3 }) { +@@ -10317,59 +10935,59 @@ function FeedbackPage({ actions, run, Section, Row: Row3 }) { const total = (report?.total ?? 0) + browserErrors.length; const shown = entries2.slice(0, 8); return [ @@ -7255,7 +7272,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ) ) ]; -@@ -10377,8 +10992,8 @@ function FeedbackPage({ actions, run, Section, Row: Row3 }) { +@@ -10377,8 +10995,8 @@ function FeedbackPage({ actions, run, Section, Row: Row3 }) { var TOAST_MS = 6e3; function ErrorToast({ useUi, actions }) { const toast = useUi((state) => state.toast); @@ -7266,7 +7283,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (!toast) return void 0; setLeaving(false); const fade = setTimeout(() => setLeaving(true), TOAST_MS); -@@ -10389,15 +11004,16 @@ function ErrorToast({ useUi, actions }) { +@@ -10389,15 +11007,16 @@ function ErrorToast({ useUi, actions }) { }; }, [toast?.token]); if (!toast) return null; @@ -7288,7 +7305,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ); } var FEEDBACK_CSS = ` -@@ -10428,7 +11044,7 @@ var FEEDBACK_CSS = ` +@@ -10428,7 +11047,7 @@ var FEEDBACK_CSS = ` `; // src/client/about.js @@ -7297,7 +7314,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa var CHEER_MS = 1800; var starCount = (count) => new Intl.NumberFormat(dateLocale(), { notation: "compact", maximumFractionDigits: 1 }).format(count); function botHint(update) { -@@ -10454,9 +11070,9 @@ function dshHint(dsh, shown) { +@@ -10454,9 +11073,9 @@ function dshHint(dsh, shown) { var linkProps = (href) => ({ className: "bt-soft bt-soft-sm bt-about-link", href, target: "_blank", rel: "noopener noreferrer" }); function AboutPage({ useUpdate, actions, run, Section, Row: Row3 }) { const update = useUpdate((value) => value) ?? {}; @@ -7310,7 +7327,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa let live = true; actions.repoStars().then((count) => { if (live && Number.isInteger(count)) setStars(count); -@@ -10466,8 +11082,8 @@ function AboutPage({ useUpdate, actions, run, Section, Row: Row3 }) { +@@ -10466,8 +11085,8 @@ function AboutPage({ useUpdate, actions, run, Section, Row: Row3 }) { live = false; }; }, []); @@ -7321,7 +7338,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (!cheer) return void 0; const timer = setTimeout(() => setCheer(false), CHEER_MS); return () => clearTimeout(timer); -@@ -10482,35 +11098,35 @@ function AboutPage({ useUpdate, actions, run, Section, Row: Row3 }) { +@@ -10482,35 +11101,35 @@ function AboutPage({ useUpdate, actions, run, Section, Row: Row3 }) { const phase = update.phase ?? "idle"; const dsh = update.dsh; const dshPhase = dsh?.phase ?? "idle"; @@ -7367,7 +7384,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa "a", { className: "bt-star", -@@ -10520,23 +11136,23 @@ function AboutPage({ useUpdate, actions, run, Section, Row: Row3 }) { +@@ -10520,23 +11139,23 @@ function AboutPage({ useUpdate, actions, run, Section, Row: Row3 }) { title: t("Star DS Bot on GitHub"), "aria-label": stars === null ? t("Star DS Bot on GitHub") : `${t("Star DS Bot on GitHub")} · ${t("{count} stars", { count: stars })}` }, @@ -7401,7 +7418,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ) ]; } -@@ -10569,54 +11185,54 @@ body:not([data-bt-motion=quiet]) .bt-about-mark:hover .bt-mark[data-live][data-s +@@ -10569,54 +11188,54 @@ body:not([data-bt-motion=quiet]) .bt-about-mark:hover .bt-mark[data-live][data-s `; // src/client/settings.js @@ -7472,7 +7489,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa key: ink, type: "button", className: "bt-swatch", -@@ -10626,21 +11242,21 @@ function GeneralPage({ roster, actions, run }) { +@@ -10626,21 +11245,21 @@ function GeneralPage({ roster, actions, run }) { style: { background: INKS[ink][0] }, onClick: () => run(actions.setPrefs({ accent: INKS[ink][0] })) })), @@ -7499,7 +7516,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ) ) ]; -@@ -10649,52 +11265,52 @@ function MainBotsSection({ roster, actions, run }) { +@@ -10649,52 +11268,52 @@ function MainBotsSection({ roster, actions, run }) { const mains = (roster.mainBotIds ?? []).map((id) => roster.byId[id]).filter(Boolean); const [first, ...others] = mains; const candidates = roster.bots.filter((bot) => !isMainOf(roster, bot.id)); @@ -7565,7 +7582,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa key: id, type: "button", "aria-pressed": motion === id, -@@ -10706,69 +11322,69 @@ function MotionSection({ roster, actions, run }) { +@@ -10706,69 +11325,69 @@ function MotionSection({ roster, actions, run }) { ); } function BotsPage({ roster, actions, run }) { @@ -7664,7 +7681,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa useEscape(close); const run = (promise) => Promise.resolve(promise).then(() => { setError(""); -@@ -10786,32 +11402,32 @@ function SettingsDialog({ page, id, roster, actions, useUpdate, leaving = false +@@ -10786,32 +11405,32 @@ function SettingsDialog({ page, id, roster, actions, useUpdate, leaving = false const nav = useGlide(shown, { axis: "y" }); const pageKey = `${shown}:${bot?.id ?? room2?.id ?? ""}`; const depth = bot || room2 ? 1 : 0; @@ -7708,7 +7725,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa type: "button", className: "bt-settings-item bt-settings-shell", title: t("Models, providers, and the rest of DeepSeek Harness"), -@@ -10819,64 +11435,65 @@ function SettingsDialog({ page, id, roster, actions, useUpdate, leaving = false +@@ -10819,64 +11438,65 @@ function SettingsDialog({ page, id, roster, actions, useUpdate, leaving = false close(); openHarnessSettings(); } @@ -7800,7 +7817,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const bot = roster.byId[menu.id]; const room2 = roster.roomsById[menu.id]; const entry = bot ?? room2; -@@ -10901,20 +11518,21 @@ function ContextMenu({ menu, roster, actions, leaving = false }) { +@@ -10901,20 +11521,21 @@ function ContextMenu({ menu, roster, actions, leaving = false }) { bot && !isMainBot ? [t("Make Main Bot"), StarLineIcon, () => actions.setMain(bot.id, true)] : null, bot && isMainBot && mainCount > 1 ? [t("Remove Main Bot role"), StarLineIcon, () => actions.setMain(bot.id, false)] : null ], @@ -7827,7 +7844,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa key: label, type: "button", role: "menuitem", -@@ -10923,14 +11541,14 @@ function ContextMenu({ menu, roster, actions, leaving = false }) { +@@ -10923,14 +11544,14 @@ function ContextMenu({ menu, roster, actions, leaving = false }) { actions.closeMenu(); void Promise.resolve(run()).catch((error) => actions.reportError(error, "menu")); } @@ -7846,7 +7863,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const box = ref.current?.getBoundingClientRect(); if (!box) return; setPlace({ left: Math.max(8, Math.min(menu.x, window.innerWidth - box.width - 8)), top: Math.max(8, Math.min(menu.y, window.innerHeight - box.height - 8)) }); -@@ -10952,14 +11570,14 @@ function AgentMenu({ menu, roster, actions, leaving }) { +@@ -10952,14 +11573,14 @@ function AgentMenu({ menu, roster, actions, leaving }) { [t("Archive"), TrashIcon, () => actions.archiveAgentSession(agent.id, menu.id), true] ] ]; @@ -7866,7 +7883,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa key: label, type: "button", role: "menuitem", -@@ -10968,7 +11586,7 @@ function AgentMenu({ menu, roster, actions, leaving }) { +@@ -10968,7 +11589,7 @@ function AgentMenu({ menu, roster, actions, leaving }) { actions.closeMenu(); void Promise.resolve(run()).catch((error) => console.warn("[ds-bot]", error)); } @@ -7875,7 +7892,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa )) ); } -@@ -11039,7 +11657,7 @@ function migrateStorage(storage = globalThis.localStorage) { +@@ -11039,7 +11660,7 @@ function migrateStorage(storage = globalThis.localStorage) { } // src/client/member-status.js @@ -7884,7 +7901,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa var NO_WORK = { bots: {}, rooms: {}, approvals: [] }; var ROWS_MAX = 4; function memberWork(room2, work = NO_WORK) { -@@ -11077,35 +11695,35 @@ function summaryText(busy) { +@@ -11077,35 +11698,35 @@ function summaryText(busy) { return { waiting, doing }; } function Face({ entry, size }) { @@ -7932,7 +7949,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ) ); } -@@ -11119,20 +11737,20 @@ function MemberDock({ sessionId, useRoster, useWork, actions }) { +@@ -11119,20 +11740,20 @@ function MemberDock({ sessionId, useRoster, useWork, actions }) { const groups = bot !== void 0 && !ownChat ? botGroupWork(bot.id, work, roster) : []; const shown = busy.length > 0 || groups.length > 0; const now = useNow2(shown); @@ -7958,7 +7975,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa key: item.room.id, bot, tool: item.tool, -@@ -11143,7 +11761,7 @@ function MemberDock({ sessionId, useRoster, useWork, actions }) { +@@ -11143,7 +11764,7 @@ function MemberDock({ sessionId, useRoster, useWork, actions }) { }))); } function useFlowColumn(ref, active, key) { @@ -7967,7 +7984,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const node = ref.current; const flow = document.querySelector("[data-chat-flow]"); if (!active || !node || !flow) return void 0; -@@ -11160,8 +11778,8 @@ function useFlowColumn(ref, active, key) { +@@ -11160,8 +11781,8 @@ function useFlowColumn(ref, active, key) { }, [active, key]); } function useNow2(active) { @@ -7978,7 +7995,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (!active) return void 0; const timer = setInterval(() => setNow(Date.now()), 3e4); return () => clearInterval(timer); -@@ -11170,38 +11788,38 @@ function useNow2(active) { +@@ -11170,38 +11791,38 @@ function useNow2(active) { } var since = (now, at) => typeof at === "number" && now - at >= 6e4 ? ` · ${elapsedLabel(now - at)}` : ""; function ApprovalCard({ approval, bot, actions }) { @@ -8032,7 +8049,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ); } if (entry.state !== "work") return null; -@@ -11210,14 +11828,14 @@ function MemberWork({ entry, bot, room: room2, roster, actions, now }) { +@@ -11210,14 +11831,14 @@ function MemberWork({ entry, bot, room: room2, roster, actions, now }) { setStopping(true); void Promise.resolve(actions.stopMemberWork(room2.id, bot.id)).catch((error) => actions.reportError(error, "stop")).finally(() => setStopping(false)); }; @@ -8051,7 +8068,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ) ); } -@@ -11264,7 +11882,7 @@ body[data-ds-dark-theme] .bt-mw-ask{color:#f0b84a} +@@ -11264,7 +11885,7 @@ body[data-ds-dark-theme] .bt-mw-ask{color:#f0b84a} `; // src/client/details-drawer.js @@ -8060,7 +8077,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa function transcriptOf(list, id, name, hiddenTurns = []) { const outline = list.projectionsBySession?.[id]?.values?.turnOutline ?? list.byId[id]?.projectionValues?.turnOutline; if (!Array.isArray(outline)) return ""; -@@ -11289,10 +11907,10 @@ var EMPTY_TABS = { ids: [], active: null }; +@@ -11289,10 +11910,10 @@ var EMPTY_TABS = { ids: [], active: null }; var PERMISSION_LABELS = { "read-only": "Read only", "workspace-write": "Workspace", "danger-full-access": "Full access" }; var DANGER = /* @__PURE__ */ new Set(["danger-full-access"]); function PermissionRow({ bot, roster, actions, run }) { @@ -8073,7 +8090,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (pending === saved) setPending(null); }, [pending, saved]); if (!catalog?.options?.length) return null; -@@ -11302,14 +11920,14 @@ function PermissionRow({ bot, roster, actions, run }) { +@@ -11302,14 +11923,14 @@ function PermissionRow({ bot, roster, actions, run }) { setPending(value); void run(actions.updateBot(bot.id, { permission: value })).catch(() => setPending(null)); }; @@ -8092,7 +8109,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa key: option.value, type: "button", "aria-pressed": option.value === current, -@@ -11323,42 +11941,42 @@ function PermissionRow({ bot, roster, actions, run }) { +@@ -11323,42 +11944,42 @@ function PermissionRow({ bot, roster, actions, run }) { function BrowserCards({ bot, sessionId, roster, actions, useBrowserTabs, useBrowserPages }) { const state = useBrowserTabs((map) => map[bot.id] ?? EMPTY_TABS); const pages = useBrowserPages((map) => map); @@ -8150,7 +8167,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa type: "button", className: "bt-bcard-x", "aria-label": t("Close tab"), -@@ -11367,71 +11985,152 @@ function BrowserCards({ bot, sessionId, roster, actions, useBrowserTabs, useBrow +@@ -11367,71 +11988,152 @@ function BrowserCards({ bot, sessionId, roster, actions, useBrowserTabs, useBrow void actions.browserCloseTab(bot.id, tabId); } }, "×"), @@ -8338,7 +8355,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ) : null ]; } -@@ -11439,15 +12138,15 @@ function DetailsDrawer({ sessionId, startRenaming, startTab, hasStrip, roster, a +@@ -11439,15 +12141,15 @@ function DetailsDrawer({ sessionId, startRenaming, startTab, hasStrip, roster, a const owner = roster.byId[sessionId]; const part = owner ? currentPart(owner) : sessionId; const isMainBot = isMainOf(roster, owner?.id); @@ -8361,7 +8378,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (last.current.view !== view) last.current = { view, tab, move: view === "settings" ? "in" : "out" }; else if (last.current.tab !== tab) last.current = { view, tab, move: "tab" }; const move = last.current.move; -@@ -11472,36 +12171,36 @@ function DetailsDrawer({ sessionId, startRenaming, startTab, hasStrip, roster, a +@@ -11472,36 +12174,36 @@ function DetailsDrawer({ sessionId, startRenaming, startTab, hasStrip, roster, a setTimeout(() => setCopied(false), 1600); }); }; @@ -8412,7 +8429,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ]; } const current = bot?.name ?? room2.name; -@@ -11515,12 +12214,12 @@ function DetailsDrawer({ sessionId, startRenaming, startTab, hasStrip, roster, a +@@ -11515,12 +12217,12 @@ function DetailsDrawer({ sessionId, startRenaming, startTab, hasStrip, roster, a const subtitle = bot ? bot.role : roomSubtitle(room2, roster); const tabs = bot ? ["details", "browser"] : ["details"]; return [ @@ -8429,7 +8446,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa className: "bt-drawer-name-input", autoFocus: true, value: draftName, -@@ -11535,55 +12234,55 @@ function DetailsDrawer({ sessionId, startRenaming, startTab, hasStrip, roster, a +@@ -11535,55 +12237,55 @@ function DetailsDrawer({ sessionId, startRenaming, startTab, hasStrip, roster, a setEditing(false); } } @@ -8508,7 +8525,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa var import_react_dom8 = require("react-dom"); var PANEL_LINGER_MS = 300; var WIDTH_KEY = STORAGE.browserWidth; -@@ -11598,10 +12297,10 @@ var EMPTY_TABS2 = { ids: [], active: null }; +@@ -11598,10 +12300,10 @@ var EMPTY_TABS2 = { ids: [], active: null }; var SPRING_LEAD2 = springEasing(576, 39.4); var SPRING_TRAIL2 = springEasing(144, 19.7); var icon = (paths) => function BrowserIcon() { @@ -8521,7 +8538,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ); }; var GlobeIcon = icon(["M10 3a7 7 0 1 0 0 14 7 7 0 0 0 0-14z", "M3 10h14", "M10 3c2 2.2 2.9 4.5 2.9 7S12 14.8 10 17c-2-2.2-2.9-4.5-2.9-7S8 5.2 10 3z"]); -@@ -11613,9 +12312,9 @@ var CloseIcon2 = icon(["M5.5 5.5l9 9M14.5 5.5l-9 9"]); +@@ -11613,9 +12315,9 @@ var CloseIcon2 = icon(["M5.5 5.5l9 9M14.5 5.5l-9 9"]); var PlusTabIcon = icon(["M10 5v10M5 10h10"]); var LockIcon2 = icon(["M6.5 9V7a3.5 3.5 0 0 1 7 0v2", "M5.5 9h9v7h-9z"]); function Favicon({ src, className }) { @@ -8534,7 +8551,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa } var savedWidth = () => { try { -@@ -11674,11 +12373,11 @@ var reducedMotion2 = () => typeof matchMedia === "function" && matchMedia("(pref +@@ -11674,11 +12376,11 @@ var reducedMotion2 = () => typeof matchMedia === "function" && matchMedia("(pref var tabLabel = (page) => page.title || shortAddress(page.url) || t("New tab"); function PanelStrip({ bot, subject, view, ids, active, reading, onView, actions, useBrowserPages }) { const pages = useBrowserPages((map) => map); @@ -8551,7 +8568,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const box = wrap.current; const capsule = ind.current; if (!box || !capsule) return void 0; -@@ -11707,7 +12406,7 @@ function PanelStrip({ bot, subject, view, ids, active, reading, onView, actions, +@@ -11707,7 +12409,7 @@ function PanelStrip({ bot, subject, view, ids, active, reading, onView, actions, trail.cancel(); }; }); @@ -8560,7 +8577,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const box = wrap.current; if (!box) return void 0; const observer = new ResizeObserver(() => { -@@ -11721,13 +12420,13 @@ function PanelStrip({ bot, subject, view, ids, active, reading, onView, actions, +@@ -11721,13 +12423,13 @@ function PanelStrip({ bot, subject, view, ids, active, reading, onView, actions, observer.observe(box); return () => observer.disconnect(); }, []); @@ -8577,7 +8594,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa "button", { type: "button", -@@ -11737,13 +12436,13 @@ function PanelStrip({ bot, subject, view, ids, active, reading, onView, actions, +@@ -11737,13 +12439,13 @@ function PanelStrip({ bot, subject, view, ids, active, reading, onView, actions, title: bot.name, onClick: () => onView("details") }, @@ -8594,7 +8611,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa "button", { key: tabId, -@@ -11764,9 +12463,9 @@ function PanelStrip({ bot, subject, view, ids, active, reading, onView, actions, +@@ -11764,9 +12466,9 @@ function PanelStrip({ bot, subject, view, ids, active, reading, onView, actions, } } }, @@ -8607,7 +8624,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa className: "bt-ptab-x", role: "button", "aria-label": t("Close tab"), -@@ -11778,20 +12477,20 @@ function PanelStrip({ bot, subject, view, ids, active, reading, onView, actions, +@@ -11778,20 +12480,20 @@ function PanelStrip({ bot, subject, view, ids, active, reading, onView, actions, }, "×") ); }), @@ -8635,7 +8652,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const element = frame.current; if (!element || leaving || !tabId) { void actions.browserShow(null); -@@ -11817,7 +12516,7 @@ function BrowserView({ bot, tabId, leaving, landed, actions, useBrowserPages }) +@@ -11817,7 +12519,7 @@ function BrowserView({ bot, tabId, leaving, landed, actions, useBrowserPages }) window.removeEventListener("resize", schedule); }; }, [tabId, showGuest, leaving]); @@ -8644,7 +8661,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa setDraft(null); if (landed && !page.url) input.current?.focus(); }, [tabId, landed]); -@@ -11833,27 +12532,27 @@ function BrowserView({ bot, tabId, leaving, landed, actions, useBrowserPages }) +@@ -11833,27 +12535,27 @@ function BrowserView({ bot, tabId, leaving, landed, actions, useBrowserPages }) }; const editing = draft !== null; const secure = /^https:/i.test(page.url); @@ -8681,7 +8698,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ref: input, type: "text", spellCheck: false, -@@ -11877,25 +12576,25 @@ function BrowserView({ bot, tabId, leaving, landed, actions, useBrowserPages }) +@@ -11877,25 +12579,25 @@ function BrowserView({ bot, tabId, leaving, landed, actions, useBrowserPages }) } }) ), @@ -8717,7 +8734,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ) : null ) ); -@@ -11907,41 +12606,41 @@ function BotPanel({ pane, leaving, roster, actions, useSessions, useSessionStatu +@@ -11907,41 +12609,41 @@ function BotPanel({ pane, leaving, roster, actions, useSessions, useSessionStatu const tabState = useBrowserTabs((map) => bot ? map[bot.id] ?? EMPTY_TABS2 : EMPTY_TABS2); const active = tabState.active; const page = useBrowserPages((pages) => (active ? pages[active] : null) ?? EMPTY_PAGE); @@ -8775,7 +8792,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa let timer; const onResize = () => { setResizing(true); -@@ -11955,28 +12654,28 @@ function BotPanel({ pane, leaving, roster, actions, useSessions, useSessionStatu +@@ -11955,28 +12657,28 @@ function BotPanel({ pane, leaving, roster, actions, useSessions, useSessionStatu window.removeEventListener("resize", onResize); }; }, []); @@ -8811,7 +8828,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (!page.readAt || Date.now() - page.readAt > READ_FLASH_MS) return void 0; setReading(true); const timer = setTimeout(() => setReading(false), READ_FLASH_MS); -@@ -12013,7 +12712,7 @@ function BotPanel({ pane, leaving, roster, actions, useSessions, useSessionStatu +@@ -12013,7 +12715,7 @@ function BotPanel({ pane, leaving, roster, actions, useSessions, useSessionStatu return actions[name](...args); }; } @@ -8820,7 +8837,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa const move = fly.current; if (!move) return; fly.current = null; -@@ -12096,7 +12795,7 @@ function BotPanel({ pane, leaving, roster, actions, useSessions, useSessionStatu +@@ -12096,7 +12798,7 @@ function BotPanel({ pane, leaving, roster, actions, useSessions, useSessionStatu for (const name of ends) grip.addEventListener(name, onEnd); }; const hasStrip = canBrowse && tabState.ids.length > 0; @@ -8829,7 +8846,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa "aside", { ref: root, -@@ -12113,15 +12812,15 @@ function BotPanel({ pane, leaving, roster, actions, useSessions, useSessionStatu +@@ -12113,15 +12815,15 @@ function BotPanel({ pane, leaving, roster, actions, useSessions, useSessionStatu if (event.target === event.currentTarget) setLanded(true); } }, @@ -8850,7 +8867,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa sessionId: pane.id, startRenaming: pane.renaming, startTab: pane.tab, -@@ -12218,7 +12917,7 @@ var BROWSER_CSS = ` +@@ -12218,7 +12920,7 @@ var BROWSER_CSS = ` `; // src/client/overlays.js @@ -8859,7 +8876,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa var import_react_dom9 = require("react-dom"); var DIALOG_EXIT_MS = 160; var MENU_EXIT_MS3 = 120; -@@ -12227,15 +12926,15 @@ function Overlays({ useRoster, useUi, useSessions, useSessionStatus, useActivity +@@ -12227,15 +12929,15 @@ function Overlays({ useRoster, useUi, useSessions, useSessionStatus, useActivity const ui2 = useUi((value) => value); useRegistry((value) => value); const current = useSessions((list) => Object.values(list.byId).find((session) => (session?.retainedBy?.mainView ?? 0) > 0)?.id ?? null); @@ -8879,7 +8896,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (open) { setKept(open); return void 0; -@@ -12247,17 +12946,17 @@ function Overlays({ useRoster, useUi, useSessions, useSessionStatus, useActivity +@@ -12247,17 +12949,17 @@ function Overlays({ useRoster, useUi, useSessions, useSessionStatus, useActivity const [settings, settingsLeaving] = useLinger(ui2.settings, DIALOG_EXIT_MS); const [memory, memoryLeaving] = useLinger(ui2.memory && roster.byId[ui2.memory] ? ui2.memory : null, DIALOG_EXIT_MS); const [menu, menuLeaving] = useLinger(ui2.menu, MENU_EXIT_MS3); @@ -8907,7 +8924,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ); } -@@ -13105,7 +13804,7 @@ function writeMode(mode, storage = globalThis.localStorage) { +@@ -13105,7 +13807,7 @@ function writeMode(mode, storage = globalThis.localStorage) { } // src/client/user-message.js @@ -8916,7 +8933,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa var ui = __toESM(require("@deepseek-ai/dsh-client-ui-primitives"), 1); var canDrawUserMessages = () => typeof ui.projectUserText === "function"; function contentParts(content) { -@@ -13121,8 +13820,25 @@ function contentParts(content) { +@@ -13121,8 +13823,25 @@ function contentParts(content) { } function mentionableBots(roster, sessionId) { const room2 = roster.roomsById[sessionId]; @@ -8944,7 +8961,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa } function userTextParts(text, bots, referenceLabels = []) { const parts = []; -@@ -13137,38 +13853,38 @@ function userTextParts(text, bots, referenceLabels = []) { +@@ -13137,38 +13856,38 @@ function userTextParts(text, bots, referenceLabels = []) { return parts; } function UserText({ text, bots, referenceLabels, skillNames, references, actions }) { @@ -8994,7 +9011,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa } var clock3 = (time) => typeof time === "number" ? new Date(time).toLocaleTimeString(dateLocale(), { hour: "numeric", minute: "2-digit" }) : void 0; function UserMessageCell({ node, sessionId, useRoster, actions, renderMessageImages, openFile, openSkill }) { -@@ -13178,26 +13894,41 @@ function UserMessageCell({ node, sessionId, useRoster, actions, renderMessageIma +@@ -13178,26 +13897,41 @@ function UserMessageCell({ node, sessionId, useRoster, actions, renderMessageIma const referenceLabels = data.referenceLabels ?? []; const references = openFile && openSkill ? { openFile, openSkill } : void 0; const steering = node.kind === "steering"; @@ -9045,7 +9062,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa .bt-ustack{min-width:0;max-width:min(calc(var(--dsh-chat-content-width,748px) * .702),82%);display:flex;flex-direction:column;align-items:flex-end;gap:8px} .bt-ubody{max-width:100%;box-sizing:border-box;background:var(--bt-user);color:var(--bt-user-ink);border-radius:18px;padding:7px 12px;font-size:var(--dsh-content-font-size,14px);line-height:calc(var(--dsh-content-font-size,14px) + 8px);white-space:pre-wrap;word-break:break-word} .bt-ubody a{color:inherit} -@@ -13250,6 +13981,14 @@ function noticeApprovals({ work, roster, shownSession, openSession, since: since +@@ -13250,6 +13984,14 @@ function noticeApprovals({ work, roster, shownSession, openSession, since: since // src/client/index.js var inject = ["sessions", "uiWorkspace", "uiConversation", "slots", "theme", "layout"]; function apply(ctx) { @@ -9060,7 +9077,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ctx.effect(() => installLocale(ctx.get("locale")), "ds-bot: interface copy"); const roster = createSource(EMPTY_ROSTER); migrateStorage(); -@@ -13501,7 +14240,7 @@ function apply(ctx) { +@@ -13501,7 +14243,7 @@ function apply(ctx) { }; render(); const offRoster = roster.subscribe(render); @@ -9069,7 +9086,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa return () => { offRoster(); offRegistry(); -@@ -13520,7 +14259,7 @@ function apply(ctx) { +@@ -13520,7 +14262,7 @@ function apply(ctx) { const accent = isHex(prefs.accent) ? prefs.accent : void 0; const motion = prefs.motion === "quiet" || prefs.motion === "lively" ? prefs.motion : "normal"; document.body.dataset.btMotion = motion; @@ -9078,7 +9095,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa if (next === key) return; key = next; style.textContent = themeCss(themes[id], accent); -@@ -13536,7 +14275,7 @@ function apply(ctx) { +@@ -13536,7 +14278,7 @@ function apply(ctx) { }; render(); const offRoster = roster.subscribe(render); @@ -9087,7 +9104,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa return () => { offRoster(); offRegistry(); -@@ -13846,6 +14585,8 @@ function apply(ctx) { +@@ -13846,6 +14588,8 @@ function apply(ctx) { noteClientError(where, error); ui2.set((state) => ({ ...state, toast: { text: error?.message ?? String(error), token: Date.now() } })); }, @@ -9096,7 +9113,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa dismissToast: (token) => ui2.set((state) => state.toast && (token === void 0 || state.toast.token === token) ? { ...state, toast: null } : state) }; if (browsers) { -@@ -13890,6 +14631,7 @@ function apply(ctx) { +@@ -13890,6 +14634,7 @@ function apply(ctx) { actions.closePanel = () => { ui2.set((state) => state.details ? { ...state, details: null, view: "details", renaming: false, tab: null } : state); }; @@ -9104,7 +9121,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa actions.signInEntry = () => ctx.slots.entries?.("settings.models.sign-in")?.[0] ?? null; actions.canSignIn = () => Boolean(actions.signInEntry()); actions.signIn = () => ui2.set((state) => ({ ...state, signIn: (state.signIn ?? 0) + 1 })); -@@ -13964,7 +14706,7 @@ function apply(ctx) { +@@ -13964,7 +14709,7 @@ function apply(ctx) { }); const browserPages = browsers?.pages ?? createSource({}); const browserTabs = browsers?.tabs ?? createSource({}); @@ -9113,7 +9130,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ctx.effect(() => noticeApprovals({ work, roster, -@@ -14024,6 +14766,7 @@ function apply(ctx) { +@@ -14024,6 +14769,7 @@ function apply(ctx) { installCompiledStyles(SCHEDULE_CSS, "schedules"), installCompiledStyles(FEEDBACK_CSS, "feedback"), installCompiledStyles(ABOUT_CSS, "about"), @@ -9121,7 +9138,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa installCompiledStyles(MEMBER_CSS, "members"), installCompiledStyles(USER_MESSAGE_CSS, "user-messages"), browsers ? installCompiledStyles(BROWSER_CSS, "browser") : null, -@@ -14039,7 +14782,8 @@ function apply(ctx) { +@@ -14039,7 +14785,8 @@ function apply(ctx) { shadow("settings.launcher", { priority: -10, inject: face }, AccountLauncher), shadow("shell.overlay", { id: "bot", order: 50, inject: face }, Overlays), shadow("shell.overlay", { id: "bot-signin", order: 55, inject: face }, DeepSeekSignIn), @@ -9131,7 +9148,7 @@ index f46d64171400ccaa6c00e4577c308297a70a0b4d..014f587a802aef70618cdc4bf66cdafa ]; const installConversation = () => [ installCompiledStyles(CONVERSATION_CSS, "conversation"), -@@ -14066,6 +14810,7 @@ function apply(ctx) { +@@ -14066,6 +14813,7 @@ function apply(ctx) { shadow("conversation.composer.dock", { id: "activity", order: 0, priority: -10 }, Nothing), shadow("conversation.composer.dock", { id: "usage", order: 1, priority: -10 }, Nothing), shadow("conversation.input.left", { id: "bot-plus", order: -100, inject: face }, ComposerPlus), diff --git a/patches/ds-bot/topics.json b/patches/ds-bot/topics.json index 8a4c80313..9f4cfb13b 100644 --- a/patches/ds-bot/topics.json +++ b/patches/ds-bot/topics.json @@ -50,6 +50,8 @@ "src/host/remote-chat.js", "src/host/channel.js", "src/host/delivery.js", + "src/client/characters.js", + "src/client/mark.js", "test/remote-chat.test.js", "test/inbound-ui.test.js" ],