Quieter chat chrome: no repeated names, no English working line, no tool names (#186)

* Name a run's chat once when its group carries the bot's name

A project's main conversation is named after its lead bot, so Today
showed "Chief · Chief". Web and mobile now share one title helper that
drops the repeated name.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ

* Drop the "1:1 with" line under a conversation's title

The stage header repeated what the chat already shows, such as
"1:1 with Chief" under a project's name. The title and the phase pill
remain.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ

* Let the stage's translated thinking lines speak while a bot works

A running bot raised a "working" awareness event whose English line,
such as "Chief is working on your request.", replaced the translated
thinking lines and added an "In progress" strip next to the phase pill.
The strip now only shows collaboration.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ

* Ask bots to describe their steps without naming tools

Long runs showed lines such as "I'm using browser_navigate" in the chat.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ

* Announce the stage phase as status and keep following after a tap

The working strip used to be the only status region while a bot worked; the
stage's phase pill now carries that role. A tap at the latest message no
longer stops the transcript from following new messages.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
cynarAI 2026-10-09 17:48:18 +02:00 committed by GitHub
parent f720683496
commit 83d2106633
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 35 additions and 68 deletions

View File

@ -6,6 +6,7 @@ import {
type SpaceBot,
type SpaceGroup,
} from "@rakazo/contracts";
import { runChatTitle } from "@rakazo/core";
import { botColors } from "@rakazo/ui-tokens";
import { Redirect, useFocusEffect, useRouter } from "expo-router";
import { type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from "react";
@ -828,7 +829,7 @@ function ActivityRow({
bot?: MobileBot;
onPress: () => void;
}) {
const title = run.groupName ? `${run.botName} · ${run.groupName}` : run.botName;
const title = runChatTitle(run);
const status = activityStatusLabel(run.status);
const preview = run.promptSnippet ? `${run.promptSnippet} · ${status}` : status;
return (

View File

@ -67,7 +67,7 @@ test("main conversation does not show a group subtitle for one Oo", async ({ pag
await signup(page, `main-1-1-${stamp}@rakazo.test`, "password12", "Solo");
await completeOnboarding(page, undefined, { mainConversation: true });
await expect(page.getByTestId("bot-settings-trigger")).not.toContainText("Gruppenunterhaltung");
await expect(page.getByTestId("oo-group-stage")).toContainText(/1:1/i);
await expect(page.getByTestId("oo-group-stage")).not.toContainText("Gruppenunterhaltung");
});
test.describe("mobile shell", () => {

View File

@ -171,6 +171,7 @@ export function OoStage({
{(hasMessages || phase !== "idle") && (
<div className={`flex w-full justify-center ${hasMessages ? "mb-1" : "mb-3"}`}>
<div
role="status"
className="inline-flex items-center gap-2 rounded-full border border-border bg-foreground/5 px-2.5 py-0.5 text-ui-label font-semibold uppercase tracking-wider text-muted-foreground backdrop-blur-sm"
style={
reducedMotion
@ -434,12 +435,6 @@ export function OoGroupStage({
multiOo,
]);
const headerSubtitle = multiOo
? t`Group conversation`
: speaker
? t`1:1 with ${speaker.name}`
: t`1:1 conversation`;
const energy = groupStageEnergy(phase, groupSpark, workingBots.length);
const phaseLabel = showEnsembleOnly
@ -477,7 +472,10 @@ export function OoGroupStage({
className={`relative z-10 mx-auto flex w-full max-w-3xl flex-col items-center px-4 md:px-8 ${hasMessages ? "py-2 md:py-3" : "py-5 md:py-6"}`}
>
<div className="mb-3 w-full min-w-0 text-center">
<div className="inline-flex items-center gap-2 rounded-full border border-border bg-foreground/5 px-2.5 py-0.5 text-ui-label font-semibold uppercase tracking-wider text-muted-foreground backdrop-blur-sm">
<div
role="status"
className="inline-flex items-center gap-2 rounded-full border border-border bg-foreground/5 px-2.5 py-0.5 text-ui-label font-semibold uppercase tracking-wider text-muted-foreground backdrop-blur-sm"
>
<span
className={`size-1.5 rounded-full ${
running ? "bg-foreground/70" : "bg-success"
@ -488,7 +486,6 @@ export function OoGroupStage({
<h2 className="mt-1.5 truncate text-ui-title font-semibold tracking-tight text-foreground">
{groupName ?? t`Group`}
</h2>
<p className="truncate text-ui-body-sm text-muted-foreground">{headerSubtitle}</p>
</div>
{awarenessLabel && awarenessEvent && speaker ? (

View File

@ -20,7 +20,6 @@ export function VoiceAwarenessStrip({
handoff: t`Handoff`,
delegation: t`Delegated`,
"peer-reply": t`Peer reply`,
working: t`In progress`,
}[event.kind];
const Icon =
event.kind === "subagent"

View File

@ -46,7 +46,7 @@ export interface BotPersonaProfile {
}
export interface VoiceAwarenessEvent {
kind: "subagent" | "handoff" | "delegation" | "peer-reply" | "working";
kind: "subagent" | "handoff" | "delegation" | "peer-reply";
actorName: string;
detail: string;
targetName?: string;
@ -347,13 +347,6 @@ export function extractVoiceAwareness(
}
}
}
if (snapshot.run && ["running", "queued", "leased"].includes(snapshot.run.status)) {
return {
kind: "working",
actorName: botName,
detail: "",
};
}
return null;
}
@ -372,8 +365,6 @@ export function narrateAwarenessForPersona(
return narrateDelegation(profile.archetype, specialist, task);
case "peer-reply":
return narratePeerReply(profile.archetype, event.actorName, task);
case "working":
return narrateWorking(profile.archetype, event.actorName);
default:
return task;
}
@ -455,19 +446,6 @@ export function narrateGroupSpark(spark: GroupSpark, speakerArchetype?: PersonaA
}
}
function narrateWorking(archetype: PersonaArchetype, name: string): string {
switch (archetype) {
case "cto-executive":
return `${name} is in the work — give me a moment.`;
case "quant":
return `${name} is crunching — one beat.`;
case "engineer":
return `${name} is heads-down on this.`;
default:
return `${name} is working on your request.`;
}
}
function truncate(text: string, max: number): string {
const trimmed = text.trim();
if (trimmed.length <= max) return trimmed;

View File

@ -319,8 +319,6 @@ msgid "1:1 conversation with {title}"
msgstr "1:1-Unterhaltung mit {title}"
#. placeholder {0}: mainConversationEntry.bot.name
#. placeholder {0}: speaker.name
#: src/components/stage/OoStage.tsx
#: src/pages/Shell.tsx
msgid "1:1 with {0}"
msgstr "1:1 mit {0}"
@ -2796,7 +2794,6 @@ msgstr "OpenAPI-JSON importieren"
msgid "In my lane — happy to geek out or just execute, your call."
msgstr "In meinem Fachgebiet — ich gehe gern ins Detail oder setze einfach um, du entscheidest."
#: src/components/voice/VoiceAwarenessStrip.tsx
#: src/pages/ProjectSessionTimeline.tsx
msgid "In progress"
msgstr "In Arbeit"

View File

@ -319,8 +319,6 @@ msgid "1:1 conversation with {title}"
msgstr "1:1 conversation with {title}"
#. placeholder {0}: mainConversationEntry.bot.name
#. placeholder {0}: speaker.name
#: src/components/stage/OoStage.tsx
#: src/pages/Shell.tsx
msgid "1:1 with {0}"
msgstr "1:1 with {0}"
@ -2796,7 +2794,6 @@ msgstr "Import OpenAPI JSON"
msgid "In my lane — happy to geek out or just execute, your call."
msgstr "In my lane — happy to geek out or just execute, your call."
#: src/components/voice/VoiceAwarenessStrip.tsx
#: src/pages/ProjectSessionTimeline.tsx
msgid "In progress"
msgstr "In progress"

View File

@ -319,8 +319,6 @@ msgid "1:1 conversation with {title}"
msgstr ""
#. placeholder {0}: mainConversationEntry.bot.name
#. placeholder {0}: speaker.name
#: src/components/stage/OoStage.tsx
#: src/pages/Shell.tsx
msgid "1:1 with {0}"
msgstr ""
@ -2796,7 +2794,6 @@ msgstr "Importar JSON de OpenAPI"
msgid "In my lane — happy to geek out or just execute, your call."
msgstr ""
#: src/components/voice/VoiceAwarenessStrip.tsx
#: src/pages/ProjectSessionTimeline.tsx
msgid "In progress"
msgstr ""

View File

@ -319,8 +319,6 @@ msgid "1:1 conversation with {title}"
msgstr ""
#. placeholder {0}: mainConversationEntry.bot.name
#. placeholder {0}: speaker.name
#: src/components/stage/OoStage.tsx
#: src/pages/Shell.tsx
msgid "1:1 with {0}"
msgstr ""
@ -2796,7 +2794,6 @@ msgstr "OpenAPI JSON इम्पोर्ट करें"
msgid "In my lane — happy to geek out or just execute, your call."
msgstr ""
#: src/components/voice/VoiceAwarenessStrip.tsx
#: src/pages/ProjectSessionTimeline.tsx
msgid "In progress"
msgstr ""

View File

@ -319,8 +319,6 @@ msgid "1:1 conversation with {title}"
msgstr ""
#. placeholder {0}: mainConversationEntry.bot.name
#. placeholder {0}: speaker.name
#: src/components/stage/OoStage.tsx
#: src/pages/Shell.tsx
msgid "1:1 with {0}"
msgstr ""
@ -2796,7 +2794,6 @@ msgstr "OpenAPI JSON 가져오기"
msgid "In my lane — happy to geek out or just execute, your call."
msgstr ""
#: src/components/voice/VoiceAwarenessStrip.tsx
#: src/pages/ProjectSessionTimeline.tsx
msgid "In progress"
msgstr ""

View File

@ -319,8 +319,6 @@ msgid "1:1 conversation with {title}"
msgstr ""
#. placeholder {0}: mainConversationEntry.bot.name
#. placeholder {0}: speaker.name
#: src/components/stage/OoStage.tsx
#: src/pages/Shell.tsx
msgid "1:1 with {0}"
msgstr ""
@ -2796,7 +2794,6 @@ msgstr "Importar OpenAPI JSON"
msgid "In my lane — happy to geek out or just execute, your call."
msgstr ""
#: src/components/voice/VoiceAwarenessStrip.tsx
#: src/pages/ProjectSessionTimeline.tsx
msgid "In progress"
msgstr ""

View File

@ -319,8 +319,6 @@ msgid "1:1 conversation with {title}"
msgstr ""
#. placeholder {0}: mainConversationEntry.bot.name
#. placeholder {0}: speaker.name
#: src/components/stage/OoStage.tsx
#: src/pages/Shell.tsx
msgid "1:1 with {0}"
msgstr ""
@ -2796,7 +2794,6 @@ msgstr "Импортировать OpenAPI JSON"
msgid "In my lane — happy to geek out or just execute, your call."
msgstr ""
#: src/components/voice/VoiceAwarenessStrip.tsx
#: src/pages/ProjectSessionTimeline.tsx
msgid "In progress"
msgstr ""

View File

@ -319,8 +319,6 @@ msgid "1:1 conversation with {title}"
msgstr ""
#. placeholder {0}: mainConversationEntry.bot.name
#. placeholder {0}: speaker.name
#: src/components/stage/OoStage.tsx
#: src/pages/Shell.tsx
msgid "1:1 with {0}"
msgstr ""
@ -2796,7 +2794,6 @@ msgstr "OpenAPI JSON içe aktar"
msgid "In my lane — happy to geek out or just execute, your call."
msgstr ""
#: src/components/voice/VoiceAwarenessStrip.tsx
#: src/pages/ProjectSessionTimeline.tsx
msgid "In progress"
msgstr ""

View File

@ -319,8 +319,6 @@ msgid "1:1 conversation with {title}"
msgstr ""
#. placeholder {0}: mainConversationEntry.bot.name
#. placeholder {0}: speaker.name
#: src/components/stage/OoStage.tsx
#: src/pages/Shell.tsx
msgid "1:1 with {0}"
msgstr ""
@ -2796,7 +2794,6 @@ msgstr "导入 OpenAPI JSON"
msgid "In my lane — happy to geek out or just execute, your call."
msgstr ""
#: src/components/voice/VoiceAwarenessStrip.tsx
#: src/pages/ProjectSessionTimeline.tsx
msgid "In progress"
msgstr ""

View File

@ -2,6 +2,7 @@ import { i18n } from "@lingui/core";
import { t } from "@lingui/core/macro";
import { Trans, useLingui } from "@lingui/react/macro";
import type { RunActivityRow } from "@rakazo/contracts";
import { runChatTitle } from "@rakazo/core";
import { useEffect, useState } from "react";
import { isNeedsYouStatus, runActivityLabel } from "../lib/agent-activity";
import { rpc } from "../lib/rpc";
@ -93,7 +94,7 @@ export function ActivityList({ onOpenRun }: ActivityListProps) {
function ActivityRow({ run, onOpen }: { run: RunActivityRow; onOpen: () => void }) {
const { t } = useLingui();
const title = run.groupName ? `${run.botName} · ${run.groupName}` : run.botName;
const title = runChatTitle(run);
const label = statusLabel(run.status);
const activityLabel = t`${title}, ${label}`;
const tone = statusTone(run.status);

View File

@ -8,6 +8,7 @@ import type {
HabitToday,
RunActivityRow,
} from "@rakazo/contracts";
import { runChatTitle } from "@rakazo/core";
import { botColors } from "@rakazo/ui-tokens";
import { BotAvatar, Button, EvooMascot, GroupAvatar, Icon } from "@rakazo/ui-web";
import { CircleCheck, Pause, Play, Square } from "lucide-react";
@ -347,7 +348,7 @@ function NeedsYouRow({
if (item.kind === "run") {
const run = item.run;
const title = run.groupName ? `${run.botName} · ${run.groupName}` : run.botName;
const title = runChatTitle(run);
const subtitle = run.promptSnippet || runActivityLabel(run.status);
const label = t`${title}, ${runActivityLabel(run.status)}`;
return (
@ -418,7 +419,7 @@ function DoingRow({
}) {
const { t } = useLingui();
const run = item.run;
const title = run.groupName ? `${run.botName} · ${run.groupName}` : run.botName;
const title = runChatTitle(run);
const subtitle = run.promptSnippet || runActivityLabel(run.status);
const label = t`${title}, ${runActivityLabel(run.status)}`;
@ -458,7 +459,7 @@ function DoneRow({
}) {
const { t } = useLingui();
const run = item.run;
const title = run.groupName ? `${run.botName} · ${run.groupName}` : run.botName;
const title = runChatTitle(run);
const subtitle = run.promptSnippet || runActivityLabel(run.status);
const label = t`${title}, ${runActivityLabel(run.status)}`;

View File

@ -313,15 +313,16 @@ export const Transcript = memo(function Transcript({
<div
ref={scrollRef}
data-testid="transcript"
// A tap at the latest message keeps following; dragging away stops it in onScroll.
onPointerDown={(event) => {
lastScrollTop.current = event.currentTarget.scrollTop;
autoScrolling.current = false;
following.current = false;
following.current = transcriptIsNearEnd(event.currentTarget);
}}
onTouchStart={(event) => {
lastScrollTop.current = event.currentTarget.scrollTop;
autoScrolling.current = false;
following.current = false;
following.current = transcriptIsNearEnd(event.currentTarget);
}}
onWheel={(event) => {
if (event.deltaY < 0) {

View File

@ -4104,6 +4104,7 @@ export function createRunExecutor(deps: ExecutorDeps) {
? "Use list_secrets to discover saved names, secret_request to make authenticated requests without reading credentials, and forget_secret to revoke access."
: undefined,
"Never ask for a raw credential in chat or inject it into shell commands.",
"Tell the user what you do in plain words; never name your tools to them.",
exposedToolNames.has("remember") ? "Use remember for durable facts." : undefined,
exposedToolNames.has("scratchpad_add")
? "Use scratchpad_add / scratchpad_update / scratchpad_complete for open work that should outlive this turn (not reminders — those are schedule_*)."

View File

@ -1,6 +1,6 @@
import * as fc from "fast-check";
import { describe, expect, it } from "vitest";
import { assertTransition, canTransition } from "./run-state.js";
import { assertTransition, canTransition, runChatTitle } from "./run-state.js";
describe("run state machine", () => {
it("allows takeover resume onto a lease", () => {
@ -30,3 +30,11 @@ describe("run state machine", () => {
);
});
});
describe("runChatTitle", () => {
it("names the group unless it repeats the bot's name", () => {
expect(runChatTitle({ botName: "Writer", groupName: "Launch" })).toBe("Writer · Launch");
expect(runChatTitle({ botName: "Chief", groupName: "Chief" })).toBe("Chief");
expect(runChatTitle({ botName: "Chief", groupName: null })).toBe("Chief");
});
});

View File

@ -41,3 +41,10 @@ export function isTerminal(status: RunStatus): boolean {
export function nextFence(current: number): number {
return current + 1;
}
/** A run's chat as one label: the bot, and its group unless the group carries the bot's name. */
export function runChatTitle(run: { botName: string; groupName: string | null }): string {
return run.groupName && run.groupName !== run.botName
? `${run.botName} · ${run.groupName}`
: run.botName;
}