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
77 changes: 77 additions & 0 deletions .claude/skills/openbot-copy/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
---
name: openbot-copy
description: The words and the voice for every piece of text a person reads in OpenBot — screen headings and descriptions, buttons, empty states, notices, chat cards, and the server's error and refusal messages that reach the browser. Holds the glossary (Bot, app, admin, OpenBot) and the style rules. Use when writing or changing any text that is shown to a person, naming a screen, a section, a button or a state, writing an error or refusal message, or reviewing a diff that adds user-facing strings under app/src or server/src. Don't use for text written for the model (tool descriptions, standing instructions, system prompts), for code comments, or for the docs under docs/.
---

# OpenBot Copy

## When To Use

Any string a person reads: a heading, a description, a button, a placeholder, an empty state, a
notice, a chat card, a server error that the browser shows. Server messages count: a 409 that says
what to do next is read by a person as much as any screen is.

Not text written for the model. Tool descriptions, standing instructions and prompts steer how a Bot
behaves, and changing their wording is a behaviour change with its own review. Not code comments,
and not `docs/`, which has its own register.

## The Glossary

One word for one thing, everywhere a person can see it.

| Say | For | Not |
|---|---|---|
| **Bot**, **Bots** (capitalised) | What a person creates, talks to and gives work to. | agent, coworker, assistant, AI |
| **agent** | Only an outside framework's program, in the places that are about connecting one: "an agent you already run", "AG-UI agent". | Bot, when the thing is a Bot |
| **app** | A service a Bot uses: Gmail, Google Drive, Notion. | connector, integration, plugin (outside Admin) |
| **Connections** | The apps and accounts a Bot uses as you, and the page that lists them. | connected accounts, linked accounts |
| **connect / disconnect / reconnect** | What a person does to their own account. | link (except GitHub and Slack identity, where "link" is the vendor's own word) |
| **Capabilities** | Your skills and your Bots' memory of you, and the page with both. | plugins |
| **skill** | A named instruction invoked with `/`. | command, prompt |
| **admin** | The person who runs OpenBot for everybody. | administrator, owner, operator |
| **OpenBot** | The product the person is using. | this deployment, the server, the instance |
| **turn on / turn off** | An admin switching an app or a feature. | enable / disable (in sentences; a switch may still read "Enabled") |
| **conversation** | A thread with one or more Bots. | channel, chat (in sentences) |

**Admin is allowed exact names.** An admin page may name `COMPOSIO_API_KEY`, OAuth and "deployment",
because the admin is the one who sets them. A page anybody can open may not.

## The Style

1. **Say what happened, then what to do.** "Gmail isn't connected. Connect it to let Bots read your
mail." Not why the screen is built the way it is.
2. **One idea per sentence. Short sentences.** A description is one or two sentences, about 20 words.
3. **Plain words.** "use", not "hold a grant on". "can't", not "is unable to". Contractions are fine.
4. **No "rather than", no em dashes, no "which is why", no "so that nothing … that may be wrong".**
These are the marks of text that is explaining itself. Cut the clause or make it its own sentence.
5. **No internals outside Admin.** Composio, OAuth client, credential, token, grant, scope,
deployment, broker, endpoint: say what the person sees instead ("not set up yet", "your account",
"this Bot can use it").
6. **Second person.** "Your apps", "you can". The Bot is "it".
7. **Sentence case** for headings and buttons: "New Bot", "Your connections".
8. **Buttons are verbs**: Connect, Request access, Create, Save. A button never says "Click here" or
"OK" when it does something specific.
9. **Errors**: what failed, in the person's terms, and one next step. "Couldn't load your
connections. Reload the page." No stack of reasons, no blame.
10. **Empty states** say what will appear and how: "No skills yet. Write one with New skill."

## Before And After

| Before | After |
|---|---|
| Your connected accounts could not be loaded, so nothing is listed here rather than a list that may be wrong. Reload the page, and tell an administrator if it persists. | Couldn't load your connections. Reload the page, or ask an admin if it keeps happening. |
| Sent to the coworker it is for. Type @ to choose one yourself. | OpenBot picks the right Bot. Type @ to choose one. |
| Gmail is a Composio app, and this deployment has no Composio key. (to a non-admin) | Gmail isn't set up yet. An admin has to turn it on. |
| The agent is still working on another request. Please wait or stop that task first. | This Bot is still working on something. Wait for it, or stop it first. |

## Red Flags

| Signal | Do instead |
|---|---|
| "agent" or "coworker" for a Bot | Bot |
| "administrator" | admin |
| "this deployment" on a page anybody can open | OpenBot, or say what is missing |
| A sentence over ~25 words | Split it, or cut the clause that justifies the screen |
| "rather than", an em dash, "which is" in UI text | Rewrite as two short sentences |
| An env var, OAuth, Composio or "grant" outside Admin | Say what the person sees |
| A test asserting long explanatory copy | Assert the short sentence, or the role and the key words |
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@ Newest first. `Unreleased` is what is on `main` and not yet tagged.
request for an administrator to switch on a work app, grant it to a Bot, or connect a Shared app's
team account is kept, with who answered it and when. It starts empty and nothing is backfilled.

### Plainer words, and one word for each thing

OpenBot now says **Bot** everywhere a person reads it, where screens used to mix Bot, agent, coworker
and assistant; "agent" is kept only for an outside program you connect. It says **admin** rather
than administrator, **conversation** rather than channel, and **OpenBot** rather than "this
deployment" on pages anybody can open. Headings, descriptions, empty states, errors and the
messages the server shows are shorter and say what happened and what to do next, across the home
page, a Bot's pages, Capabilities, Connections, Settings, conversations and Admin. Admin keeps the
exact setting names an admin needs, such as `COMPOSIO_API_KEY`. Text written for the model, such
as tool descriptions and instructions, is unchanged.

### Every settings screen is the same, slightly wider column

Settings, Admin, Connections, Capabilities and each Bot's pages share one column width, now 720px instead of 630px, so
Expand Down
4 changes: 2 additions & 2 deletions app/src/components/admin/admin-sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,12 @@ const GROUPS: {
linkOptions: { to: "/admin/skills" },
},
{
title: "Automatic Learning",
title: "Automatic learning",
icon: IconFileText,
linkOptions: { to: "/admin/learning" },
},
{
title: "UI Components",
title: "UI components",
icon: IconLayoutGrid,
linkOptions: { to: "/admin/components" },
},
Expand Down
4 changes: 2 additions & 2 deletions app/src/components/agents/agent-profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export function AgentProfile({ agentId }: { agentId: string }) {
if (agent.error || !agent.data) {
return (
<p className="p-8 text-sm text-destructive" role="alert">
Could not load this coworker.
Couldn't load this Bot.
</p>
);
}
Expand Down Expand Up @@ -103,7 +103,7 @@ export function AgentProfile({ agentId }: { agentId: string }) {
void navigate({ search: { agent: agentId }, to: "/channel/new" })
}
>
Start new channel
New conversation
</Button>
<Button
className="w-full text-sm!"
Expand Down
17 changes: 8 additions & 9 deletions app/src/components/agents/callback-token-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,19 @@ export function CallbackTokenPanel({
return (
<section className="mt-6 grid gap-2">
<h2 className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
Calling tools back
Key for tools
</h2>

<p className="text-muted-foreground text-sm">
{hasToken
? "This coworker holds a credential, so it can use the tools it has been granted. Rotating replaces it, and the old one stops working straight away."
: "This coworker has no credential, so it can hold a conversation but cannot use any tool it has been granted. Generate one and put it in that agent's configuration."}
? "This Bot has a key, so it can use its tools. If you make a new key, the old one stops working right away."
: "This Bot has no key yet. It can talk but can't use tools. Generate a key and add it to your agent's configuration."}
</p>

{token ? (
<div className="grid gap-2 rounded-lg border border-border bg-card p-3">
<p className="font-medium text-sm">
Copy this now. It will not be shown again.
Copy this key now. You won't see it again.
</p>
{/*
* Selectable and wrapped rather than a copy button alone: somebody pasting this into a
Expand All @@ -59,8 +59,7 @@ export function CallbackTokenPanel({
{token}
</code>
<p className="text-muted-foreground text-xs">
The deployment keeps only a hash of it, so nothing here can show it
to you a second time.
If you lose it, make a new one.
</p>
<Button onClick={() => setToken(null)} size="sm" variant="outline">
Done
Expand All @@ -77,8 +76,8 @@ export function CallbackTokenPanel({
{issue.isPending
? "Generating…"
: hasToken
? "Rotate token"
: "Generate token"}
? "Make a new key"
: "Generate key"}
</Button>
{hasToken ? (
<Button
Expand All @@ -87,7 +86,7 @@ export function CallbackTokenPanel({
size="sm"
variant="outline"
>
{revoke.isPending ? "Revoking…" : "Revoke"}
{revoke.isPending ? "Revoking…" : "Revoke key"}
</Button>
) : null}
</div>
Expand Down
21 changes: 10 additions & 11 deletions app/src/components/agents/handoff-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,22 +76,21 @@ export function HandoffPanel({ agentId }: { agentId: string }) {
</span>
) : null
}
description="Who this Bot may ask, not who may ask it. What the asked Bot says comes back into the conversation that asked, relayed and attributed."
title="Bots it may ask"
description="The Bots this one can ask for help. This doesn't change who can ask it. Their answers come back into the same conversation, under their name."
title="Bots it can ask"
>
{/* Facts about the deployment rather than rows to act on, so said as sentences. */}
{enabled ? null : (
<PageEmpty>
Switched off for this deployment. These grants are kept but none takes
effect until handing work between Bots is switched back on.
Handing work between Bots is turned off. These settings are kept, and
they apply again once an admin turns it back on.
</PageEmpty>
)}

{grantable ? null : (
<PageEmpty>
This coworker cannot hand work on. This deployment has nowhere to
record which Bots it may hand work to, so there is nothing to grant
it. It can still be asked by Bots that can.
This Bot can't hand work to other Bots. Other Bots can still hand work
to it.
</PageEmpty>
)}

Expand All @@ -103,8 +102,8 @@ export function HandoffPanel({ agentId }: { agentId: string }) {

{grantable && total === 0 ? (
<PageEmpty>
No other Bot here yet. When this deployment has more Bots, this is
where this one is allowed to ask them.
No other Bots yet. When there are more, you can let this Bot ask them
here.
</PageEmpty>
) : null}
{candidates.length > 0 ? (
Expand All @@ -129,7 +128,7 @@ export function HandoffPanel({ agentId }: { agentId: string }) {
*/}
<ItemDescription>
{candidate.hidden
? `${candidate.title} · hidden from your roster`
? `${candidate.title} · hidden from your Bots list`
: candidate.title}
</ItemDescription>
</ItemContent>
Expand All @@ -154,7 +153,7 @@ export function HandoffPanel({ agentId }: { agentId: string }) {
) : null}

{canGrant ? null : (
<PageEmpty>An administrator decides which Bots may be asked.</PageEmpty>
<PageEmpty>An admin decides which Bots this one can ask.</PageEmpty>
)}
</PageSection>
);
Expand Down
26 changes: 12 additions & 14 deletions app/src/components/agents/new-bot-sheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ function NewBotFlow({ onCreated }: { onCreated: (agentId: string) => void }) {
const named = name.trim() !== "";
const endpointIssue =
endpoint.trim() === ""
? "An address is required for an agent you run."
? "Enter your agent's address."
: agentFormSchema.shape.endpoint.safeParse(endpoint).error?.issues[0]
?.message;
const busy = checking || createAgent.isPending;
Expand Down Expand Up @@ -486,13 +486,13 @@ const VISIBILITY_OPTIONS: Array<{
{
value: "private",
title: "Private",
description: "Only you can see it and start channels with it.",
description: "Only you can see it and talk to it.",
icon: IconLock,
},
{
value: "public",
title: "Public",
description: "Everyone in the deployment can find and use it.",
description: "Everyone in your OpenBot can find and use it.",
icon: IconUsers,
},
];
Expand Down Expand Up @@ -562,9 +562,7 @@ function AboutStep({
placeholder={runsHere ? "Finance Operations" : defaultTitle(runtime)}
value={title}
/>
<FieldDescription>
A few words, shown beneath its name in lists.
</FieldDescription>
<FieldDescription>A few words shown under its name.</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="new-bot-instructions">
Expand All @@ -582,8 +580,8 @@ function AboutStep({
/>
<FieldDescription>
{runsHere
? "How it behaves in every conversation. Left blank, it helps with whatever it is asked."
: "Added to the agent's own instructions, in every conversation it joins."}
? "How it should work. Leave it blank and it helps with anything."
: "Added to your agent's own instructions."}
</FieldDescription>
</Field>
<FieldSet>
Expand Down Expand Up @@ -686,8 +684,8 @@ function RuntimeStep({
<span className="block font-semibold text-base">Run it here</span>
<span className="mt-1 block text-pretty text-muted-foreground text-sm">
{builtInAvailable
? "OpenBot runs it for you. Nothing to set up — it can start work as soon as it is made."
: "Not available yet. Whoever set OpenBot up has to switch this on first."}
? "OpenBot runs it for you. Nothing to set up, and it can start right away."
: "Not available yet. An admin has to turn this on first."}
</span>
</span>
</button>
Expand Down Expand Up @@ -787,8 +785,8 @@ function EndpointStep({
/>
) : (
<FieldDescription>
Anything that speaks AG-UI works. This server dials your agent, so
an agent on your own machine has to be reachable from here.
Any agent that speaks AG-UI works. OpenBot connects to it, so it
has to be reachable from OpenBot's server.
</FieldDescription>
)}
</Field>
Expand All @@ -806,8 +804,8 @@ function EndpointStep({
value={secret}
/>
<FieldDescription>
Sent as an <code>Authorization</code> header on every run, and kept
in the credential vault.
Sent as an <code>Authorization</code> header with every request, and
stored encrypted.
</FieldDescription>
</Field>
</FieldGroup>
Expand Down
Loading
Loading