diff --git a/apps/mobile/app/index.tsx b/apps/mobile/app/index.tsx index 699f2a2..91c69f1 100644 --- a/apps/mobile/app/index.tsx +++ b/apps/mobile/app/index.tsx @@ -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 ( diff --git a/apps/web/e2e/first-run-onboarding-ux.spec.ts b/apps/web/e2e/first-run-onboarding-ux.spec.ts index 2dfa45f..73c844a 100644 --- a/apps/web/e2e/first-run-onboarding-ux.spec.ts +++ b/apps/web/e2e/first-run-onboarding-ux.spec.ts @@ -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", () => { diff --git a/apps/web/src/components/stage/OoStage.tsx b/apps/web/src/components/stage/OoStage.tsx index 586486e..9817ee8 100644 --- a/apps/web/src/components/stage/OoStage.tsx +++ b/apps/web/src/components/stage/OoStage.tsx @@ -171,6 +171,7 @@ export function OoStage({ {(hasMessages || phase !== "idle") && (
-
+
{groupName ?? t`Group`} -

{headerSubtitle}

{awarenessLabel && awarenessEvent && speaker ? ( diff --git a/apps/web/src/components/voice/VoiceAwarenessStrip.tsx b/apps/web/src/components/voice/VoiceAwarenessStrip.tsx index e299d11..f16b72a 100644 --- a/apps/web/src/components/voice/VoiceAwarenessStrip.tsx +++ b/apps/web/src/components/voice/VoiceAwarenessStrip.tsx @@ -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" diff --git a/apps/web/src/lib/bot-persona.ts b/apps/web/src/lib/bot-persona.ts index 2645819..04b5dfa 100644 --- a/apps/web/src/lib/bot-persona.ts +++ b/apps/web/src/lib/bot-persona.ts @@ -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; diff --git a/apps/web/src/locales/de/messages.po b/apps/web/src/locales/de/messages.po index 901cd15..260de3a 100644 --- a/apps/web/src/locales/de/messages.po +++ b/apps/web/src/locales/de/messages.po @@ -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" diff --git a/apps/web/src/locales/en/messages.po b/apps/web/src/locales/en/messages.po index 7bb7a10..a0b1b88 100644 --- a/apps/web/src/locales/en/messages.po +++ b/apps/web/src/locales/en/messages.po @@ -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" diff --git a/apps/web/src/locales/es/messages.po b/apps/web/src/locales/es/messages.po index de2e52f..9f4e3b9 100644 --- a/apps/web/src/locales/es/messages.po +++ b/apps/web/src/locales/es/messages.po @@ -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 "" diff --git a/apps/web/src/locales/hi/messages.po b/apps/web/src/locales/hi/messages.po index d434e10..cf85a9c 100644 --- a/apps/web/src/locales/hi/messages.po +++ b/apps/web/src/locales/hi/messages.po @@ -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 "" diff --git a/apps/web/src/locales/ko/messages.po b/apps/web/src/locales/ko/messages.po index cb7e027..0da0535 100644 --- a/apps/web/src/locales/ko/messages.po +++ b/apps/web/src/locales/ko/messages.po @@ -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 "" diff --git a/apps/web/src/locales/pt-BR/messages.po b/apps/web/src/locales/pt-BR/messages.po index f0a5865..219e406 100644 --- a/apps/web/src/locales/pt-BR/messages.po +++ b/apps/web/src/locales/pt-BR/messages.po @@ -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 "" diff --git a/apps/web/src/locales/ru/messages.po b/apps/web/src/locales/ru/messages.po index cfd504e..c47c8c4 100644 --- a/apps/web/src/locales/ru/messages.po +++ b/apps/web/src/locales/ru/messages.po @@ -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 "" diff --git a/apps/web/src/locales/tr/messages.po b/apps/web/src/locales/tr/messages.po index c45dc4a..e4a4afb 100644 --- a/apps/web/src/locales/tr/messages.po +++ b/apps/web/src/locales/tr/messages.po @@ -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 "" diff --git a/apps/web/src/locales/zh-CN/messages.po b/apps/web/src/locales/zh-CN/messages.po index 3b41a24..1ede177 100644 --- a/apps/web/src/locales/zh-CN/messages.po +++ b/apps/web/src/locales/zh-CN/messages.po @@ -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 "" diff --git a/apps/web/src/pages/ActivityList.tsx b/apps/web/src/pages/ActivityList.tsx index 41f0b06..0cb4e5b 100644 --- a/apps/web/src/pages/ActivityList.tsx +++ b/apps/web/src/pages/ActivityList.tsx @@ -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); diff --git a/apps/web/src/pages/HeuteBrief.tsx b/apps/web/src/pages/HeuteBrief.tsx index 9379b9b..99660bc 100644 --- a/apps/web/src/pages/HeuteBrief.tsx +++ b/apps/web/src/pages/HeuteBrief.tsx @@ -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)}`; diff --git a/apps/web/src/pages/shell/transcript.tsx b/apps/web/src/pages/shell/transcript.tsx index e3ba975..a19aa1d 100644 --- a/apps/web/src/pages/shell/transcript.tsx +++ b/apps/web/src/pages/shell/transcript.tsx @@ -313,15 +313,16 @@ export const Transcript = memo(function Transcript({
{ 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) { diff --git a/packages/adapters/src/executor.ts b/packages/adapters/src/executor.ts index 43bc0da..6d1b5c5 100644 --- a/packages/adapters/src/executor.ts +++ b/packages/adapters/src/executor.ts @@ -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_*)." diff --git a/packages/core/src/run-state.test.ts b/packages/core/src/run-state.test.ts index e82cbe1..6b61fe2 100644 --- a/packages/core/src/run-state.test.ts +++ b/packages/core/src/run-state.test.ts @@ -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"); + }); +}); diff --git a/packages/core/src/run-state.ts b/packages/core/src/run-state.ts index ec2ce84..99fc6b7 100644 --- a/packages/core/src/run-state.ts +++ b/packages/core/src/run-state.ts @@ -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; +}