From 43c250b4f454c6602d8e2f68f862c2ba2800e1eb Mon Sep 17 00:00:00 2001 From: Guido Vizoso Date: Wed, 21 Oct 2026 17:51:30 -0300 Subject: [PATCH] Copy pass: one word for each thing, and plainer text everywhere Bot everywhere a person reads it (agent only for an outside program), admin rather than administrator, conversation rather than channel, and OpenBot rather than this deployment outside Admin. Shorter headings, descriptions, empty states, errors and server messages that say what happened and what to do next. Adds .claude/skills/openbot-copy, the glossary and style guide. Text written for the model is unchanged. --- .claude/skills/openbot-copy/SKILL.md | 77 ++++++++ CHANGELOG.md | 11 ++ app/src/components/admin/admin-sidebar.tsx | 4 +- app/src/components/agents/agent-profile.tsx | 4 +- .../agents/callback-token-panel.tsx | 17 +- app/src/components/agents/handoff-panel.tsx | 21 +- app/src/components/agents/new-bot-sheet.tsx | 26 ++- app/src/components/agents/proposed-bot.tsx | 24 +-- .../components/app-sidebar/app-sidebar.tsx | 18 +- .../app-sidebar/channel-pagination.tsx | 4 +- app/src/components/app-sidebar/channel.tsx | 6 +- app/src/components/approvals/bot-rules.tsx | 6 +- app/src/components/approvals/rules.tsx | 4 +- app/src/components/approvals/settings.tsx | 27 ++- .../approvals/shared-use-requests.tsx | 6 +- .../components/approvals/team-settings.tsx | 2 +- app/src/components/bot-profile/access.tsx | 18 +- app/src/components/bot-profile/activity.tsx | 2 +- .../components/bot-profile/bot-subpage.tsx | 2 +- app/src/components/bot-profile/manage.tsx | 33 ++-- .../components/bot-profile/pause-banner.tsx | 2 +- app/src/components/bot-profile/profile.tsx | 4 +- app/src/components/bot-profile/setup.tsx | 21 +- app/src/components/bot-profile/sharing.tsx | 19 +- app/src/components/bots/bot-roster.tsx | 6 +- .../components/channels/app-access-card.tsx | 2 +- app/src/components/channels/channel-chat.tsx | 16 +- .../channels/composer/attachment-strip.tsx | 2 +- .../channels/composer/attachments.ts | 4 +- .../components/channels/composer/composer.tsx | 24 +-- .../channels/composer/dictation-controls.tsx | 2 +- .../channels/composer/picked-files.ts | 14 +- .../components/channels/composer/sources.ts | 2 +- .../components/channels/composer/triggers.ts | 8 +- .../components/channels/connect-app-card.tsx | 3 +- app/src/components/channels/group-chat.tsx | 2 +- app/src/components/channels/group-people.tsx | 4 +- .../components/channels/recipient-field.tsx | 6 +- app/src/components/channels/tool-activity.tsx | 2 +- app/src/components/channels/tool-boundary.tsx | 4 +- .../components/channels/voice-call-widget.tsx | 4 +- .../components/channels/voice-chat-card.tsx | 2 +- app/src/components/computer/activity-log.tsx | 3 +- .../components/computer/command-output.tsx | 2 +- app/src/components/computer/computer-view.tsx | 49 +++-- .../computer/demonstration-recorder.tsx | 15 +- .../computer/handoff-resume-notice.tsx | 2 +- app/src/components/computer/live-screen.tsx | 6 +- .../connections/identity-connection-page.tsx | 2 +- app/src/components/delivery/reaching-you.tsx | 14 +- app/src/components/gallery/activity.tsx | 13 +- app/src/components/gallery/decisions.tsx | 4 +- app/src/components/gallery/form.tsx | 6 +- app/src/components/gallery/preview.tsx | 6 +- app/src/components/memory/memories.tsx | 12 +- app/src/components/memory/sources.tsx | 18 +- app/src/components/passwords/sign-in-form.tsx | 8 +- .../components/plugins/access-requests.tsx | 6 +- .../plugins/account-mode-dialog.tsx | 3 +- .../components/plugins/app-access-action.tsx | 20 +- .../components/plugins/broker-directory.tsx | 6 +- .../plugins/brokered-account-row.tsx | 25 ++- .../components/plugins/connect-in-new-tab.ts | 2 +- app/src/components/plugins/grant-list.tsx | 18 +- .../components/plugins/shared-app-notice.tsx | 10 +- .../plugins/shared-approval-fields.tsx | 4 +- .../new-responsibility-dialog.tsx | 7 +- .../responsibilities/responsibilities.tsx | 4 +- .../responsibility-dialog.tsx | 20 +- .../components/responsibilities/triggers.tsx | 22 +-- app/src/components/routines/routines-list.tsx | 27 ++- .../components/settings/github-sources.tsx | 6 +- .../components/settings/linked-accounts.tsx | 15 +- .../components/settings/linked-notice.test.ts | 4 +- app/src/components/settings/linked-notice.ts | 4 +- .../settings/message-list-preference.tsx | 12 +- .../settings/standing-instructions.tsx | 2 +- .../components/settings/team-bot-consents.tsx | 2 +- .../components/settings/update-routing.tsx | 6 +- app/src/components/skills/edit-skill.tsx | 8 +- app/src/components/skills/new-skill.tsx | 4 +- app/src/components/skills/proposed-skill.tsx | 12 +- app/src/components/skills/skill-agents.tsx | 7 +- app/src/components/skills/skill-fields.tsx | 8 +- app/src/components/skills/skill-tools.tsx | 23 +-- app/src/lib/agents/mutations.ts | 2 +- app/src/lib/agents/queries.ts | 4 +- app/src/lib/channels/mutations.ts | 8 +- app/src/lib/channels/queries.ts | 4 +- app/src/lib/channels/route.ts | 4 +- app/src/lib/client.ts | 2 +- app/src/lib/computers/control.ts | 7 +- app/src/lib/plugins/grants.ts | 4 +- app/src/lib/plugins/mutations.ts | 20 +- app/src/lib/plugins/provider-vocabulary.ts | 53 ++--- app/src/lib/plugins/queries.ts | 6 +- app/src/lib/plugins/shared-use.ts | 8 +- app/src/lib/skills/form.ts | 2 +- app/src/routes/_authed/_app/bot.tsx | 15 +- .../_authed/_app/bots.$agentId.access.tsx | 2 +- .../_authed/_app/bots.$agentId.approvals.tsx | 2 +- .../_authed/_app/bots.$agentId.index.tsx | 4 +- .../_authed/_app/bots.$agentId.memory.tsx | 4 +- .../_app/bots.$agentId.responsibilities.tsx | 2 +- .../_authed/_app/bots.$agentId.routines.tsx | 2 +- .../_authed/_app/bots.$agentId.sharing.tsx | 2 +- app/src/routes/_authed/_app/bots.index.tsx | 2 +- .../_authed/_app/capabilities.memory.tsx | 2 +- .../_authed/_app/capabilities.skills.tsx | 10 +- .../_authed/_app/channel/$channelId.tsx | 4 +- app/src/routes/_authed/_app/channel/new.tsx | 12 +- .../routes/_authed/_app/connections.$key.tsx | 53 +++-- .../routes/_authed/_app/connections.index.tsx | 41 ++-- app/src/routes/_authed/_app/index.tsx | 23 ++- app/src/routes/_authed/admin/approvals.tsx | 2 +- app/src/routes/_authed/admin/audit.tsx | 36 ++-- app/src/routes/_authed/admin/boundaries.tsx | 63 +++--- .../routes/_authed/admin/components/$name.tsx | 56 +++--- .../routes/_authed/admin/components/index.tsx | 8 +- app/src/routes/_authed/admin/computers.tsx | 61 +++--- app/src/routes/_authed/admin/credentials.tsx | 18 +- app/src/routes/_authed/admin/enterprise.tsx | 62 +++--- .../_authed/admin/identity-providers.tsx | 18 +- app/src/routes/_authed/admin/index.tsx | 33 ++-- app/src/routes/_authed/admin/learning.tsx | 47 +++-- app/src/routes/_authed/admin/people.tsx | 16 +- app/src/routes/_authed/admin/playground.tsx | 18 +- app/src/routes/_authed/admin/plugins/$key.tsx | 86 ++++---- .../admin/plugins/$key_.bots.$agentId.tsx | 28 +-- .../admin/plugins/$key_.tools.$tool.tsx | 31 ++- .../routes/_authed/admin/plugins/arcade.tsx | 3 +- .../routes/_authed/admin/plugins/composio.tsx | 3 +- .../routes/_authed/admin/plugins/index.tsx | 12 +- app/src/routes/_authed/admin/skills.tsx | 16 +- app/src/routes/_authed/onboarding.tsx | 19 +- app/src/routes/_authed/settings/approvals.tsx | 2 +- .../settings/components-gallery/$name.tsx | 7 +- .../settings/components-gallery/index.tsx | 6 +- app/src/routes/_authed/settings/index.tsx | 2 +- .../routes/_authed/settings/notifications.tsx | 2 +- app/src/routes/_authed/settings/passwords.tsx | 10 +- app/src/routes/_authed/sign-in/$requestId.tsx | 2 +- app/src/routes/sign.tsx | 11 +- app/tests/account-mode-dialog.test.tsx | 2 +- .../admin-brokered-row-provider.test.tsx | 4 +- app/tests/agent-roster-error.test.tsx | 20 +- app/tests/app-access-action.test.tsx | 20 +- app/tests/app-access-card.test.tsx | 10 +- app/tests/approvals-rules.test.tsx | 10 +- app/tests/arcade-admin-plugins.test.tsx | 15 +- app/tests/authoring-tool-cards.test.tsx | 2 +- app/tests/bot-access-apps.test.tsx | 18 +- app/tests/bot-access-skills.test.tsx | 10 +- app/tests/bot-app-grants-screen.test.tsx | 24 +-- app/tests/bot-manage.test.tsx | 4 +- app/tests/bot-memory.test.tsx | 4 +- app/tests/bot-profile-active.test.tsx | 6 +- app/tests/bot-profile-notice.test.tsx | 4 +- app/tests/bot-reaching-you.test.tsx | 8 +- app/tests/bot-roster.test.tsx | 8 +- app/tests/bot-route-default-agent.test.tsx | 24 ++- app/tests/bot-sharing.test.tsx | 2 +- app/tests/brokered-account-row.test.tsx | 186 +++++++----------- app/tests/channel-history-notice.test.ts | 10 +- app/tests/channel-history-refresh.test.tsx | 4 +- app/tests/channel-new-error-state.test.tsx | 4 +- app/tests/channel-pagination.test.tsx | 14 +- app/tests/composer-attachments.test.ts | 13 +- app/tests/composer-insecure-context.test.tsx | 4 +- app/tests/composer-queue-attachments.test.tsx | 2 +- app/tests/composer-unnamed-mime.test.tsx | 4 +- .../composio-route-plugins-error.test.tsx | 2 +- .../composio-unconfigured-account.test.tsx | 6 +- .../connected-account-confirmation.test.tsx | 85 ++++---- app/tests/connected-account-grants.test.tsx | 8 +- ...connected-accounts-oneshot-params.test.tsx | 24 +-- app/tests/directory-read-fallback.test.ts | 8 +- app/tests/gallery-components.test.tsx | 4 +- app/tests/learning-admin.test.tsx | 2 +- app/tests/new-bot-sheet.test.tsx | 6 +- app/tests/plugin-read-failure.test.tsx | 26 +-- app/tests/proposed-bot-card.test.tsx | 2 +- app/tests/proposed-skill-card.test.tsx | 4 +- app/tests/shared-app-notice.test.tsx | 10 +- app/tests/shared-grant-dialog.test.tsx | 2 +- app/tests/shared-use-requests.test.tsx | 6 +- app/tests/skills-read-failure.test.tsx | 36 ++-- app/tests/user-preferences.test.tsx | 2 +- server/src/admin/capabilities.ts | 2 +- server/src/admin/controls.ts | 4 +- server/src/admin/gate.ts | 2 +- server/src/agents/endpoint.ts | 4 +- server/src/agents/lifecycle-routes.ts | 2 +- server/src/agents/profile-store.ts | 10 +- server/src/agents/routes.ts | 17 +- server/src/app.ts | 4 +- server/src/auth/email-domain.ts | 2 +- server/src/auth/guards.ts | 2 +- server/src/auth/index.ts | 4 +- server/src/channels/attachments.ts | 32 ++- server/src/channels/routes.ts | 9 +- server/src/components/headless.ts | 2 +- server/src/components/routes.ts | 4 +- server/src/computer/client.ts | 6 +- server/src/computer/routes.ts | 4 +- server/src/computer/target.ts | 4 +- server/src/dictation/provider.ts | 2 +- server/src/dictation/routes.ts | 17 +- server/src/learning/routes.ts | 2 +- server/src/memory/ingestion.ts | 4 +- server/src/plugins/broker.ts | 6 +- server/src/plugins/composio.ts | 4 +- server/src/plugins/mcp.ts | 6 +- server/src/plugins/plugin-store/calls.ts | 8 +- server/src/plugins/plugin-store/context.ts | 18 +- server/src/plugins/route-groups/app-access.ts | 35 ++-- .../src/plugins/route-groups/connections.ts | 68 +++---- server/src/plugins/route-groups/grants.ts | 10 +- server/src/plugins/route-groups/skills.ts | 2 +- server/src/plugins/routes.ts | 2 +- server/src/plugins/shared-accounts.ts | 2 +- server/src/plugins/shared-use-store.ts | 2 +- server/src/routing/routes.ts | 6 +- server/src/team-bots/team-bots.ts | 8 +- server/src/voice/routes.ts | 43 ++-- server/src/voice/session-routes.ts | 14 +- server/src/voice/sessions.ts | 5 +- server/tests/agent-routes.test.ts | 12 +- server/tests/app-access-tool.test.ts | 2 +- server/tests/arcade-authorize-cancel.test.ts | 8 +- server/tests/attachment-routes.test.ts | 8 +- server/tests/audit.test.ts | 2 +- server/tests/channel-routes.test.ts | 29 ++- server/tests/composio-broker-seam.test.ts | 8 +- .../tests/composio-connection-kinds.test.ts | 2 +- server/tests/composio-transport.test.ts | 14 +- server/tests/computer-client.test.ts | 6 +- server/tests/computer-routes.test.ts | 2 +- server/tests/computer-target.test.ts | 8 +- server/tests/dictation-provider.test.ts | 2 +- .../enterprise-controls.integration.test.ts | 4 +- server/tests/gate-grant-is-local.test.ts | 2 +- server/tests/guards.test.ts | 2 +- server/tests/headless-components.test.ts | 2 +- server/tests/helpers/refusals.ts | 4 +- server/tests/memory-shared-source.test.ts | 2 +- ...pending-authorization-audit-ledger.test.ts | 3 +- server/tests/plugin-connect-route.test.ts | 8 +- server/tests/plugin-grants-validation.test.ts | 2 +- server/tests/plugin-routes.test.ts | 19 +- server/tests/plugin-store.integration.test.ts | 12 +- ...plugin-user-credential.integration.test.ts | 4 +- server/tests/routes-vendor-vocabulary.test.ts | 44 ++++- .../tests/routing-limits-validation.test.ts | 2 +- server/tests/shared-brokered-routes.test.ts | 6 +- .../shared-brokered-store.integration.test.ts | 6 +- server/tests/store-scheme-sites.test.ts | 2 +- server/tests/voice-session-routes.test.ts | 6 +- .../voice-session-store.integration.test.ts | 4 +- 259 files changed, 1616 insertions(+), 1562 deletions(-) create mode 100644 .claude/skills/openbot-copy/SKILL.md diff --git a/.claude/skills/openbot-copy/SKILL.md b/.claude/skills/openbot-copy/SKILL.md new file mode 100644 index 0000000000..17afc18ddb --- /dev/null +++ b/.claude/skills/openbot-copy/SKILL.md @@ -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 | diff --git a/CHANGELOG.md b/CHANGELOG.md index 3baa354ed8..da2b039241 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/app/src/components/admin/admin-sidebar.tsx b/app/src/components/admin/admin-sidebar.tsx index 0217dc3759..2b4a7d5d6c 100644 --- a/app/src/components/admin/admin-sidebar.tsx +++ b/app/src/components/admin/admin-sidebar.tsx @@ -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" }, }, diff --git a/app/src/components/agents/agent-profile.tsx b/app/src/components/agents/agent-profile.tsx index 6d77bf57d5..a7b87d77a6 100644 --- a/app/src/components/agents/agent-profile.tsx +++ b/app/src/components/agents/agent-profile.tsx @@ -57,7 +57,7 @@ export function AgentProfile({ agentId }: { agentId: string }) { if (agent.error || !agent.data) { return (

