Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/1626-activity-row-running-host.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
After a message is sent on DeepSeek Harness 0.2.1, the Bot avatar and shimmering status line under the transcript show again: the activity row finds `RunningStatus` even when it sits inside the `conversation.chat.flow` slot ([#1626](https://github.com/ThinkFlowLab/FlyBot/issues/1626)).
16 changes: 12 additions & 4 deletions core/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -7115,6 +7115,16 @@ function remoteApprovalPhrase(remote, { tool, inputAt = 0 } = {}) {
return { key: "remote-approval", label: REMOTE_APPROVAL_LABEL, state: "thinking" };
}

// src/client/activity-row-host.js
var RUNNING_HOST = "[data-chat-flow] [data-chat-running]";
var TAIL_HOST = "tail";
function findRunningHostIn(root) {
if (!root || typeof root.querySelector !== "function") return null;
if (typeof root.matches === "function" && root.matches("[data-chat-running]")) return root;
if (typeof Element !== "undefined" && root instanceof Element) return root.querySelector("[data-chat-running]");
return root.querySelector(RUNNING_HOST);
}

// src/client/activity-row.js
var import_react10 = require("react");
var import_react_dom2 = require("react-dom");
Expand All @@ -7126,8 +7136,6 @@ function elapsedLabel(ms) {
const hours = Math.floor(minutes / 60);
return minutes % 60 === 0 ? `${hours}h` : `${hours}h ${minutes % 60}m`;
}
var RUNNING_HOST = "[data-chat-flow] > [data-chat-running]";
var TAIL_HOST = "tail";
var REMOTE_GRACE_MS = 1500;
function useAwaitingReply(roster, sessionId) {
const at = roster?.remoteAwaiting?.[sessionId];
Expand All @@ -7141,10 +7149,10 @@ function useAwaitingReply(roster, sessionId) {
return shown;
}
function findHost(where) {
if (where !== TAIL_HOST) return document.querySelector(where);
if (where !== TAIL_HOST) return findRunningHostIn(document);
const flow = document.querySelector("[data-conversation-scroll] [data-chat-flow]") ?? document.querySelector("[data-chat-flow]");
if (!flow) return null;
const running = flow.querySelector(":scope > [data-chat-running]");
const running = findRunningHostIn(flow);
if (running) return running;
const tails = flow.querySelectorAll(":scope > * [data-turn-tail], :scope > [data-turn-tail]");
return tails.length ? tails[tails.length - 1] : null;
Expand Down
17 changes: 17 additions & 0 deletions core/src/client/activity-row-host.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Pure host helpers for the activity row (#1626). Kept free of React so unit tests
// import them without a DOM renderer.

// DSH 0.2.0 put RunningStatus as a direct child of [data-chat-flow]. DSH 0.2.1+
// appends it inside the conversation.chat.flow slot, so a direct-child selector
// misses the host and the Bot avatar + shimmer never portals (#1626).
export const RUNNING_HOST = '[data-chat-flow] [data-chat-running]'
export const TAIL_HOST = 'tail'

/** Resolve the shell running-status node under a document or flow root (0.2.0 or 0.2.1+). */
export function findRunningHostIn(root) {
if (!root || typeof root.querySelector !== 'function') return null
if (typeof root.matches === 'function' && root.matches('[data-chat-running]')) return root
// An Element (the flow column) scopes to descendants; a Document uses the full path.
if (typeof Element !== 'undefined' && root instanceof Element) return root.querySelector('[data-chat-running]')
return root.querySelector(RUNNING_HOST)
}
9 changes: 5 additions & 4 deletions core/src/client/activity-row.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import { reducedMotion } from './motion.js'
import { t } from './i18n.js'
import { AWAIT_REPLY_MS, awaitingReply } from './awaiting.js'
import { remoteApprovalPhrase } from './remote-approval-wait.js'
import { RUNNING_HOST, TAIL_HOST, findRunningHostIn } from './activity-row-host.js'

export { RUNNING_HOST, TAIL_HOST, findRunningHostIn } from './activity-row-host.js'

// -------------------------------------------------------------------------
// The activity row: the Bot's mark and a shimmering phrase at the transcript
Expand All @@ -24,8 +27,6 @@ export { reducedMotion }

// Where the row draws: the shell's running status while a turn runs; between turns of a
// friend's chat (#443), the tail of the transcript's last turn, so it stays below the last message.
export const RUNNING_HOST = '[data-chat-flow] > [data-chat-running]'
export const TAIL_HOST = 'tail'
const REMOTE_GRACE_MS = 1500
function useAwaitingReply(roster, sessionId) {
const at = roster?.remoteAwaiting?.[sessionId]
Expand All @@ -39,10 +40,10 @@ function useAwaitingReply(roster, sessionId) {
return shown
}
function findHost(where) {
if (where !== TAIL_HOST) return document.querySelector(where)
if (where !== TAIL_HOST) return findRunningHostIn(document)
const flow = document.querySelector('[data-conversation-scroll] [data-chat-flow]') ?? document.querySelector('[data-chat-flow]')
if (!flow) return null
const running = flow.querySelector(':scope > [data-chat-running]')
const running = findRunningHostIn(flow)
if (running) return running
const tails = flow.querySelectorAll(':scope > * [data-turn-tail], :scope > [data-turn-tail]')
return tails.length ? tails[tails.length - 1] : null
Expand Down
46 changes: 46 additions & 0 deletions core/test/activity-row-host.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import assert from 'node:assert/strict'
import { createRequire } from 'node:module'
import { join } from 'node:path'
import { readFileSync } from 'node:fs'
import { test } from 'node:test'
import { findRunningHostIn, RUNNING_HOST } from '../src/client/activity-row-host.js'

// jsdom comes from the DSH harness (test/harness-resolve.mjs sets DSH_HARNESS).
const { JSDOM } = createRequire(join(process.env.DSH_HARNESS, 'package.json'))('jsdom')

test('RUNNING_HOST is a descendant selector so DSH 0.2.1 nested RunningStatus still matches (#1626)', () => {
assert.equal(RUNNING_HOST, '[data-chat-flow] [data-chat-running]')
assert.equal(RUNNING_HOST.includes('>'), false, 'must not require a direct child of the flow column')
const src = readFileSync(new URL('../src/client/activity-row.js', import.meta.url), 'utf8')
assert.match(src, /activity-row-host\.js/)
assert.doesNotMatch(src, /\[data-chat-flow\] > \[data-chat-running\]/)
const hostSrc = readFileSync(new URL('../src/client/activity-row-host.js', import.meta.url), 'utf8')
assert.match(hostSrc, /conversation\.chat\.flow/)
})

test('findRunningHostIn finds RunningStatus as a direct child (DSH 0.2.0 layout)', () => {
const { document } = new JSDOM(`<!doctype html><div data-chat-flow="">
<div data-chat-running id="run"></div>
</div>`).window
const host = findRunningHostIn(document)
assert.equal(host?.id, 'run')
assert.equal(findRunningHostIn(document.querySelector('[data-chat-flow]'))?.id, 'run')
})

test('findRunningHostIn finds RunningStatus nested under conversation.chat.flow (DSH 0.2.1+)', () => {
const { document } = new JSDOM(`<!doctype html><div data-chat-flow="">
<div data-slot="conversation.chat.flow">
<div data-chat-flow-kind="user"></div>
<div data-chat-running id="nested-run"></div>
</div>
</div>`).window
assert.equal(document.querySelector('[data-chat-flow] > [data-chat-running]'), null, 'direct-child selector misses 0.2.1')
assert.equal(findRunningHostIn(document)?.id, 'nested-run')
assert.equal(findRunningHostIn(document.querySelector('[data-chat-flow]'))?.id, 'nested-run')
})

test('findRunningHostIn returns null when the running host is absent', () => {
const { document } = new JSDOM(`<!doctype html><div data-chat-flow=""><div data-slot="conversation.chat.flow"></div></div>`).window
assert.equal(findRunningHostIn(document), null)
assert.equal(findRunningHostIn(null), null)
})
2 changes: 1 addition & 1 deletion docs/subsystems/core-client.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ The overlay (`flybot-slots.js` `FlyBotApprovals`) is the fallback, not a second
- In a face-to-face room the bubble matches a mention by every alias the room host routes on (`faceAliases` in `shared/remote-label.js`, the same list `face-mentions.js` uses), so `@SelfA's Chief` typed on B is A's own Chief on A. The chip shows `@` and the seat label (`faceSeatLabel`, `SelfA · Chief`) on both homes, a bare name means the writer's own Bot (my line tries my seats first, a friend's line the friend's, whatever order the room lists its seats in, as `faceCandidates` does), a friend's Bot outside the room is not matched, and one space typed after a picked `@Name ` (the menu inserts its own) is dropped from the drawing (#305).
- A remote Bot's generated look is seeded by its handle (`remoteSeed` in `src/client/characters.js`), never by its session id. When Bot Social sends `avatar` / `color` / `avatar_updated_at`, `flybot.remote.publish` stores them on the remote-chat record (cached by `avatar_updated_at`). `lookBotForRemote` overlays a person payload's look (face-room `members[]` is the source for room avatars). A stored `color`/`avatar` still wins over the seed; missing look stays handle-seeded (#137). The @ menu icon (`menuIcons`) and the user-bubble mention chip (`withMentionLook` in `user-message.js`) draw that same record through `markBotForMention`. A stand-in that keeps the look only on `remote` is not drawn: `lookOf` does not read it, and the handle seed (a green moon for `@chief.selfa1025.home`) was showing beside the stored blue whale (#1088). The seed remains the fallback when neither the chat nor the face-room seat has a look. A friend's file-chip anchor uses `lookAvoidingLocals`, the same decision as `BotMark`; a local Bot still owns a shared `@Name` chip (#963).
- A shared display name does not restyle a local Bot (#963). The `@Name` file chip (`mentionCss` in `src/client/mention-style.js`) uses the local Bot's look when a friend has the same name; that friend's own mention anchor keeps the friend's color, and a name only a friend has keeps that friend's look. `indexRoster` does not let a remote chat replace a local Bot's id or session keys, and `rosterBot` resolves a member id to the local Bot. `@Name` in a bubble (`linkMentions`) names the local Bot when both are listed. A group reply that is not remote resolves through `rosterBot` before the index. The friend's own row keeps the friend's look, unless that drawn look is the same picture as the local Bot of the same display name, in which case the row steps its shape and the local Bot stays as stored (#1069).
- A friend's 1:1 chat (#443): `AssistantCell` gives the reply bubbles a `bt-lead` `BotMark` from the chat record (`friendLead`; none on a `Not sent:` note). The activity row (`activity-row.js`) stays up while `awaitingReply` (`awaiting.js`, `roster.remoteAwaiting`, 10 min cap) holds. Between the send turn and the echo turn it sits in the last turn's tail, with a 1.5 s grace and `data-carry` so there is no second entrance, which keeps one row from send to reply. The social `PeerThinking` line is for face rooms only. While the row is up in a friend chat and no local tool is the newest activity, its phrase is **Waiting for their approval** (等对方批准) instead of the thinking pool (`Sorting out the details` and the rest): that wait is the other home holding the reply, and this home cannot see the card (#1087). A local tool line still wins. A friend chat shows the row for a run that started from my submission and, between runs, while `awaitingReply` holds. The friend's reply run (the echo, which has no submission) hides it at once, and only the end of my send run gets the grace. The turn-tail hiding rules in `styles.js` spare `.bt-typing` and its tail item, or the row between runs would be drawn with `display:none`.
- A friend's 1:1 chat (#443): `AssistantCell` gives the reply bubbles a `bt-lead` `BotMark` from the chat record (`friendLead`; none on a `Not sent:` note). The activity row (`activity-row.js`; host `[data-chat-flow] [data-chat-running]`, nested under `conversation.chat.flow` on DSH 0.2.1+ — #1626) stays up while `awaitingReply` (`awaiting.js`, `roster.remoteAwaiting`, 10 min cap) holds. Between the send turn and the echo turn it sits in the last turn's tail, with a 1.5 s grace and `data-carry` so there is no second entrance, which keeps one row from send to reply. The social `PeerThinking` line is for face rooms only. While the row is up in a friend chat and no local tool is the newest activity, its phrase is **Waiting for their approval** (等对方批准) instead of the thinking pool (`Sorting out the details` and the rest): that wait is the other home holding the reply, and this home cannot see the card (#1087). A local tool line still wins. A friend chat shows the row for a run that started from my submission and, between runs, while `awaitingReply` holds. The friend's reply run (the echo, which has no submission) hides it at once, and only the end of my send run gets the grace. The turn-tail hiding rules in `styles.js` spare `.bt-typing` and its tail item, or the row between runs would be drawn with `display:none`.
- A friend's Bot the owner hid, a remote chat marked `gone`/`hidden`, and a home-level handle (`@friend.home`, not `@bot.friend.home`) must not reappear in the sidebar, the **发给 / 和谁聊…** picker, `@` mentions, or `list_bots` friends lines (`isListedRemoteChat`). `flybot.remote.publish` skips those handles so remote-chat sync marks the persisted session gone/hidden instead of opening it again; history is kept. Handle comparison is case-insensitive (`normRemoteHandle`).
- The "@" reader is shared with the host (`core/src/shared/mentions.js`, bundled into `core/client.js`). #287 added `unmatchedMentions` there (the `@` words that name nobody, used by the host for the face-room hint); `mentionSpans` and what the bubbles draw are unchanged.
- A friend's Bot settings surface is not the local editor (#160): no rename, avatar editor, model, folder, instructions, Main Bot, Hide, Duplicate, or Delete. The details gear and context menu say **Bot settings**. The menu is Mark as Unread, Bot settings, and Copy conversation ID. Per-friend **Auto-send** and **Remove this Bot** stay on that page (`flybot.bot.details`); Remove closes the overlay so a stale local editor cannot appear. The Bots settings list is local Bots only.
Expand Down
13 changes: 7 additions & 6 deletions patches/ds-bot/0003-flybot-api-client.patch
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,25 @@ FlyBot patch 0003-flybot-api-client on Fei2411/DeepSeek-Bot@2b10c70 (ds-bot/). G
diff --git a/src/client/activity-row.js b/src/client/activity-row.js
--- a/src/client/activity-row.js
+++ b/src/client/activity-row.js
@@ -4,6 +4,8 @@ import { BotMark, RoomAvatar } from './mark.js'
@@ -4,6 +4,11 @@ import { BotMark, RoomAvatar } from './mark.js'
import { Shimmer, PHRASES } from './cells.js'
import { reducedMotion } from './motion.js'
import { t } from './i18n.js'
+import { AWAIT_REPLY_MS, awaitingReply } from './awaiting.js'
+import { remoteApprovalPhrase } from './remote-approval-wait.js'
+import { RUNNING_HOST, TAIL_HOST, findRunningHostIn } from './activity-row-host.js'
+
+export { RUNNING_HOST, TAIL_HOST, findRunningHostIn } from './activity-row-host.js'

// -------------------------------------------------------------------------
// The activity row: the Bot's mark and a shimmering phrase at the transcript
@@ -20,21 +20,76 @@ export function elapsedLabel(ms) {
@@ -20,21 +20,74 @@ export function elapsedLabel(ms) {
}
export { reducedMotion }

-function useRunningHost(active) {
+// Where the row draws: the shell's running status while a turn runs; between turns of a
+// friend's chat (#443), the tail of the transcript's last turn, so it stays below the last message.
+export const RUNNING_HOST = '[data-chat-flow] > [data-chat-running]'
+export const TAIL_HOST = 'tail'
+const REMOTE_GRACE_MS = 1500
+function useAwaitingReply(roster, sessionId) {
+ const at = roster?.remoteAwaiting?.[sessionId]
Expand All @@ -34,10 +35,10 @@ diff --git a/src/client/activity-row.js b/src/client/activity-row.js
+ return shown
+}
+function findHost(where) {
+ if (where !== TAIL_HOST) return document.querySelector(where)
+ if (where !== TAIL_HOST) return findRunningHostIn(document)
+ const flow = document.querySelector('[data-conversation-scroll] [data-chat-flow]') ?? document.querySelector('[data-chat-flow]')
+ if (!flow) return null
+ const running = flow.querySelector(':scope > [data-chat-running]')
+ const running = findRunningHostIn(flow)
+ if (running) return running
+ const tails = flow.querySelectorAll(':scope > * [data-turn-tail], :scope > [data-turn-tail]')
+ return tails.length ? tails[tails.length - 1] : null
Expand Down
26 changes: 12 additions & 14 deletions patches/ds-bot/0007-version-and-tooling.patch
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
FlyBot patch 0007-version-and-tooling on Fei2411/DeepSeek-Bot@2b10c70 (ds-bot/). Generated by scripts/ds-bot-patches.mjs; edits to upstream files only (files FlyBot adds are in files/).

diff --git a/dev/harness/package.json b/dev/harness/package.json
--- a/dev/harness/package.json
+++ b/dev/harness/package.json
@@ -3,7 +3,7 @@
"private": true,
"description": "The DeepSeek Harness packages that the unit tests and the linter run against. Install with npm run harness in ds-bot/.",
"dependencies": {
- "@deepseek-ai/dsh": "0.2.0-rc.2",
+ "@deepseek-ai/dsh": "0.2.1-rc.2",
"jsdom": "29.1.1",
"oxlint": "1.76.0"
}
diff --git a/dev/smoke.mjs b/dev/smoke.mjs
--- a/dev/smoke.mjs
+++ b/dev/smoke.mjs
Expand Down Expand Up @@ -871,17 +883,3 @@ diff --git a/test/diagnostics.test.js b/test/diagnostics.test.js
+test('Report on GitHub opens a new issue in FlyBot\'s own repository', () => {
+ assert.equal(ISSUES_URL, 'https://github.com/ThinkFlowLab/FlyBot/issues/new')
+})

diff --git a/dev/harness/package.json b/dev/harness/package.json
--- a/dev/harness/package.json
+++ b/dev/harness/package.json
@@ -2,8 +2,8 @@
"name": "ds-bot-test-harness",
"private": true,
"description": "The DeepSeek Harness packages that the unit tests and the linter run against. Install with npm run harness in ds-bot/.",
"dependencies": {
- "@deepseek-ai/dsh": "0.2.0-rc.2",
+ "@deepseek-ai/dsh": "0.2.1-rc.2",
"jsdom": "29.1.1",
"oxlint": "1.76.0"
}
Loading
Loading