- Could not load this coworker. + Couldn't load this Bot.

); } @@ -103,7 +103,7 @@ export function AgentProfile({ agentId }: { agentId: string }) { void navigate({ search: { agent: agentId }, to: "/channel/new" }) } > - Start new channel + New conversation {hasToken ? ( ) : null} diff --git a/app/src/components/agents/handoff-panel.tsx b/app/src/components/agents/handoff-panel.tsx index 841ca870ef..d9d9b008f3 100644 --- a/app/src/components/agents/handoff-panel.tsx +++ b/app/src/components/agents/handoff-panel.tsx @@ -76,22 +76,21 @@ export function HandoffPanel({ agentId }: { agentId: string }) { ) : 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 : ( - 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. )} {grantable ? null : ( - 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. )} @@ -103,8 +102,8 @@ export function HandoffPanel({ agentId }: { agentId: string }) { {grantable && total === 0 ? ( - 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. ) : null} {candidates.length > 0 ? ( @@ -129,7 +128,7 @@ export function HandoffPanel({ agentId }: { agentId: string }) { */} {candidate.hidden - ? `${candidate.title} · hidden from your roster` + ? `${candidate.title} · hidden from your Bots list` : candidate.title} @@ -154,7 +153,7 @@ export function HandoffPanel({ agentId }: { agentId: string }) { ) : null} {canGrant ? null : ( - An administrator decides which Bots may be asked. + An admin decides which Bots this one can ask. )} ); diff --git a/app/src/components/agents/new-bot-sheet.tsx b/app/src/components/agents/new-bot-sheet.tsx index 4c8bb2bc60..47386372dc 100644 --- a/app/src/components/agents/new-bot-sheet.tsx +++ b/app/src/components/agents/new-bot-sheet.tsx @@ -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; @@ -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, }, ]; @@ -562,9 +562,7 @@ function AboutStep({ placeholder={runsHere ? "Finance Operations" : defaultTitle(runtime)} value={title} /> - - A few words, shown beneath its name in lists. - + A few words shown under its name. @@ -582,8 +580,8 @@ function AboutStep({ /> {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."}
@@ -686,8 +684,8 @@ function RuntimeStep({ Run it here {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."} @@ -787,8 +785,8 @@ function EndpointStep({ /> ) : ( - 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. )} @@ -806,8 +804,8 @@ function EndpointStep({ value={secret} /> - Sent as an Authorization header on every run, and kept - in the credential vault. + Sent as an Authorization header with every request, and + stored encrypted. diff --git a/app/src/components/agents/proposed-bot.tsx b/app/src/components/agents/proposed-bot.tsx index 34786c6eb4..0b64b45ffc 100644 --- a/app/src/components/agents/proposed-bot.tsx +++ b/app/src/components/agents/proposed-bot.tsx @@ -95,7 +95,7 @@ export function ProposedBotCard({ if (!respond) { return ( -

Writing the coworker…

+

Writing the Bot…

); } @@ -116,9 +116,7 @@ export function ProposedBotCard({ // Left on the card with the buttons still live. See `refusal` above. setSending(null); setRefusal( - cause instanceof Error - ? cause.message - : "The coworker was not created.", + cause instanceof Error ? cause.message : "The Bot wasn't created.", ); return; } @@ -139,8 +137,8 @@ export function ProposedBotCard({ action={Waiting on you} caption={ clashes - ? `There is already a ${clashes.title} here, which is ${clashes.ownership}. Names do not have to be unique, but two with the same name are hard to tell apart in a channel list.` - : "Nothing is created until you press the button." + ? `There's already a ${clashes.title}, and it's ${clashes.ownership === "the deployment's" ? "built into OpenBot" : clashes.ownership}. Two Bots with the same name are hard to tell apart.` + : "Nothing is created until you press Create it." } title={titleFor(values.name, clashes)} > @@ -162,7 +160,7 @@ export function ProposedBotCard({

- Its standing instructions. This text is given to a model on every turn. + Its instructions. It follows these every time it replies.

{/* * Scrolled rather than clamped, and the distinction is the whole point of the card. A clamp @@ -174,8 +172,7 @@ export function ProposedBotCard({

- It arrives private, with no address, no connector and no tool. Anything - it needs to reach is granted on its profile afterwards. + It starts private, with no apps. Apps are added on its page afterwards.

{refusal ? ( @@ -209,7 +206,7 @@ function titleFor( name: string | undefined, clashes: { title: string } | undefined, ): string { - const called = name?.trim() ? name.trim() : "a new coworker"; + const called = name?.trim() ? name.trim() : "a new Bot"; return clashes ? `Create ${called}, again` : `Create ${called}`; } @@ -233,7 +230,7 @@ function TalkToIt({ params={{ agentId }} to="/bots/$agentId" > - Open {name?.trim() || "the coworker"} + Open {name?.trim() || "the Bot"} ); } @@ -269,11 +266,10 @@ function Unwritable({ return ( Not created} - title="Create a coworker" + title="Create a Bot" >

- The coworker could not be created as written, so nothing was asked of - you. It is being redrafted. + This Bot couldn't be created as written. It's being rewritten.

); diff --git a/app/src/components/app-sidebar/app-sidebar.tsx b/app/src/components/app-sidebar/app-sidebar.tsx index 0189142f40..47e336b056 100644 --- a/app/src/components/app-sidebar/app-sidebar.tsx +++ b/app/src/components/app-sidebar/app-sidebar.tsx @@ -257,6 +257,7 @@ export function AppSidebar({ ...props }: React.ComponentProps) { )} /> ); } diff --git a/app/src/components/bot-profile/profile.tsx b/app/src/components/bot-profile/profile.tsx index 2fc72bd96a..dace529344 100644 --- a/app/src/components/bot-profile/profile.tsx +++ b/app/src/components/bot-profile/profile.tsx @@ -102,7 +102,7 @@ export function BotProfile({ ) : null} {lifecycle.isPending ? null : lifecycle.error ? (

- Could not load this Bot's state. + Couldn't load this Bot's settings. Reload the page.

) : ( @@ -116,7 +116,7 @@ export function BotProfile({ Active {lifecycle.data.paused - ? "Paused. No routine, responsibility, hand-off or follow-up starts for you, and what was running has stopped." + ? "Paused. Nothing runs for you until you turn it back on." : "Runs its routines, responsibilities, hand-offs and follow-ups for you."} diff --git a/app/src/components/bot-profile/setup.tsx b/app/src/components/bot-profile/setup.tsx index 70cb8d05b0..0a4ad5097f 100644 --- a/app/src/components/bot-profile/setup.tsx +++ b/app/src/components/bot-profile/setup.tsx @@ -125,8 +125,7 @@ export function SetupSections({ agent }: { agent: AgentProfile }) { System owned - Ships with this deployment rather than belonging to a - person. + Comes with OpenBot. It doesn't belong to a person. @@ -170,7 +169,7 @@ function VisibilityRow({ {error ?? (agent.visibility === "private" ? "Only you can see it and start conversations with it." - : "Everyone in the deployment can find and use it.")} + : "Everyone on OpenBot can find and use it.")} @@ -189,7 +188,7 @@ function VisibilityRow({ setError( failure instanceof Error ? failure.message - : "Could not save.", + : "Couldn't save. Try again.", ); } finally { setSaving(false); @@ -241,7 +240,9 @@ function EditTextDialog({ const submit = async () => { const parsed = schema.safeParse(draft); if (!parsed.success) { - setError(parsed.error.issues[0]?.message ?? "That value does not fit."); + setError( + parsed.error.issues[0]?.message ?? "Check this field and try again.", + ); return; } setSaving(true); @@ -249,7 +250,11 @@ function EditTextDialog({ await onSave(parsed.data); onClose(); } catch (failure) { - setError(failure instanceof Error ? failure.message : "Could not save."); + setError( + failure instanceof Error + ? failure.message + : "Couldn't save. Try again.", + ); } finally { setSaving(false); } @@ -321,14 +326,14 @@ function ConnectionSection({ agent }: { agent: AgentProfile }) { - {builtIn ? "Built in" : "Endpoint"} + {builtIn ? "Built in" : "Agent URL"} {builtIn - ? "Runs on this deployment's own Bot. Nothing to connect and nothing to authenticate." + ? "Runs inside OpenBot. There's nothing to connect." : agent.endpoint} diff --git a/app/src/components/bot-profile/sharing.tsx b/app/src/components/bot-profile/sharing.tsx index 5a2e7d38a0..16a69214f4 100644 --- a/app/src/components/bot-profile/sharing.tsx +++ b/app/src/components/bot-profile/sharing.tsx @@ -104,8 +104,8 @@ export function SharingSections({ agent }: { agent: AgentProfile }) { {published.visibleToTeam - ? "Each chat with it is private to the person having it." - : "Hidden from teammates until it has a real name and a description."} + ? "Each person's conversations with it are private." + : "Teammates can't see it until it has a real name and a description."} {agent.mine ? ( @@ -128,7 +128,9 @@ export function SharingSections({ agent }: { agent: AgentProfile }) { Link - Opens a private chat with it. + + Opens a private conversation with it. + } - description={`${agents.length} ${agents.length === 1 ? "Bot" : "Bots"} you took off this list. Nobody else's list changed.`} + description={`${agents.length} ${agents.length === 1 ? "Bot" : "Bots"} you hid. Only your list changes.`} title="Hidden" > {open ? : null} diff --git a/app/src/components/channels/app-access-card.tsx b/app/src/components/channels/app-access-card.tsx index d58164cdf9..c41443819d 100644 --- a/app/src/components/channels/app-access-card.tsx +++ b/app/src/components/channels/app-access-card.tsx @@ -144,6 +144,6 @@ function refusedSentence(access: AppAccess, prompt: ConnectPrompt): string { prompt.reason === "shared" && !access.requestKind ) - return `Your organisation's ${access.displayName} account needs reconnecting. Only an administrator can reconnect it.`; + return `Your organisation's ${access.displayName} account needs reconnecting. Only an admin can reconnect it.`; return appAccessSentence(access); } diff --git a/app/src/components/channels/channel-chat.tsx b/app/src/components/channels/channel-chat.tsx index 27f1047200..3bd769d2e2 100644 --- a/app/src/components/channels/channel-chat.tsx +++ b/app/src/components/channels/channel-chat.tsx @@ -107,13 +107,13 @@ export function channelHistoryNotice({ historyAvailability === "unavailable" && (historyReadFailed || (messageCount === 0 && lastMessageAt !== null)) ) { - return "Earlier messages are temporarily unavailable. You can keep using this conversation."; + return "Couldn't load earlier messages. You can keep using this conversation."; } if (unreadable > 0) { return unreadable === 1 - ? "One earlier message could not be read and is not shown. The rest of this conversation is complete." - : `${unreadable} earlier messages could not be read and are not shown. The rest of this conversation is complete.`; + ? "One earlier message couldn't be read, so it isn't shown. Everything else is here." + : `${unreadable} earlier messages couldn't be read, so they aren't shown. Everything else is here.`; } return null; @@ -651,7 +651,7 @@ export function ChannelChat({ ]) .catch(() => { setVoiceContextNotice( - "Earlier voice context wasn’t available for this message. The agent may need you to repeat details from the call.", + "Couldn’t bring in what was said on the call. You may need to repeat it.", ); return cachedVoiceArchive(channel.id); }) @@ -779,7 +779,7 @@ export function ChannelChat({ // Voice and typed requests share this turn boundary, including gaps between frontend tools. if (turnsRef.current > 0 || agentRef.current.isRunning) { throw new Error( - "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.", ); } @@ -968,7 +968,7 @@ export function ChannelChat({ /> {voiceArchive.error && (

- Voice chats couldn’t be loaded. Refresh to try again. + Couldn’t load voice chats. Reload the page.

)} {voiceContextNotice && ( @@ -992,8 +992,8 @@ export function ChannelChat({ className="pb-2 text-sm text-muted-foreground" role="status" > - This coworker has been deleted. The conversation stays - readable, but it can no longer reply. + This Bot was deleted. You can still read this conversation, + but the Bot can't reply.

)} diff --git a/app/src/components/channels/composer/attachment-strip.tsx b/app/src/components/channels/composer/attachment-strip.tsx index 6885b960e2..c14f769370 100644 --- a/app/src/components/channels/composer/attachment-strip.tsx +++ b/app/src/components/channels/composer/attachment-strip.tsx @@ -157,7 +157,7 @@ export function AttachmentStrip({ className="truncate text-muted-foreground text-xs" title={ file.mayTruncate === true - ? "The model reads the first 120,000 characters of this file." + ? "The Bot reads only the first 120,000 characters of this file." : undefined } > diff --git a/app/src/components/channels/composer/attachments.ts b/app/src/components/channels/composer/attachments.ts index 81b933acba..df2429a967 100644 --- a/app/src/components/channels/composer/attachments.ts +++ b/app/src/components/channels/composer/attachments.ts @@ -81,7 +81,9 @@ export function uploadToChannel( const body: unknown = await response.json().catch(() => undefined); // Named after the file rather than left empty, for every way this can go wrong. It is the only // sentence anybody gets: the composer keeps the string verbatim and shows it as the reason. - const couldNotUpload = new Error(`Could not upload "${file.name}".`); + const couldNotUpload = new Error( + `Couldn't upload "${file.name}". Try again.`, + ); if (!response.ok) { // The server phrases refusals in the product's voice — naming the mime type or the limit diff --git a/app/src/components/channels/composer/composer.tsx b/app/src/components/channels/composer/composer.tsx index f92acd582b..22d12e02bd 100644 --- a/app/src/components/channels/composer/composer.tsx +++ b/app/src/components/channels/composer/composer.tsx @@ -259,9 +259,8 @@ function overCap(draft: ComposerDraft): boolean { /** What the person is told when Send is held shut by the cap, and what to do about it. */ function tooManyStagedReason(count: number): string { return ( - `${count} attachments are staged and a message can carry at most ` + - `${MAX_ATTACHMENTS_PER_MESSAGE}. Remove ` + - `${count - MAX_ATTACHMENTS_PER_MESSAGE} to send the rest.` + `A message can take at most ${MAX_ATTACHMENTS_PER_MESSAGE} attachments. ` + + `Remove ${count - MAX_ATTACHMENTS_PER_MESSAGE} to send.` ); } @@ -274,11 +273,9 @@ function tooManyStagedReason(count: number): string { */ const DROPPED_REASON: Record = { "merged-over-cap": - "dropped when queued messages were merged into one: a " + - `message can carry at most ${MAX_ATTACHMENTS_PER_MESSAGE} ` + - "attachments.", - "queued-message-removed": - "removed along with the queued message it was attached to.", + "dropped when your queued messages were merged. A message can take " + + `at most ${MAX_ATTACHMENTS_PER_MESSAGE} attachments.`, + "queued-message-removed": "removed with its queued message.", }; /** @@ -306,11 +303,10 @@ const DROPPED_REASON: Record = { * second time. */ const UNACCEPTED_DROP_REASON = { - "cannot-send": - "was not attached: this conversation can no longer take messages.", + "cannot-send": "not attached. This conversation can no longer take messages.", "no-conversation": - "was not attached: there is no conversation here yet. Send this " + - "message first, then attach to the one it opens.", + "not attached. There is no conversation here yet. Send your message " + + "first, then attach files in the conversation it starts.", } as const; export function Composer({ @@ -1363,8 +1359,8 @@ export function Composer({ callAction.supported ? callAction.active ? "Show voice call" - : "Call this agent" - : "Voice calls need HTTPS or localhost and microphone support" + : "Call this Bot" + : "Voice calls need a microphone and a secure (HTTPS) connection" } className={actionClassName} disabled={disabled || dictation.busy || !callAction.supported} diff --git a/app/src/components/channels/composer/dictation-controls.tsx b/app/src/components/channels/composer/dictation-controls.tsx index d4e373119b..a1de0b6b86 100644 --- a/app/src/components/channels/composer/dictation-controls.tsx +++ b/app/src/components/channels/composer/dictation-controls.tsx @@ -29,7 +29,7 @@ export function DictationButton({ title={ dictation.supported ? "Dictate a message" - : "Dictation requires a supported browser and HTTPS (or localhost)" + : "Dictation needs a supported browser and a secure (HTTPS) connection" } className="size-8 shrink-0 self-end rounded-full" disabled={disabled || !dictation.supported || dictation.busy} diff --git a/app/src/components/channels/composer/picked-files.ts b/app/src/components/channels/composer/picked-files.ts index 0a50c15750..cb3654a71a 100644 --- a/app/src/components/channels/composer/picked-files.ts +++ b/app/src/components/channels/composer/picked-files.ts @@ -50,8 +50,8 @@ export function screenPickedFiles( reject( file, mediaType === "image/svg+xml" - ? `'${file.name}' is an SVG, which can carry scripts and is not accepted.` - : `'${file.name}' is an image format that is not supported.`, + ? `'${file.name}' can't be attached. SVG files aren't accepted.` + : `'${file.name}' isn't a supported image format.`, ), ); continue; @@ -76,7 +76,7 @@ export function screenPickedFiles( if (kind === "unsupported" && !unnamed) { rejected.push( - reject(file, `'${file.name}' is not a file type this chat accepts.`), + reject(file, `'${file.name}' isn't a file type you can attach.`), ); continue; } @@ -85,7 +85,7 @@ export function screenPickedFiles( rejected.push( reject( file, - `'${file.name}' is too large for an image attachment (limit ${formatBytes(MAX_IMAGE_BYTES)}).`, + `'${file.name}' is too large for an image. The limit is ${formatBytes(MAX_IMAGE_BYTES)}.`, ), ); continue; @@ -95,7 +95,7 @@ export function screenPickedFiles( rejected.push( reject( file, - `'${file.name}' is too large for a text attachment (limit ${formatBytes(MAX_FILE_BYTES)}).`, + `'${file.name}' is too large for a text file. The limit is ${formatBytes(MAX_FILE_BYTES)}.`, ), ); continue; @@ -108,7 +108,7 @@ export function screenPickedFiles( rejected.push( reject( file, - `'${file.name}' is larger than the ${formatBytes(MAX_IMAGE_BYTES)} limit for an attachment.`, + `'${file.name}' is too large. The limit is ${formatBytes(MAX_IMAGE_BYTES)}.`, ), ); continue; @@ -121,7 +121,7 @@ export function screenPickedFiles( rejected.push( reject( file, - `'${file.name}' was not added: a message can carry at most ${MAX_ATTACHMENTS_PER_MESSAGE} attachments.`, + `'${file.name}' wasn't added. A message can take at most ${MAX_ATTACHMENTS_PER_MESSAGE} attachments.`, ), ); continue; diff --git a/app/src/components/channels/composer/sources.ts b/app/src/components/channels/composer/sources.ts index 548ab5073d..5d3070a974 100644 --- a/app/src/components/channels/composer/sources.ts +++ b/app/src/components/channels/composer/sources.ts @@ -16,6 +16,6 @@ export const PLACEHOLDER_COMMANDS: CommandOption[] = [ name: "summarize", description: "Summarize this conversation", kind: "prompt", - prompt: "Summarize what we covered in this channel so far.", + prompt: "Summarize what we covered in this conversation so far.", }, ]; diff --git a/app/src/components/channels/composer/triggers.ts b/app/src/components/channels/composer/triggers.ts index 7a0a9d7a4c..f2cbef110c 100644 --- a/app/src/components/channels/composer/triggers.ts +++ b/app/src/components/channels/composer/triggers.ts @@ -54,9 +54,9 @@ function matches(query: string, ...fields: (string | undefined)[]): boolean { export function agentTrigger(agents: readonly AgentOption[]): TriggerConfig { return mentionTrigger({ char: AGENT_TRIGGER, - accessibilityLabel: "agent", + accessibilityLabel: "Bot", reopenOnChipClick: true, - emptyMessage: "No agents in this channel", + emptyMessage: "No Bots in this conversation", onSearch: (query): TriggerSuggestion[] => agents .filter((agent) => matches(query, agent.name, agent.description)) @@ -80,8 +80,8 @@ export function slashCommandTrigger( return commandTrigger({ char: COMMAND_TRIGGER, position: "start", - accessibilityLabel: "command", - emptyMessage: "No matching commands", + accessibilityLabel: "skill", + emptyMessage: "No matching skills", onSearch: (query): TriggerSuggestion[] => commands .filter((command) => matches(query, command.name, command.description)) diff --git a/app/src/components/channels/connect-app-card.tsx b/app/src/components/channels/connect-app-card.tsx index 8a8910c9db..d89372fa4b 100644 --- a/app/src/components/channels/connect-app-card.tsx +++ b/app/src/components/channels/connect-app-card.tsx @@ -90,8 +90,7 @@ export function ConnectAppCard({

) : needsKey ? (

- {title} is connected with a key you hold rather than by visiting a - page.{" "} + {title} needs a key from you.{" "} - {message.text || "This Bot could not answer."} + {message.text || "This Bot couldn't answer."}

) : ( People in this group - Everyone here sees the whole conversation, and every Bot in it - answers them. + Everyone here sees the whole conversation. Every Bot in it answers + them. diff --git a/app/src/components/channels/recipient-field.tsx b/app/src/components/channels/recipient-field.tsx index bb33f113e5..e97f7ae39f 100644 --- a/app/src/components/channels/recipient-field.tsx +++ b/app/src/components/channels/recipient-field.tsx @@ -61,9 +61,9 @@ export function RecipientField({ {isFull ? null : ( setSearch(event.target.value)} - placeholder="Choose a coworker…" + placeholder="Choose a Bot…" value={search} /> @@ -96,7 +96,7 @@ export function RecipientField({ ))} {matches.length === 0 ? (
  • - No coworker by that name. + No Bot by that name.
  • ) : null} diff --git a/app/src/components/channels/tool-activity.tsx b/app/src/components/channels/tool-activity.tsx index c03e5d2870..ff0c739336 100644 --- a/app/src/components/channels/tool-activity.tsx +++ b/app/src/components/channels/tool-activity.tsx @@ -426,7 +426,7 @@ function StepRow({ step, active }: { step: StepDetails; active: boolean }) { const reason = step.failed && step.kind === "browser" ? (

    - {step.reason ?? "This step could not be completed."} + {step.reason ?? "Couldn't finish this step."}

    ) : null; // A browser step says everything it has in its line. Anything else opens to what it was given diff --git a/app/src/components/channels/tool-boundary.tsx b/app/src/components/channels/tool-boundary.tsx index c53d6082d4..73a9637c01 100644 --- a/app/src/components/channels/tool-boundary.tsx +++ b/app/src/components/channels/tool-boundary.tsx @@ -30,8 +30,8 @@ export class ToolRenderBoundary extends Component< if (this.state.failed) { return (

    - {this.props.name} could not be - drawn. The rest of this conversation is unaffected. + Couldn't show {this.props.name}. + The rest of the conversation is fine.

    ); } diff --git a/app/src/components/channels/voice-call-widget.tsx b/app/src/components/channels/voice-call-widget.tsx index 292e33cbce..8d7389f980 100644 --- a/app/src/components/channels/voice-call-widget.tsx +++ b/app/src/components/channels/voice-call-widget.tsx @@ -223,7 +223,7 @@ export function VoiceCallWidget({ title={ state.muted ? "Unmute your microphone" - : "Mute your microphone · you’ll still hear the agent" + : "Mute your microphone · you’ll still hear the Bot" } variant="secondary" size="icon" @@ -264,7 +264,7 @@ export function VoiceCallWidget({
    {state.reply || state.transcript || - "Speak when you’re ready. This call uses the current chat."} + "Speak when you’re ready. This call is part of this conversation."}
    )} diff --git a/app/src/components/channels/voice-chat-card.tsx b/app/src/components/channels/voice-chat-card.tsx index 49de1874e6..1ae1b7244c 100644 --- a/app/src/components/channels/voice-chat-card.tsx +++ b/app/src/components/channels/voice-chat-card.tsx @@ -35,7 +35,7 @@ export function VoiceChatCard({ call }: { call: VoiceChatEntry }) { {call.transcript.map((entry) => (

    - {entry.role === "user" ? "You" : "Agent"} + {entry.role === "user" ? "You" : "Bot"}

    {entry.text} diff --git a/app/src/components/computer/activity-log.tsx b/app/src/components/computer/activity-log.tsx index 3a7a771919..417dc47b41 100644 --- a/app/src/components/computer/activity-log.tsx +++ b/app/src/components/computer/activity-log.tsx @@ -106,8 +106,7 @@ export function ActivityLog({ computerId }: { computerId: string }) { - Nothing yet. Commands the Bot runs, and files it reads, appear here - as they happen. + Nothing yet. Commands the Bot runs and files it reads appear here. diff --git a/app/src/components/computer/command-output.tsx b/app/src/components/computer/command-output.tsx index 502c61a5b4..741752b5a5 100644 --- a/app/src/components/computer/command-output.tsx +++ b/app/src/components/computer/command-output.tsx @@ -28,7 +28,7 @@ export function CommandOutput({

    {truncated ? (

    - Output was cut short at the start. What follows is the end of it. + The output was too long. This is the end of it.

    ) : null} {output ? ( diff --git a/app/src/components/computer/computer-view.tsx b/app/src/components/computer/computer-view.tsx index bd5efa58e9..4fc93e71b6 100644 --- a/app/src/components/computer/computer-view.tsx +++ b/app/src/components/computer/computer-view.tsx @@ -150,19 +150,17 @@ function NothingToSee({ ) : problem ? ( <> - - You cannot see the screen right now - + Can't show the screen right now {problem} - The assistant may still be working. An administrator can check - whether its computer is running. + The Bot may still be working. If this keeps happening, ask an admin + to check its Computer. ) : blankBrowser ? ( - The assistant has not opened a page yet. + The Bot hasn't opened a page yet. ) : ( - Waiting for the assistant's screen… + Waiting for the Bot's screen… )} ); @@ -345,7 +343,7 @@ export function ComputerView({ if (generation.current !== mine) return; if (!frame) { - setProblem(error ?? "The screen is not available right now."); + setProblem(error ?? "The screen isn't available right now."); } else { // Exact byte comparison is the settling signal. unchanged = frame.base64 === lastFrame ? unchanged + 1 : 0; @@ -430,7 +428,7 @@ export function ComputerView({ const polledScreen = showScreen ? ( What the assistant is looking at @@ -452,7 +450,7 @@ export function ComputerView({ */ className="relative block w-full cursor-pointer bg-muted" style={frameStyle} - aria-label="Open the assistant's screen full size" + aria-label="Open the Bot's screen full size" > {polledScreen} @@ -487,7 +485,7 @@ export function ComputerView({ {!driving && !settled && control?.requested ? (
    - The assistant needs you.{" "} + The Bot needs you.{" "} {control.reason}
    @@ -507,7 +505,7 @@ export function ComputerView({ ) : null} {!settled && control?.transitioning ? (

    - Finishing the assistant's current action before giving you control… + Finishing the Bot's current action before giving you control…

    ) : null} {/* @@ -516,14 +514,13 @@ export function ComputerView({ */} {!settled && control?.recoveryRequired ? (

    - The Computer stopped while you had control, so the assistant can't - use the browser yet. Take control to check the page, then hand it - back. + The Computer stopped while you had control. Take control to check + the page, then hand it back to the Bot.

    ) : !settled && control?.request?.status === "interrupted" ? (

    - The Computer stopped before anyone took control. The assistant will - ask again if it still needs you. + The Computer stopped before anyone took control. The Bot will ask + again if it still needs you.

    ) : null} {/* @@ -547,7 +544,7 @@ export function ComputerView({ }} >
    @@ -559,7 +556,7 @@ export function ComputerView({ autoComplete="off" autoCorrect="off" spellCheck={false} - placeholder="Typed here, never shown to the assistant" + placeholder="Never shown to the Bot" className="min-w-0 flex-1 rounded-md border bg-background px-2 py-1 text-sm" />

    - This goes straight to the page. It is not shown in the - conversation and the assistant never receives it. + This goes straight to the page. It isn't shown in the + conversation, and the Bot never sees it.

    {secretProblem ? (

    {secretProblem}

    @@ -589,7 +586,7 @@ export function ComputerView({ ? "Finishing the current action…" : driving ? "You have control. Open the screen to click and type." - : "The assistant has control."} + : "The Bot has control."} {/* @@ -617,7 +614,7 @@ export function ComputerView({ ) : undefined } - caption={`Counted from this deployment's audit trail, last ${data.days} days`} + caption={`Counted from OpenBot's audit trail, last ${data.days} days`} title={title ?? "Bot activity"} >
      @@ -226,7 +223,7 @@ function RefusalList({ return (

      - This deployment has refused nothing. + OpenBot hasn't refused anything.

      ); @@ -249,7 +246,7 @@ function RefusalList({ ) : undefined } - caption="Read from this deployment's audit trail" + caption="Read from OpenBot's audit trail" title={title ?? "Recent refusals"} >
        diff --git a/app/src/components/gallery/decisions.tsx b/app/src/components/gallery/decisions.tsx index 208d0f3cfe..bca8064f03 100644 --- a/app/src/components/gallery/decisions.tsx +++ b/app/src/components/gallery/decisions.tsx @@ -56,7 +56,7 @@ export function ApprovalCard(props: Waiting & { name?: string }) { if (status === "inProgress") { return ( - +

        Preparing the request…

        ); @@ -155,7 +155,7 @@ export function ChoiceCard(props: Waiting) { if (status === "inProgress") { return ( - +

        Preparing the question…

        ); diff --git a/app/src/components/gallery/form.tsx b/app/src/components/gallery/form.tsx index 75d1065103..b3aa6a2295 100644 --- a/app/src/components/gallery/form.tsx +++ b/app/src/components/gallery/form.tsx @@ -80,7 +80,7 @@ export function FormCard(props: Record) { return (

        - The assistant is putting the questions together. + The Bot is putting the questions together.

        ); @@ -138,7 +138,7 @@ function InvalidForm({

        {returned ? "The form had invalid questions. The Bot can send a corrected form." - : "The form has invalid questions and could not be displayed."} + : "The form has invalid questions, so it can't be shown."}

        {failed && ( } - description="Each app you connected in your own account that this Bot can read becomes a source on its own, and you can add more. Enabled sources refresh every 15 minutes, and their facts are available to this Bot only. Turn one off or remove it and it stays that way." - title="Connected app sources" + description="Apps you connected that this Bot can read show up here, and you can add more. Sources refresh every 15 minutes. Only this Bot sees what they hold." + title="App sources" > {sources.isPending ? null : sources.error ? (

        {sources.error.message}

        ) : shown.length === 0 ? ( - No connected app sources yet. + No sources yet. Add one with Add source. ) : ( {shown.map((source, index) => ( @@ -139,7 +139,7 @@ export function MemorySources({ agentId: fixedBot }: { agentId?: string }) { ? source.syncStatus === "succeeded" ? "Up to date" : source.syncStatus - : "Disabled"} + : "Turned off"} {source.lastSyncAt ? ` · Last read ${new Date(source.lastSyncAt).toLocaleString()}` : ""} @@ -203,7 +203,7 @@ export function MemorySources({ agentId: fixedBot }: { agentId?: string }) { Add a source - It is read now, and then every 15 minutes while it is enabled. + OpenBot reads it now, then every 15 minutes while it's on. @@ -235,7 +235,7 @@ export function MemorySources({ agentId: fixedBot }: { agentId?: string }) { .filter(Boolean); else if (setting.schema.type === "object") throw new Error( - "Choose an action with simple search settings.", + "This one has settings OpenBot can't show. Choose another.", ); else values[setting.name] = value; } @@ -293,9 +293,7 @@ export function MemorySources({ agentId: fixedBot }: { agentId?: string }) { ) : null} - - Connected app action - + What to read @@ -76,7 +76,7 @@ export function MessageListPreference() { size="sm" onClick={() => void preferences.refetch()} > - Retry + Try again
    ) : null} diff --git a/app/src/components/settings/standing-instructions.tsx b/app/src/components/settings/standing-instructions.tsx index c4ea2c32e2..9287797fc4 100644 --- a/app/src/components/settings/standing-instructions.tsx +++ b/app/src/components/settings/standing-instructions.tsx @@ -57,7 +57,7 @@ export function StandingInstructions() { return ( {stored.isPending ? null : stored.error ? ( diff --git a/app/src/components/settings/team-bot-consents.tsx b/app/src/components/settings/team-bot-consents.tsx index 7fa6e16b07..e4e6ba3b1a 100644 --- a/app/src/components/settings/team-bot-consents.tsx +++ b/app/src/components/settings/team-bot-consents.tsx @@ -21,7 +21,7 @@ export function TeamBotConsents() { const clear = useMutation(clearTeamBotConsentsMutationOptions()); return ( {clear.error ? ( diff --git a/app/src/components/settings/update-routing.tsx b/app/src/components/settings/update-routing.tsx index 1bf932b33d..1cd447e1a8 100644 --- a/app/src/components/settings/update-routing.tsx +++ b/app/src/components/settings/update-routing.tsx @@ -24,7 +24,7 @@ import { queryClient } from "@/query-client"; const KIND_LABEL: Record = { progress: { title: "Progress", - description: "Replies and results from work you were not watching.", + description: "Replies and results from work you weren't watching.", }, decision: { title: "Decisions", @@ -62,12 +62,12 @@ export function UpdateRoutingSection() { reach.data ? reach.data.available[transport] : true; return ( {routing.isPending || reach.isPending ? null : routing.error ? (

    - Could not load where your updates go. + Couldn't load where your updates go. Reload the page.

    ) : ( diff --git a/app/src/components/skills/edit-skill.tsx b/app/src/components/skills/edit-skill.tsx index 7d20243188..a57e9615e7 100644 --- a/app/src/components/skills/edit-skill.tsx +++ b/app/src/components/skills/edit-skill.tsx @@ -38,7 +38,7 @@ export function EditSkill({ slug }: { slug: string }) { return (

    - This skill could not be loaded. + Couldn't load this skill. Reload the page.

    ); @@ -54,7 +54,7 @@ export function EditSkill({ slug }: { slug: string }) { * not own. */}

    - That skill no longer exists, or it is not yours to edit. + This skill was deleted, or it isn't yours to edit.

    ); @@ -65,8 +65,8 @@ export function EditSkill({ slug }: { slug: string }) {

    Edit skill

    - Changes apply the next time /{skill.slug} is used. Agents - already carrying it keep it. + Changes apply the next time someone uses /{skill.slug}. + Bots that have it keep it.

    diff --git a/app/src/components/skills/new-skill.tsx b/app/src/components/skills/new-skill.tsx index 48b8ca9090..2f1674bb39 100644 --- a/app/src/components/skills/new-skill.tsx +++ b/app/src/components/skills/new-skill.tsx @@ -21,8 +21,8 @@ export function NewSkill() {

    New skill

    - A named instruction you invoke with /. It goes on the - Bots you own, and nobody else sees it. + An instruction you start with /. Only you see it, and it + works on Bots you own.

    diff --git a/app/src/components/skills/proposed-skill.tsx b/app/src/components/skills/proposed-skill.tsx index 1f914f15ee..9a84265e57 100644 --- a/app/src/components/skills/proposed-skill.tsx +++ b/app/src/components/skills/proposed-skill.tsx @@ -94,7 +94,7 @@ export function ProposedSkillCard({ // Left on the card with the buttons still live. See `refusal` above. setSending(null); setRefusal( - cause instanceof Error ? cause.message : "The skill was not saved.", + cause instanceof Error ? cause.message : "Couldn't save the skill.", ); return; } @@ -112,7 +112,7 @@ export function ProposedSkillCard({ caption={ replaces ? `This replaces ${replaces.title}, which is ${replaces.ownership}.` - : "Nothing is saved until you press the button." + : "Nothing is saved until you say so." } title={titleFor(values.slug, replaces)} > @@ -145,8 +145,8 @@ export function ProposedSkillCard({ {values.tools.length > 0 ? (

    - Naming a tool grants nothing. A Bot is offered these only if an - administrator has already granted them to it. + Naming a tool doesn't give access. A Bot can only use these if it + already has them.

    ) : null} @@ -236,8 +236,8 @@ function Unwritable({ title="Skill" >

    - The Bot's draft was not a valid skill, so nothing was saved. It has been - told what to fix. + The Bot's draft wasn't a valid skill. Nothing was saved, and the Bot + knows what to fix.

    ); diff --git a/app/src/components/skills/skill-agents.tsx b/app/src/components/skills/skill-agents.tsx index 1603f5d85d..6f55ad7f4f 100644 --- a/app/src/components/skills/skill-agents.tsx +++ b/app/src/components/skills/skill-agents.tsx @@ -36,10 +36,10 @@ export function SkillAgents({ return (
    -

    Agents

    +

    Bots

    {mine.length === 0 ? (

    - You do not own an Agent to put this on yet. + You don't have a Bot of your own yet.

    ) : ( <> @@ -66,7 +66,8 @@ export function SkillAgents({ })}

    - An Agent carrying this offers /{slug} in its composer. + Bots you pick here offer /{slug} when you type{" "} + /.

    )} diff --git a/app/src/components/skills/skill-fields.tsx b/app/src/components/skills/skill-fields.tsx index 8f6fd8efbe..651cb2d16e 100644 --- a/app/src/components/skills/skill-fields.tsx +++ b/app/src/components/skills/skill-fields.tsx @@ -91,7 +91,7 @@ export function SkillFields({ ) : (

    {slugLocked ? ( - "A command cannot be changed. To rename a skill, write a new one and delete this." + "You can't change a command. To rename this skill, write a new one and delete this one." ) : ( <> What you type after a slash.{" "} @@ -174,15 +174,15 @@ export function SkillFields({ name={field.name} onBlur={field.handleBlur} onChange={(event) => field.handleChange(event.target.value)} - placeholder="Summarise what I did yesterday from the channel, then list what is left." + placeholder="Summarise what I did yesterday in this conversation, then list what is left." value={field.state.value} /> {isInvalid ? ( ) : (

    - Added to the run when the command is used. Write it as - instructions to the Bot, not as a description of them. + What the Bot is told when you use the command. Write it as + instructions to the Bot.

    )} diff --git a/app/src/components/skills/skill-tools.tsx b/app/src/components/skills/skill-tools.tsx index ec6df54a16..acaa8c444d 100644 --- a/app/src/components/skills/skill-tools.tsx +++ b/app/src/components/skills/skill-tools.tsx @@ -82,12 +82,12 @@ export function SkillTools({ {plugins.isPending ? null : plugins.error ? (

    - Could not load the tools this deployment has. + Couldn't load the tools. Reload the page.

    ) : servers.length === 0 ? (

    - No connected server offers a tool yet. A skill can still be written — - most are instructions rather than tool use. + No app offers tools yet. You can still write the skill. Most skills + are only instructions.

    ) : (
    @@ -143,7 +143,7 @@ export function SkillTools({ key={role.ref} onClick={() => toggle(role.ref)} size="sm" - title={`${role.ref} — on whichever research connector the Bot holds`} + title={`${role.ref}: uses whichever research app the Bot has`} type="button" variant={held.has(role.ref) ? "default" : "outline"} > @@ -156,7 +156,7 @@ export function SkillTools({ {elsewhere.length > 0 ? (
    -

    Not connected here

    +

    Not available here

    {elsewhere.map((ref) => (

    - This skill names these, and no server connected here offers them — - because the connector has not been added, or was removed. They cost - nothing and load nothing until it exists. Click one to stop naming - it. + This skill asks for these, but no app here offers them. They do + nothing until an admin adds the app. Click one to remove it.

    ) : null}

    - Picking this skill is what loads these tools for a turn. It does not - grant them — a Bot still only calls what it was granted, so naming a - tool here gives nobody access to it. + Using this skill loads these tools for that reply. It doesn't give + access. A Bot can only use tools it already has.

    ); diff --git a/app/src/lib/agents/mutations.ts b/app/src/lib/agents/mutations.ts index 639f6961e8..760f833c17 100644 --- a/app/src/lib/agents/mutations.ts +++ b/app/src/lib/agents/mutations.ts @@ -19,7 +19,7 @@ export type AgentInput = { }; /** The sentence for every write here, since they all fail the same way to a reader. */ -const FALLBACK = "Coworker operation failed"; +const FALLBACK = "Couldn't save that change to the Bot."; /** Server-derived fields are invalidated instead of patched by hand. */ function invalidateAgents(queryClient: QueryClient) { diff --git a/app/src/lib/agents/queries.ts b/app/src/lib/agents/queries.ts index 326d006485..182ef08541 100644 --- a/app/src/lib/agents/queries.ts +++ b/app/src/lib/agents/queries.ts @@ -118,7 +118,7 @@ export function agentListQueryOptions(hidden = false) { queryKey: agentKeys.list(hidden), queryFn: (): Promise => client(`/api/agents${hidden ? "?hidden=true" : ""}`, "agents", { - fallback: "Could not load coworkers", + fallback: "Couldn't load your Bots.", }), }); } @@ -133,7 +133,7 @@ export function agentQueryOptions(agentId: string) { queryKey: agentKeys.detail(agentId), queryFn: (): Promise => client(agentApiPath(agentId), "agent", { - fallback: "Could not load this coworker", + fallback: "Couldn't load this Bot.", }), }); } diff --git a/app/src/lib/channels/mutations.ts b/app/src/lib/channels/mutations.ts index 7665c9b823..f837bb7faa 100644 --- a/app/src/lib/channels/mutations.ts +++ b/app/src/lib/channels/mutations.ts @@ -18,7 +18,7 @@ export function createChannelMutationOptions(queryClient: QueryClient) { const response = await client("/api/channels", { method: "POST", body: { agentIds }, - fallback: "Could not start a channel", + fallback: "Couldn't start a conversation.", }); return ((await response.json()) as { channel: AgentChannel }).channel; }, @@ -84,7 +84,7 @@ export function setChannelPinnedMutationOptions(queryClient: QueryClient) { await client(`/api/channels/${variables.channelId}/pin`, { method: "PUT", body: { pinned: variables.pinned }, - fallback: "Could not pin this channel", + fallback: "Couldn't pin this conversation.", }); }, onSuccess: () => @@ -107,7 +107,7 @@ export function markChannelReadMutationOptions(queryClient: QueryClient) { mutationFn: async (channelId: string) => { await client(`/api/channels/${channelId}/read`, { method: "PUT", - fallback: "Could not mark this channel read", + fallback: "Couldn't mark this conversation as read.", }); }, onSuccess: () => @@ -150,7 +150,7 @@ export function deleteChannelMutationOptions(queryClient: QueryClient) { mutationFn: async (channelId: string) => { await client(`/api/channels/${channelId}`, { method: "DELETE", - fallback: "Could not delete this channel", + fallback: "Couldn't delete this conversation.", }); }, // The roster only. The open channel's detail query would refetch into the fresh 404 and diff --git a/app/src/lib/channels/queries.ts b/app/src/lib/channels/queries.ts index bcd2f6e925..e8ef1a2f08 100644 --- a/app/src/lib/channels/queries.ts +++ b/app/src/lib/channels/queries.ts @@ -77,7 +77,7 @@ export function channelListQueryOptions() { ? `?cursor=${encodeURIComponent(pageParam as string)}` : ""; const response = await client(`/api/channels${suffix}`, { - fallback: "Could not load channels", + fallback: "Couldn't load your conversations.", }); return (await response.json()) as ChannelPage; }, @@ -92,7 +92,7 @@ export function channelQueryOptions(channelId: string) { queryKey: channelKeys.detail(channelId), queryFn: async (): Promise => { return client(`/api/channels/${channelId}`, "channel", { - fallback: "Could not load this channel", + fallback: "Couldn't load this conversation.", }); }, }); diff --git a/app/src/lib/channels/route.ts b/app/src/lib/channels/route.ts index a214429a0c..59ef14f690 100644 --- a/app/src/lib/channels/route.ts +++ b/app/src/lib/channels/route.ts @@ -62,9 +62,7 @@ export async function routeMessage( names?: unknown; } | null; const message = - typeof body?.error === "string" - ? body.error - : "Could not choose a coworker."; + typeof body?.error === "string" ? body.error : "Couldn't pick a Bot."; if ( response.status === 409 && Array.isArray(body?.names) && diff --git a/app/src/lib/client.ts b/app/src/lib/client.ts index 2e92b282b4..14a5f1b945 100644 --- a/app/src/lib/client.ts +++ b/app/src/lib/client.ts @@ -20,7 +20,7 @@ export type ClientOptions = { /** * What a person reads when the server sent no message of its own. * - * Name the entity in it — "Could not load coworkers" rather than "Request failed" — because this + * Name the entity in it — "Couldn't load your Bots." rather than "Request failed" — because this * is the sentence that reaches the screen when the server is the one that broke. */ fallback?: string; diff --git a/app/src/lib/computers/control.ts b/app/src/lib/computers/control.ts index 9b1a5325a5..f7ee332f7d 100644 --- a/app/src/lib/computers/control.ts +++ b/app/src/lib/computers/control.ts @@ -84,11 +84,14 @@ export async function supplySecret( const body = (await response.json().catch(() => null)) as { error?: string; } | null; - return { ok: false, error: body?.error ?? "That could not be entered." }; + return { + ok: false, + error: body?.error ?? "Couldn't type that into the page. Try again.", + }; } catch { return { ok: false, - error: "The assistant's computer could not be reached.", + error: "Couldn't reach the Bot's computer. Try again.", }; } } diff --git a/app/src/lib/plugins/grants.ts b/app/src/lib/plugins/grants.ts index da331695c8..890a570fe1 100644 --- a/app/src/lib/plugins/grants.ts +++ b/app/src/lib/plugins/grants.ts @@ -35,7 +35,7 @@ export function grantsQueryOptions(serverId: string, enabled: boolean) { queryFn: async (): Promise => { const response = await client( `/api/plugins/servers/${encodeURIComponent(serverId)}/grants`, - { fallback: "What this account holds could not be read." }, + { fallback: "Couldn't read what this account allows." }, ); const body: { actions?: unknown } = await response.json(); return Array.isArray(body.actions) ? (body.actions as GrantState[]) : []; @@ -62,7 +62,7 @@ export function authorizeActionMutationOptions( { method: "POST", body: { action: variables.action }, - fallback: "That action could not be authorized.", + fallback: "Couldn't authorize that action.", }, ); return authorizationUrl ?? null; diff --git a/app/src/lib/plugins/mutations.ts b/app/src/lib/plugins/mutations.ts index f933d8f954..c448077e42 100644 --- a/app/src/lib/plugins/mutations.ts +++ b/app/src/lib/plugins/mutations.ts @@ -52,7 +52,7 @@ export type CustomServerInput = { /** Which kinds of plugin a grant can be about. */ export type PluginKind = "mcp" | "skill"; -const FALLBACK = "That did not work."; +const FALLBACK = "That didn't work. Try again."; /** * Refetch everything the plugin screens read. @@ -123,7 +123,7 @@ export function grantPlugin(variables: { agentId: variables.agentId, ...(variables.approval ? { approval: variables.approval } : {}), }, - fallback: "That Agent could not be changed.", + fallback: "Couldn't change that Bot.", }); } @@ -214,7 +214,7 @@ export function setPluginGrantMutationOptions(queryClient: QueryClient) { } await client( `/api/plugins/grants?kind=${variables.kind}&ref=${encodeURIComponent(variables.ref)}&agentId=${encodeURIComponent(variables.agentId)}`, - { method: "DELETE", fallback: "That Agent could not be changed." }, + { method: "DELETE", fallback: "Couldn't change that Bot." }, ); }, onSettled: () => invalidatePlugins(queryClient), @@ -318,7 +318,7 @@ export function saveSkillMutationOptions(queryClient: QueryClient) { * The server refuses for reasons a form cannot check — a slug somebody else already owns is * the common one — and paraphrasing that would throw away the only part worth reading. */ - fallback: "The skill could not be saved.", + fallback: "Couldn't save the skill.", }), onSettled: () => invalidatePlugins(queryClient), }); @@ -407,7 +407,7 @@ export function connectAccountMutationOptions( const authorizationUrl = await client( `/api/plugins/servers/${encodeURIComponent(serverId)}/connect?returnTo=${returnTo}`, "authorizationUrl", - { method: "POST", fallback: "That account could not be connected." }, + { method: "POST", fallback: "Couldn't connect that account." }, ); return authorizationUrl ?? null; }, @@ -450,7 +450,7 @@ export function confirmBrokeredConnectionMutationOptions( ): Promise => { const response = await client( `/api/plugins/servers/${encodeURIComponent(serverId)}/connection/confirm`, - { method: "POST", fallback: "That connection could not be confirmed." }, + { method: "POST", fallback: "Couldn't confirm that connection." }, ); return response.json(); }, @@ -472,7 +472,7 @@ export function disconnectBrokeredMutationOptions(queryClient: QueryClient) { `/api/plugins/servers/${encodeURIComponent(serverId)}/connection`, { method: "DELETE", - fallback: "That account could not be disconnected.", + fallback: "Couldn't disconnect that account.", }, ); }, @@ -534,7 +534,7 @@ export function brokeredConnectionFieldsMutationOptions() { "fields", { method: "POST", - fallback: "That app could not be asked what it needs.", + fallback: "Couldn't find out what this app needs.", }, ); return fields ?? null; @@ -597,7 +597,7 @@ export function connectBrokeredWithFieldsMutationOptions( { method: "POST", body: { values: variables.values }, - fallback: "That account could not be connected.", + fallback: "Couldn't connect that account.", }, ); return response.json(); @@ -649,7 +649,7 @@ export function recheckBrokeredConnectionMutationOptions( mutationFn: async (serverId: string): Promise => { const response = await client( `/api/plugins/servers/${encodeURIComponent(serverId)}/connection/recheck`, - { method: "POST", fallback: "That connection could not be checked." }, + { method: "POST", fallback: "Couldn't check that connection." }, ); return response.json(); }, diff --git a/app/src/lib/plugins/provider-vocabulary.ts b/app/src/lib/plugins/provider-vocabulary.ts index 9a0e2a9147..deecac7020 100644 --- a/app/src/lib/plugins/provider-vocabulary.ts +++ b/app/src/lib/plugins/provider-vocabulary.ts @@ -52,7 +52,7 @@ const VOCABULARY: Record = { envVar: "COMPOSIO_API_KEY", directoryPath: "/admin/plugins/composio", directoryDescription: - "A few hundred apps, reached as whoever is asking. Each person connects their own account.", + "A few hundred apps. Each person connects their own account.", }, arcade: { name: "Arcade", @@ -60,7 +60,7 @@ const VOCABULARY: Record = { envVar: "ARCADE_API_KEY", directoryPath: "/admin/plugins/arcade", directoryDescription: - "A directory this deployment has not reviewed, reached as whoever is asking. Each person connects their own account.", + "Apps nobody here has reviewed. Each person connects their own account.", }, }; @@ -77,12 +77,12 @@ export function providerVocabulary( * fallback once spelled a vendor on its own and printed Composio's name on an Arcade deployment. */ export function directoryReadFailure(provider: PluginsProvider): string { - return `${VOCABULARY[provider].name}'s app directory could not be read.`; + return `Couldn't load ${VOCABULARY[provider].name}'s app directory. Reload the page.`; } /** The "More apps" section's own sentence. */ export function moreAppsSectionDescription(provider: PluginsProvider): string { - return `A directory this deployment has not reviewed, reached through ${VOCABULARY[provider].name}.`; + return `Apps from ${VOCABULARY[provider].name}. Nobody here has reviewed them.`; } /** @@ -108,7 +108,7 @@ export function moreAppsRow( if (!configured) { return { title: name, - description: `Add your ${name} key to enable a catalogue of tools. Set ${envVar} on this deployment.`, + description: `Not set up. Set ${envVar} on this deployment to browse its apps.`, linked: false, }; } @@ -133,42 +133,27 @@ export function brokeredReach(provider: PluginsProvider): { } { const { name } = VOCABULARY[provider]; return { - description: `This app is reached through ${name}, which holds the account. This deployment sends one key and the name of whoever is asking, so each person connects their own and a Bot sees only what that person can see. There is no token to paste.`, + description: `${name} holds the account. This deployment sends one key and who is asking. Each person connects their own, and a Bot sees only what that person can see. There is no token to paste.`, badge: `Through ${name}`, }; } /** What disconnecting a brokered account does, said where the button is. */ export function brokeredDisconnectNote(provider: PluginsProvider): string { - return `Disconnecting ends the account at ${VOCABULARY[provider].name}, not just here.`; + return `Disconnecting also ends the account at ${VOCABULARY[provider].name}.`; } /** - * The line under a brokered app's own page title on a person's connected-accounts screen. + * What an admin's own brokered row says where the broker authorizes PER ACTION, in place of a + * consent trip. * - * NOT {@link brokeredConnectionDescription}, which is the admin Connection card's sentence and says - * something different ("each person's own account"). This one speaks to the person reading their - * own page: the broker holds the account, so a Bot sees only what they can see. - */ -export function brokeredAccountPageDescription( - provider: PluginsProvider, -): string { - return `Reached through ${VOCABULARY[provider].name}, which holds the account, so a Bot sees only what you can see.`; -} - -/** What Connect does on a consent app, said before the press: where it goes first. */ -export function brokeredConsentTrip(provider: PluginsProvider): string { - return `Connecting takes you to ${VOCABULARY[provider].name} and then to the vendor to consent.`; -} - -/** - * What stands in for {@link brokeredConsentTrip} where the broker authorizes PER ACTION. + * NOT A CONSENT TRIP WITH A WORD CHANGED, because it is not the same offer. Under a per-action broker + * there is no whole-app press to name — the row withholds Connect entirely — so this says where the + * act actually is instead. A person reading "Connecting takes you to…" beside no Connect button is + * being told about a button that is not there. * - * NOT THE SAME SENTENCE WITH A WORD CHANGED, because it is not the same offer. The consent trip - * names a press and says where it goes; under a per-action broker there is no whole-app press to - * name — the row withholds Connect entirely — so this says where the act actually is instead. A - * person reading "Connecting takes you to…" beside no Connect button is being told about a button - * that is not there. + * The person's own page under Connections names no vendor, so it says this in its own words; the + * admin setup page may name the broker, and reads it from here. * * `where` IS NOT DECORATION AND HAS NO DEFAULT. The two screens that draw this row do not both * draw the action list: the Connections page has it directly underneath, and the admin setup page @@ -181,7 +166,7 @@ export function brokeredPerActionTrip( provider: PluginsProvider, where: "below" | "settings", ): string { - const stem = `${VOCABULARY[provider].name} approves one action at a time, so there is nothing to connect for the whole app.`; + const stem = `${VOCABULARY[provider].name} approves one action at a time, so there's nothing to connect for the whole app.`; return where === "below" ? `${stem} Authorize the actions you want below.` : `${stem} Authorize them from Connections.`; @@ -189,7 +174,7 @@ export function brokeredPerActionTrip( /** What a person is told where the deployment has no key for the active broker. */ export function brokeredKeyMissing(provider: PluginsProvider): string { - return `Set ${VOCABULARY[provider].envVar} on this deployment. Without it there is no broker to reach, so this account can be neither connected nor ended from here. The app stays enabled and every grant on its tools still stands.`; + return `Set ${VOCABULARY[provider].envVar} on this deployment. Until then, this account can't be connected or disconnected here. The app stays turned on, and its tool grants still stand.`; } /** @@ -218,8 +203,8 @@ export function disconnectSiblingsNotice(answer: { if (unanswered > 0) { parts.push( siblings.length > 0 - ? `${unanswered} other ${unanswered === 1 ? "app" : "apps"} could not be checked and may be affected too.` - : `${unanswered} ${unanswered === 1 ? "app" : "apps"} on this connection could not be checked, so disconnecting may end ${unanswered === 1 ? "it" : "them"} too.`, + ? `${unanswered} other ${unanswered === 1 ? "app" : "apps"} couldn't be checked and may be affected too.` + : `${unanswered} ${unanswered === 1 ? "app" : "apps"} on this connection couldn't be checked, so disconnecting may end ${unanswered === 1 ? "it" : "them"} too.`, ); } return parts.length > 0 ? parts.join(" ") : null; diff --git a/app/src/lib/plugins/queries.ts b/app/src/lib/plugins/queries.ts index 28e08e5609..c3a42ccbaf 100644 --- a/app/src/lib/plugins/queries.ts +++ b/app/src/lib/plugins/queries.ts @@ -442,7 +442,7 @@ export function connectionsQueryOptions() { queryKey: pluginKeys.connections(), queryFn: async (): Promise => { const response = await client("/api/plugins/connections", { - fallback: "Your connected accounts could not be loaded.", + fallback: "Couldn't load your connections.", }); return response.json(); }, @@ -465,7 +465,7 @@ export function connectionSiblingsQueryOptions(serverId: string) { const response = await client( `/api/plugins/servers/${encodeURIComponent(serverId)}/connection/siblings`, { - fallback: "Which other apps share this connection could not be read.", + fallback: "Couldn't read which other apps share this connection.", }, ); const body: Partial = await response.json(); @@ -529,7 +529,7 @@ export function agentPluginsQueryOptions(agentId: string) { queryFn: async (): Promise => { const response = await client( `/api/plugins/for/${encodeURIComponent(agentId)}`, - { fallback: "This Bot's plugins could not be read." }, + { fallback: "Couldn't load this Bot's apps." }, ); return response.json(); }, diff --git a/app/src/lib/plugins/shared-use.ts b/app/src/lib/plugins/shared-use.ts index 358646caa1..8a2010e197 100644 --- a/app/src/lib/plugins/shared-use.ts +++ b/app/src/lib/plugins/shared-use.ts @@ -53,7 +53,7 @@ export function sharedUseRequestsQueryOptions(enabled: boolean) { queryKey: sharedUseKeys.requests(), queryFn: () => client("/api/approvals/shared-use", "requests", { - fallback: "Shared account requests could not be loaded.", + fallback: "Couldn't load shared account requests.", }), enabled, refetchInterval: 10_000, @@ -66,7 +66,7 @@ export function botSharedAppsQueryOptions(botId: string) { queryFn: async (): Promise => { const response = await client( `/api/approvals/shared-use/bot/${encodeURIComponent(botId)}`, - { fallback: "This Bot's shared apps could not be loaded." }, + { fallback: "Couldn't load this Bot's shared apps." }, ); return response.json(); }, @@ -84,7 +84,7 @@ export function decideSharedUseMutationOptions(queryClient: QueryClient) { }) => client( `/api/approvals/shared-use/${encodeURIComponent(id)}/${decision}`, - { method: "POST", fallback: "That decision could not be saved." }, + { method: "POST", fallback: "Couldn't save that decision." }, ), onSettled: () => queryClient.invalidateQueries({ queryKey: ["approvals"] }), }); @@ -100,7 +100,7 @@ export function requestSharedUseMutationOptions(queryClient: QueryClient) { client("/api/approvals/shared-use", { method: "POST", body, - fallback: "The request could not be sent.", + fallback: "Couldn't send the request.", }), onSettled: (_data, _error, body) => queryClient.invalidateQueries({ diff --git a/app/src/lib/skills/form.ts b/app/src/lib/skills/form.ts index 2346f722df..b570056cf8 100644 --- a/app/src/lib/skills/form.ts +++ b/app/src/lib/skills/form.ts @@ -34,7 +34,7 @@ export const skillFormSchema = z.object({ instructions: z .string() .trim() - .min(1, "Instructions are required — this is what the Bot follows."), + .min(1, "Instructions are required. They are what the Bot follows."), /** * The tools this skill says it needs, as `/` refs. * diff --git a/app/src/routes/_authed/_app/bot.tsx b/app/src/routes/_authed/_app/bot.tsx index c64ff8263c..f5c41b5ab9 100644 --- a/app/src/routes/_authed/_app/bot.tsx +++ b/app/src/routes/_authed/_app/bot.tsx @@ -67,10 +67,10 @@ async function loadExplicitBot(agentId: string): Promise { `/api/agents/${encodeURIComponent(agentId)}`, ); if (response.status === 404) return { status: "missing" }; - if (!response.ok) throw new Error("Bot couldn't be loaded."); + if (!response.ok) throw new Error("Couldn't load this Bot."); const body: unknown = await response.json().catch(() => null); - if (!isAgentEnvelope(body)) throw new Error("Bot couldn't be loaded."); + if (!isAgentEnvelope(body)) throw new Error("Couldn't load this Bot."); return { bot: body.agent, status: "found" }; } @@ -106,7 +106,7 @@ function RouteComponent() { return (

    - Bots couldn't be loaded. + Couldn't load your Bots. Reload the page.

    ); @@ -115,7 +115,7 @@ function RouteComponent() { return (

    - Bot couldn't be loaded. + Couldn't load this Bot. Reload the page.

    ); @@ -125,8 +125,8 @@ function RouteComponent() {

    {agent - ? `This deployment has no Bot called "${agent}".` - : "This deployment has no Bots yet."} + ? `There's no Bot called "${agent}".` + : "There are no Bots yet."}

    ); @@ -216,8 +216,7 @@ function BotChat({ agentId, name }: { agentId: string; name: string }) { data-testid="bot-chat-history-unavailable" role="alert" > - Earlier messages in this conversation could not be loaded, and the - Bot is answering without them. + Couldn't load earlier messages. The Bot is answering without them.

    ) : null} {/* diff --git a/app/src/routes/_authed/_app/bots.$agentId.access.tsx b/app/src/routes/_authed/_app/bots.$agentId.access.tsx index 1c8591197d..c4d80db8e8 100644 --- a/app/src/routes/_authed/_app/bots.$agentId.access.tsx +++ b/app/src/routes/_authed/_app/bots.$agentId.access.tsx @@ -11,7 +11,7 @@ function AccessPage() { return ( {(agent) => } diff --git a/app/src/routes/_authed/_app/bots.$agentId.approvals.tsx b/app/src/routes/_authed/_app/bots.$agentId.approvals.tsx index 94f2fa87c9..a8906fa7eb 100644 --- a/app/src/routes/_authed/_app/bots.$agentId.approvals.tsx +++ b/app/src/routes/_authed/_app/bots.$agentId.approvals.tsx @@ -11,7 +11,7 @@ function ApprovalRulesPage() { return ( {(agent) => } diff --git a/app/src/routes/_authed/_app/bots.$agentId.index.tsx b/app/src/routes/_authed/_app/bots.$agentId.index.tsx index 410eb5d106..e3b1fd7a97 100644 --- a/app/src/routes/_authed/_app/bots.$agentId.index.tsx +++ b/app/src/routes/_authed/_app/bots.$agentId.index.tsx @@ -18,12 +18,12 @@ function BotProfilePage() { return ( {agent.isPending ? null : agent.error ? (

    - This Bot is not one you can reach. + Couldn't open this Bot. It may not be shared with you.

    ) : ( {(agent) => ( @@ -23,7 +23,7 @@ function MemoryPage() { className="underline underline-offset-4" to="/capabilities/memory" > - Settings → Memory + Capabilities .

    diff --git a/app/src/routes/_authed/_app/bots.$agentId.responsibilities.tsx b/app/src/routes/_authed/_app/bots.$agentId.responsibilities.tsx index ec612945bd..cc21b2c7d8 100644 --- a/app/src/routes/_authed/_app/bots.$agentId.responsibilities.tsx +++ b/app/src/routes/_authed/_app/bots.$agentId.responsibilities.tsx @@ -13,7 +13,7 @@ function ResponsibilitiesPage() { return ( {(agent) => } diff --git a/app/src/routes/_authed/_app/bots.$agentId.routines.tsx b/app/src/routes/_authed/_app/bots.$agentId.routines.tsx index b2cc658497..71cac12735 100644 --- a/app/src/routes/_authed/_app/bots.$agentId.routines.tsx +++ b/app/src/routes/_authed/_app/bots.$agentId.routines.tsx @@ -11,7 +11,7 @@ function RoutinesPage() { return ( {(agent) => } diff --git a/app/src/routes/_authed/_app/bots.$agentId.sharing.tsx b/app/src/routes/_authed/_app/bots.$agentId.sharing.tsx index 69243d320b..dbacc40ec1 100644 --- a/app/src/routes/_authed/_app/bots.$agentId.sharing.tsx +++ b/app/src/routes/_authed/_app/bots.$agentId.sharing.tsx @@ -11,7 +11,7 @@ function SharingPage() { return ( {(agent) => } diff --git a/app/src/routes/_authed/_app/bots.index.tsx b/app/src/routes/_authed/_app/bots.index.tsx index e27fd9f506..be6dfe60f2 100644 --- a/app/src/routes/_authed/_app/bots.index.tsx +++ b/app/src/routes/_authed/_app/bots.index.tsx @@ -32,7 +32,7 @@ function BotsPage() { New Bot } - description="Every Bot you can reach: what each is doing, what it needs from you, and whether it is paused." + description="Your Bots and the ones shared with you. See what each is doing and what it needs from you." title="Bots" > diff --git a/app/src/routes/_authed/_app/capabilities.memory.tsx b/app/src/routes/_authed/_app/capabilities.memory.tsx index 37647af15c..00393f5ae3 100644 --- a/app/src/routes/_authed/_app/capabilities.memory.tsx +++ b/app/src/routes/_authed/_app/capabilities.memory.tsx @@ -11,7 +11,7 @@ function MemoryPage() { } - description="What your Bots know about you. Facts you tell them reach every Bot; what a Bot learns stays with that Bot." + description="What your Bots know about you. Facts you add reach every Bot. What a Bot learns on its own stays with that Bot." /> ); diff --git a/app/src/routes/_authed/_app/capabilities.skills.tsx b/app/src/routes/_authed/_app/capabilities.skills.tsx index 321122fd08..6cf3b47ce3 100644 --- a/app/src/routes/_authed/_app/capabilities.skills.tsx +++ b/app/src/routes/_authed/_app/capabilities.skills.tsx @@ -129,8 +129,8 @@ function SkillsPage() { } description={ <> - A skill is a named instruction you invoke with / and - a Bot follows. Yours are yours alone, and go on the Bots you own. + Type / and a skill's name to give a Bot that + instruction. Only you see yours, and they work on Bots you own. } title="Your skills" @@ -146,13 +146,13 @@ function SkillsPage() { */} {loading ? null : !data ? (

    - Your skills could not be loaded. + Couldn't load your skills. Reload the page.

    ) : mine.length === 0 ? ( - You don't have any skills yet. + No skills yet. Write one with New skill. @@ -235,7 +235,7 @@ function SkillsPage() { */} {deployment.length > 0 ? ( diff --git a/app/src/routes/_authed/_app/channel/$channelId.tsx b/app/src/routes/_authed/_app/channel/$channelId.tsx index 4e60ec43a7..727c1b8871 100644 --- a/app/src/routes/_authed/_app/channel/$channelId.tsx +++ b/app/src/routes/_authed/_app/channel/$channelId.tsx @@ -141,7 +141,7 @@ function RouteComponent() { onOpenChange={(open) => show(open ? "watch" : null)} />
    @@ -86,7 +86,7 @@ function RouteComponent() { setError( caught instanceof Error ? caught.message - : "Could not start the conversation.", + : "Couldn't start the conversation. Try again.", ); throw caught; } @@ -97,8 +97,7 @@ function RouteComponent() { // Said out loud: a message that silently reaches somebody you did not choose is the // kind of surprise that costs trust the first time it happens.

    - Sent to the coworker it is for. Type @ to choose one - yourself. + OpenBot picks the right Bot. Type @ to choose one.

    ) : null} {error ? ( @@ -120,8 +119,8 @@ function RouteComponent() { className="mt-2 w-full max-w-2xl text-sm text-destructive" role="alert" > - Your coworkers couldn't be loaded, so there's no one to send this - to yet. + Couldn't load your Bots, so there's no one to send this to. Reload + the page.

    ) : null}
    @@ -147,7 +146,7 @@ function RouteComponent() { */} {loading ? ( <> -

    Explore agents

    +

    Explore Bots

    ) : explore?.length ? ( @@ -160,7 +159,7 @@ function RouteComponent() { >

    - Explore agents + Explore Bots

    {/* * `static` undoes the primitive's own absolute placement, which parks these either @@ -194,11 +193,11 @@ function RouteComponent() { // card there would say the opposite of what a populated composer beside it (still // working off that same, successfully loaded `agents`) proves. <> -

    Explore agents

    +

    Explore Bots

    - Agents shared with you couldn't be loaded. + Couldn't load the Bots shared with you. @@ -208,11 +207,11 @@ function RouteComponent() { // when it failed but `agents` is defined — a loaded, empty slice either way. Same // plain copy for both: an empty roster is a fact, not an error. <> -

    Explore agents

    +

    Explore Bots

    - Nobody has shared an agent with you yet. + Nobody has shared a Bot with you yet. diff --git a/app/src/routes/_authed/admin/approvals.tsx b/app/src/routes/_authed/admin/approvals.tsx index af067d45f8..e0cdf94f18 100644 --- a/app/src/routes/_authed/admin/approvals.tsx +++ b/app/src/routes/_authed/admin/approvals.tsx @@ -9,7 +9,7 @@ export const Route = createFileRoute("/_authed/admin/approvals")({ function AdminApprovalsPage() { return ( diff --git a/app/src/routes/_authed/admin/audit.tsx b/app/src/routes/_authed/admin/audit.tsx index 9d661a1da1..ec27d4faed 100644 --- a/app/src/routes/_authed/admin/audit.tsx +++ b/app/src/routes/_authed/admin/audit.tsx @@ -83,7 +83,7 @@ function AuditPage() { setExportCapped(null); try { const response = await client(auditExportUrl(search), { - fallback: "Could not export audit events", + fallback: "Couldn't export audit events. Try again.", }); // A file that stopped early must say so, or it reads as the whole trail. const capped = Number(response.headers.get("x-audit-export-truncated")); @@ -101,7 +101,7 @@ function AuditPage() { setExportError( error instanceof Error ? error.message - : "Could not export audit events", + : "Couldn't export audit events. Try again.", ); } finally { setExporting(false); @@ -160,13 +160,13 @@ function AuditPage() { ) : null} {exportCapped ? (

    - The file holds the newest {exportCapped.toLocaleString()} events, - not all of them. Narrow the filter or the dates to export the rest. + The file has the newest {exportCapped.toLocaleString()} events, not + all of them. Narrow the filter or dates to export the rest.

    ) : null} {events.isPending ? null : events.isError ? (

    - The audit trail could not be loaded. + Couldn't load the audit log. Reload the page.

    ) : rows.length === 0 ? ( No events match this filter yet. @@ -278,10 +278,10 @@ function Row({ const routed = event.eventType === "channel.routed" ? payload.viaMention === true - ? "The person chose this coworker" + ? "The person chose this Bot" : payload.fallback === true - ? "Sent to the default coworker" - : "Sent to the coworker it is for" + ? "Sent to the default Bot" + : "Sent to the Bot it was meant for" : null; // Allowed by policy but not carried out. A stalled turn belongs in the same family: the Bot was // asked and the answer never arrived. Colour is how this table is read, and a row left in the @@ -485,7 +485,7 @@ function Row({ const DISCOVERY_REASONS: Record = { "under-floor": "Few enough tools to offer them all", "nothing-declared": "No skill declares any of these tools", - unavailable: "Could not choose, so all were offered", + unavailable: "Couldn't choose, so all were offered", "nothing-chosen": "No skill applied, so all were offered", selected: "Chosen by skill", }; @@ -505,8 +505,8 @@ const DECISIONS: Record = { "agent.stream_stalled": "The Bot stopped responding", "computer.policy_loaded": "Boundary at start-up", "computer.isolation_loaded": "Isolation at start-up", - "computer.control_taken": "A person took the wheel", - "computer.control_released": "The wheel was handed back", + "computer.control_taken": "A person took control", + "computer.control_released": "Control went back to the Bot", "computer.help_requested": "The Bot asked for help", "computer.secret_requested": "The Bot asked for a secret", "computer.secret_supplied": "A person supplied a secret", @@ -516,25 +516,25 @@ const DECISIONS: Record = { "component.granted": "Granted to this Bot", "component.revoked": "Taken away from this Bot", - "component.published": "Published, so every Bot may use it", - "component.unpublished": "Unpublished, so no Bot may use it", + "component.published": "Published. Every Bot can use it", + "component.unpublished": "Unpublished. No Bot can use it", "component.draft_saved": "Draft saved, not yet published", "component.refused": "Refused", - "component.function_granted": "May read this", - "component.function_revoked": "May no longer read this", + "component.function_granted": "Can read this", + "component.function_revoked": "Can no longer read this", "component.function_called": "Read real data", "component.function_refused": "Refused", // A function failure is execution failure, not a policy refusal. - "component.function_failed": "Could not be read", + "component.function_failed": "Couldn't read it", // Not a call and not a decision: the tools this run was allowed to see. Worded so nobody reads it // as permission, which it is not — everything named was already granted. "mcp.tools_discovered": "Tools offered for one run", "mcp.call_succeeded": "Called on this Bot's behalf", "mcp.call_rejected": "Blocked", - "mcp.call_failed": "The server did not answer", + "mcp.call_failed": "The server didn't answer", // Not "Blocked": nothing about the Bot was judged, because nothing proved which Bot it was. - "mcp.callback_refused": "Could not prove which Bot it was", + "mcp.callback_refused": "Couldn't confirm which Bot it was", "configuration.changed": "Configuration changed", "credential.created": "Credential saved", diff --git a/app/src/routes/_authed/admin/boundaries.tsx b/app/src/routes/_authed/admin/boundaries.tsx index 46d5f7351c..8906302fde 100644 --- a/app/src/routes/_authed/admin/boundaries.tsx +++ b/app/src/routes/_authed/admin/boundaries.tsx @@ -31,17 +31,17 @@ const PRESETS: { label: string; rule: string; cost?: string }[] = [ // flag that presses it once the text is in, and a rule naming only `computer_key` left that door // open. rule: '(intent == "activate" && contains(element.name, "submit")) || ((tool.name == "computer_key" || tool.name == "computer_type") && key == "Enter")', - cost: "Also stops the Bot pressing Enter for anything else, because a form submits from Enter in any of its fields.", + cost: "Also stops the Bot pressing Enter anywhere else. A form can submit from Enter in any field.", }, { label: "Never type into a password field", rule: 'intent == "type" && contains(element.name, "password")', - cost: "A password box the page labels something else is not covered, the rule matches the label.", + cost: "A password box with a different label isn't covered. The rule matches the label.", }, { label: "Stay off social media", rule: 'intent == "navigate" && (contains(page.host, "facebook.com") || contains(page.host, "x.com"))', - cost: "Only the two hosts named. A link that redirects there from somewhere else is allowed.", + cost: "Only these two hosts. A link that redirects there from elsewhere is allowed.", }, ]; @@ -136,18 +136,18 @@ function BoundariesPage() { - What every Bot may and may not do with its computer. Rules are checked - on every action before it happens, and a refusal is recorded in{" "} + What a Bot can and can't do with its computer. Every action is checked + before it happens. Refusals show in{" "} Audit {" "} - with the rule that refused it. + with the rule that refused them. } title="Boundaries" >
    @@ -170,31 +170,28 @@ function BoundariesPage() {

    {policy.mode === "enforce" ? "The Bot is stopped and told which rule refused it." - : "Nothing is stopped. Every action a rule matches is recorded as it would have been refused, which is how a rule is tried out before it is switched on."} + : "Nothing is stopped. Each match is recorded as if it had been refused."}

    - Checked first, and a match ends it: nothing below is consulted and - the Bot is told which rule refused it. Rules are CEL, and can ask - about tool.name, intent,{" "} - bot.id, actor.id, page.url{" "} - and page.host, the element being acted on, the{" "} - key being pressed, the file being touched, the{" "} - command being run, and mcp.server,{" "} - mcp.tool and mcp.effect for a call to - somebody else’s tools. initiator.kind says what - started the run — person, routine or{" "} - handoff — which actor.id cannot, - because a scheduled run carries its owner’s authority while - nobody is watching. A rule that cannot be evaluated counts as a - match, so a mistyped deny refuses rather than quietly permitting - what it was meant to forbid. + Checked first. A match stops the action, and the Bot is told which + rule refused it. Rules are CEL. They can check{" "} + tool.name, intent, bot.id,{" "} + actor.id, page.url, page.host + , the element, the key pressed, the file, the{" "} + command run, and mcp.server,{" "} + mcp.tool and mcp.effect for outside tools.{" "} + initiator.kind says what started the run:{" "} + person, routine or handoff. A + scheduled run acts as its owner, so actor.id alone + can't tell you that. A rule that can't be evaluated counts as a + match. A typo never lets an action through. } - title="It may never" + title="Bots may never" > {policy.deny.length === 0 ? (

    @@ -290,8 +287,8 @@ function BoundariesPage() {

      {policy.allow.map((rule) => ( @@ -310,7 +307,7 @@ function BoundariesPage() { ) : saved ? ( "Saved. It applies to the next action any Bot takes." ) : ( - "Changes apply to the next action any Bot takes, and are kept: a restart comes back up enforcing what is here." + "Changes apply to the next action any Bot takes. They stay after a restart." )}

      @@ -327,8 +324,8 @@ function DryRunResult({ report }: { report: DryRunReport }) { if (report.scanned === 0) { return (

      - No recorded computer actions to test against yet. The rule is valid; - what it matches will only be known once Bots have acted. + No recorded computer actions to test against yet. The rule is valid. + You'll see what it matches once Bots have acted.

      ); } @@ -337,8 +334,8 @@ function DryRunResult({ report }: { report: DryRunReport }) {

      {report.wouldRefuse === 0 - ? `Tested against the last ${report.scanned} recorded actions: this rule would have refused none of them. It may still match future actions.` - : `Tested against the last ${report.scanned} recorded actions: this rule would have refused ${report.wouldRefuse}.`} + ? `Tested on the last ${report.scanned} recorded actions. This rule would have refused none. It may still match future ones.` + : `Tested on the last ${report.scanned} recorded actions. This rule would have refused ${report.wouldRefuse}.`}

      {report.changes.length > 0 ? (
        @@ -371,8 +368,8 @@ function DryRunResult({ report }: { report: DryRunReport }) { ) : null} {report.wouldRefuse > report.changes.length ? (

        - Showing the first {report.changes.length}; the count above covers - everything scanned. + Showing the first {report.changes.length}. The count above covers + everything checked.

        ) : null}
      diff --git a/app/src/routes/_authed/admin/components/$name.tsx b/app/src/routes/_authed/admin/components/$name.tsx index 86ab48129c..5651291600 100644 --- a/app/src/routes/_authed/admin/components/$name.tsx +++ b/app/src/routes/_authed/admin/components/$name.tsx @@ -68,7 +68,7 @@ import { queryClient } from "@/query-client"; */ /** The same way back from every state this route can be in. */ const BACK = { - label: "UI Components", + label: "UI components", linkProps: { to: "/admin/components" }, } as const; @@ -107,7 +107,7 @@ function RouteComponent() { return (

      - Could not load components. + Couldn't load components. Reload the page.

      ); @@ -121,15 +121,14 @@ function RouteComponent() { return ( {name} - It may have been renamed, or this deployment may no longer ship - it. + It may have been renamed, or this deployment no longer ships it.
    @@ -328,7 +326,7 @@ function ComponentDetail({ Not in this build - Nothing here can draw it, whatever else is set. + OpenBot can't draw it. The settings below have no effect. @@ -344,10 +342,10 @@ function ComponentDetail({ Published {component.published - ? "Bots may answer with it." - : "No Bot may use it."} + ? "Bots can answer with it." + : "No Bot can use it."} {component.hasUnpublishedChanges - ? " The description has changes that are not published." + ? " The description has unpublished changes." : null} @@ -385,7 +383,7 @@ function ComponentDetail({ } - label="May read" + label="Can read" onOpen={() => setSheet("functions")} summary={functionSummary} testId={`functions-${component.name}`} @@ -430,8 +428,8 @@ function ComponentDetail({ Description - What the model reads when deciding to call this. It changes - nothing until the component is published. + What the model reads when it decides to use this. It has no effect + until the component is published. @@ -475,16 +473,13 @@ function ComponentDetail({ Available to - Switch a Bot off and it is never told this component exists, so it - cannot ask for it and does not apologise for not having it. Each - change takes effect immediately. + Turn a Bot off and it never learns this component exists. Changes + apply right away. {bots.length === 0 ? ( -

    - There are no Bots yet. -

    +

    No Bots yet.

    ) : (
    {bots.map((bot, index) => { @@ -525,19 +520,18 @@ function ComponentDetail({ > - May read + Can read - A separate grant from Available to, and not implied by it: that - one decides who may draw this, and this decides what it may go and - fetch in order to draw itself. Until one of these is on it shows - only what the Bot passes it, and every read it does make is a row - in Audit. Each change takes effect immediately. + The data this component can fetch to draw itself. This is separate + from Available to. With all of these off, it shows only what the + Bot passes it. Every read shows in Audit. Changes apply right + away. {dataFunctions.length === 0 ? (

    - This deployment grants no data functions. + This deployment has no data functions.

    ) : (
    diff --git a/app/src/routes/_authed/admin/components/index.tsx b/app/src/routes/_authed/admin/components/index.tsx index 8abd0a4e1e..49382e7443 100644 --- a/app/src/routes/_authed/admin/components/index.tsx +++ b/app/src/routes/_authed/admin/components/index.tsx @@ -26,8 +26,8 @@ function RouteComponent() { return ( {/* * Pending, error, empty, rows — in that order. Pending draws nothing rather than a @@ -36,14 +36,14 @@ function RouteComponent() { */} {components.isPending ? null : components.error ? (

    - Could not load components. + Couldn't load components. Reload the page.

    ) : components.data?.length === 0 ? ( No components yet - Components that your Agents can use will be shown here. + Components your Bots can use will show here. diff --git a/app/src/routes/_authed/admin/computers.tsx b/app/src/routes/_authed/admin/computers.tsx index e2bc9e424e..fe314eee70 100644 --- a/app/src/routes/_authed/admin/computers.tsx +++ b/app/src/routes/_authed/admin/computers.tsx @@ -72,7 +72,9 @@ function ComputersPage() { * A list that could not be read. A refused stop, reset or update is said beside the buttons * instead, because the top of this page is a long scroll away from them. */ - const problem = fleet.error ? "The computers could not be listed." : null; + const problem = fleet.error + ? "Couldn't load the computers. Reload the page." + : null; const hostProblem = hostAccess.error ? hostAccess.error.message : requestGrant.error @@ -92,7 +94,7 @@ function ComputersPage() { return ( {problem ? ( @@ -109,14 +111,14 @@ function ComputersPage() { Every Bot is sharing one computer. {" "} - They share its logins, its files and its session, so a Bot can reach - what another signed into. Set COMPUTER_SUPERVISOR_URL to - give each Bot its own. + They share its logins, files and session. A Bot can reach what another + signed into. Set COMPUTER_SUPERVISOR_URL to give each Bot + its own.

    ) : isolation === "per-bot" ? (

    - Each Bot has a computer of its own: its own container, its own files - and its own browser profile. + Each Bot has its own computer, with its own container, files and + browser profile.

    ) : null} @@ -154,10 +156,10 @@ function ComputersPage() {

    ) : null} {computers === null && problem ? ( - The list could not be loaded. + Couldn't load the list. ) : computers === null ? null : computers.length === 0 ? ( - No computers yet. One appears the first time a Bot opens a page. + No computers yet. One shows up the first time a Bot opens a page. ) : ( @@ -235,8 +237,8 @@ function ComputersPage() { Reset {confirming ? nameFor(confirming) : ""}'s computer? - Its profile is deleted, so the Bot is signed out of every service - it had logged into and starts clean. This cannot be undone. + Its profile is deleted. The Bot is signed out of every service and + starts clean. You can't undo this. @@ -274,9 +276,9 @@ function ComputersPage() { computer? - It moves onto the computer image this deployment now runs. Its - files and sign-ins are kept. If it is running it restarts, which - interrupts anything it is doing right now. + It moves to the computer image this deployment now runs. Its files + and sign-ins are kept. If it's running, it restarts and stops what + it's doing. @@ -301,13 +303,12 @@ function ComputersPage() {

    - Stop closes the browser and keeps its logins: the next - thing the Bot does starts it again where it left off.{" "} - Reset deletes the profile, so the Bot is signed out of - everything and starts clean. Update computer appears - when a computer runs an older image than this deployment, and moves it - onto the new one with its files and sign-ins kept. All three are - recorded in{" "} + Stop closes the browser and keeps its logins. It starts + again the next time the Bot needs it. Reset deletes the + profile and signs the Bot out of everything.{" "} + Update computer shows when a computer runs an older + image. It moves the computer to the new one and keeps its files and + sign-ins. All three show in{" "} Audit @@ -372,15 +373,13 @@ function HostFoldersSection({ {!connected && !loading ? (

    - The desktop app is offline. Folders can only be approved from the - desktop app, and command access stays unavailable while it is offline. + The desktop app is offline. Open it to approve folders or run + commands.

    ) : null} {loading ? null : botIds.length === 0 ? ( - - No Bots yet. Create a Bot before choosing folders. - + No Bots yet. Create one, then choose its folders. ) : ( {botIds.map((botId, index) => ( @@ -418,8 +417,8 @@ function HostFoldersSection({

    - Grants are read-only by default. Writes are requested from the native - desktop prompt when a Bot needs them. + Folders are read-only. When a Bot needs to write, the desktop app asks + you first.

    diff --git a/app/src/routes/_authed/admin/index.tsx b/app/src/routes/_authed/admin/index.tsx index d80c2831c3..440fbecfac 100644 --- a/app/src/routes/_authed/admin/index.tsx +++ b/app/src/routes/_authed/admin/index.tsx @@ -54,30 +54,29 @@ const SECTIONS: { }[] = [ { title: "What Bots can reach", - description: - "Everything a Bot can touch outside this app, and the limits on it.", + description: "What a Bot can touch outside OpenBot, and the limits on it.", items: [ { title: "Credentials", - description: "Keys and tokens held for this deployment.", + description: "Keys and tokens for this deployment.", icon: IconKey, linkOptions: { to: "/admin/credentials" }, }, { title: "Boundaries", - description: "Rules that decide what a Bot may never do.", + description: "Things no Bot may ever do.", icon: IconShieldCheck, linkOptions: { to: "/admin/boundaries" }, }, { title: "Approvals", - description: "What every member's Bots must ask before acting.", + description: "What members' Bots must ask before they act.", icon: IconChecks, linkOptions: { to: "/admin/approvals" }, }, { title: "Computers", - description: "The machines Bots run their tools on.", + description: "The machines Bots run tools on.", icon: IconDeviceDesktop, linkOptions: { to: "/admin/computers" }, }, @@ -85,30 +84,30 @@ const SECTIONS: { }, { title: "What Bots can do", - description: "Capabilities and interface pieces available across Bots.", + description: "Apps, skills and interface pieces every Bot can use.", items: [ { title: "Plugins", description: - "The services this deployment can reach, and which Bots may.", + "The services OpenBot can reach, and which Bots can use them.", icon: IconPuzzle, linkOptions: { to: "/admin/plugins" }, }, { title: "Skills", - description: "Named instructions anybody can invoke with a slash.", + description: "Named instructions anybody can run with /.", icon: IconFileText, linkOptions: { to: "/admin/skills" }, }, { - title: "Automatic Learning", + title: "Automatic learning", description: - "Learn from completed conversations and review proposed skills.", + "Learn from finished conversations and review the skills it proposes.", icon: IconFileText, linkOptions: { to: "/admin/learning" }, }, { - title: "UI Components", + title: "UI components", description: "Custom pieces a Bot can draw in a conversation.", icon: IconLayoutGrid, linkOptions: { to: "/admin/components" }, @@ -128,21 +127,21 @@ const SECTIONS: { { title: "People", description: - "Everybody who has signed in, who administers this deployment, and whose access has been removed.", + "Everybody who has signed in, who the admins are, and whose access was removed.", icon: IconUsers, linkOptions: { to: "/admin/people" }, }, { title: "Enterprise controls", description: - "Capabilities by role and group, SSO required, SCIM, network policy, MCP and model allowlists, Action Recording.", + "Roles and groups, required SSO, SCIM, network policy, MCP and model allowlists, Action Recording.", icon: IconShieldCheck, linkOptions: { to: "/admin/enterprise" }, }, { title: "Identity providers", description: - "A company's own SAML or OpenID Connect provider, routed by email domain.", + "Your company's SAML or OpenID Connect provider, matched by email domain.", icon: IconBuildingBank, linkOptions: { to: "/admin/identity-providers" }, }, @@ -154,7 +153,7 @@ const SECTIONS: { items: [ { title: "Audit", - description: "Every action taken in this deployment, and by whom.", + description: "Every action taken in OpenBot, and who took it.", icon: IconListDetails, linkOptions: { to: "/admin/audit" }, }, @@ -165,7 +164,7 @@ const SECTIONS: { function RouteComponent() { return ( {SECTIONS.map((section) => ( diff --git a/app/src/routes/_authed/admin/learning.tsx b/app/src/routes/_authed/admin/learning.tsx index 19a82e05bd..c5094d6342 100644 --- a/app/src/routes/_authed/admin/learning.tsx +++ b/app/src/routes/_authed/admin/learning.tsx @@ -35,13 +35,13 @@ function automaticMessage( automatic.state === "assigned" && settings.defaultTarget?.containerId === automatic.containerId ) - return `OpenBot assigned this Intelligence project's "${automatic.containerId}" container automatically. Change it here at any time.`; + return `OpenBot assigned this Intelligence project's "${automatic.containerId}" container automatically. You can change it here.`; if (automatic.state === "absent") - return `This Intelligence project has no "${automatic.containerId}" Learning container yet, so nothing is collected.`; + return `This Intelligence project has no "${automatic.containerId}" Learning container yet. Nothing is collected.`; if (automatic.state === "not_entitled") - return "This Intelligence project's plan does not include Learning, so no container was assigned."; + return "This Intelligence project's plan doesn't include Learning. No container was assigned."; if (automatic.state === "unavailable") - return "OpenBot could not ask Intelligence for this project's Learning container. It tries again."; + return "Couldn't reach Intelligence to find this project's Learning container. OpenBot will try again."; return undefined; } @@ -127,8 +127,7 @@ function ContainerSetup({ : `Create the ${automatic.containerId} container`} - Uses the credential this deployment was given, never the runtime - key. + Uses this deployment's credential, not the runtime key.
    )} @@ -193,12 +192,12 @@ export function LearningPage() { return ( {settingsQuery.isPending && (

    Loading Learning settings…

    @@ -224,7 +223,7 @@ export function LearningPage() { change({ ...settings, enabled: event.target.checked }) } /> - Enable Automatic Learning and skill delivery + Turn on automatic learning and skill delivery )}

    - An empty default leaves unmapped Bots out. IDs use 1–64 lowercase - letters, numbers, and single hyphens. Leave revision empty to - follow the latest published skills. + With no default, Bots without their own container are left out. + IDs are 1 to 64 lowercase letters, numbers and single hyphens. + Leave revision empty to use the latest published skills.

    @@ -252,7 +251,7 @@ export function LearningPage() { {(agents.error || hiddenAgents.error) && (

    - The Bot list could not be loaded. Existing mappings are shown + Couldn't load the Bot list. Saved settings for each Bot are below.

    )} @@ -287,7 +286,7 @@ export function LearningPage() { }} > - + @@ -332,11 +331,11 @@ export function LearningPage() {