Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
65de945
Release v1.0.0-rc.1 (#1325)
github-actions[bot] Nov 9, 2026
0154b2e
Open "OpenBot is running" on its heading, with Updates under the butt…
davidmckayv Nov 9, 2026
1b8c8f3
Reach past the newest 50 events on Activity and Admin Audit (#1326)
davidmckayv Nov 9, 2026
09dde85
Skip the narration question on a computer with no microphone (#1327)
davidmckayv Nov 9, 2026
b93699c
Fit New Bot's four named frameworks above the footer at 800px (#1328)
davidmckayv Nov 9, 2026
d38dfbd
Say which apps need a key, and where to get it, before the key form (…
davidmckayv Nov 9, 2026
2692433
Let a person connect several accounts of one app, and let their Bots …
davidmckayv Nov 9, 2026
8b5c55b
Say what happens next after a recording saves on Teach a Bot (#1302)
davidmckayv Nov 9, 2026
a0c68dc
Keep Install's bar short of full while items unpack, and say Give wor…
davidmckayv Nov 9, 2026
dfb0c9a
Release v1.0.0-rc.2 (#1330)
github-actions[bot] Nov 9, 2026
dea620f
Ask the Google Maps starter for step-by-step walking directions (#1336)
davidmckayv Nov 9, 2026
6f71a6d
Book a demo: only Not now under the calendar until a call is booked (…
davidmckayv Nov 9, 2026
e423bbc
Lay bar charts with long names on their side (#1337)
davidmckayv Nov 9, 2026
51397bb
Keep a conversation's rail of turns clear of the Bot's replies (#1338)
davidmckayv Nov 9, 2026
a21ba49
Release v1.0.0-rc.3 (#1339)
github-actions[bot] Nov 9, 2026
fb6b5e5
Tell a Bot which apps it can use, and where each stands for the perso…
davidmckayv Nov 9, 2026
bb7108c
Say which Docker resource is short for the back end, and where to rai…
davidmckayv Nov 9, 2026
e57e3f5
Let OpenBot's own 4 CPU Podman machine through the engine check befor…
davidmckayv Nov 9, 2026
ca059de
Release v1.0.0-rc.4 (#1348)
github-actions[bot] Nov 9, 2026
3684ed9
Tell a Bot where each app stands the way its page does (#1350)
davidmckayv Nov 9, 2026
d0ec63d
Release v1.0.0-rc.5 (#1351)
github-actions[bot] Nov 9, 2026
ac9c16f
Release v1.0.0 (#1352)
github-actions[bot] Nov 9, 2026
eab4d4d
Merge main into merge-back/v1.0.0
davidmckayv Nov 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -157,15 +157,16 @@ jobs:
OPENBOT_COMPUTER_BROWSER: '1'
COMPUTER_BROWSER_MODE: headless
# The app's real-browser tests, here because this job has the browser: a running step's words
# under reduced motion, where onboarding's Back lands beside the Mac window's buttons, and the name
# step's orb drawn whole under the step dashes, which only a browser's layout resolves. The root
# playwright-core is the same version as
# under reduced motion, where onboarding's Back lands beside the Mac window's buttons, the name
# step's orb drawn whole under the step dashes, and the New Bot sheet's first four frameworks
# clearing its footer in an 800px window, and a conversation's rail of turns clear of the Bot's
# text, which only a browser's layout resolves. The root playwright-core is the same version as
# agent-computer's playwright, so it finds the Chromium installed above. The onboarding frame
# reads the generated app config, which `bun test` alone doesn't write (the test script's
# pretest does).
- run: |
bun run generate:app-config
bun test app/tests/tool-line-reduced-motion.test.tsx app/tests/onboarding-window-buttons.test.tsx app/tests/onboarding-orb-room.test.tsx
bun test app/tests/tool-line-reduced-motion.test.tsx app/tests/onboarding-window-buttons.test.tsx app/tests/onboarding-orb-room.test.tsx app/tests/new-bot-sheet-fit.test.tsx app/tests/transcript-rail-clearance.test.tsx
env:
OPENBOT_APP_BROWSER: '1'
# Headed is the default for hosted computers, so it is tested as one: the computer starting its
Expand Down
139 changes: 116 additions & 23 deletions CHANGELOG.md

Large diffs are not rendered by default.

32 changes: 17 additions & 15 deletions app/src/components/agents/new-bot-sheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export function NewBotSheet({
const STEPS = ["about", "runtime", "endpoint"] as const;
/**
* The web list as it comes: the frameworks people look for first, and "An agent you already run"
* last, so the row that peeks above the footer on the first screen is a framework they know.
* last, so the rows that show above the footer on the first screen are frameworks they know.
*/
const CONNECTION_OPTIONS = WEB_HARNESSES;
type Step = (typeof STEPS)[number];
Expand Down Expand Up @@ -438,7 +438,7 @@ function NewBotFlow({ onCreated }: { onCreated: (agentId: string) => void }) {
submit();
}}
>
<div className="min-h-0 flex-1 overflow-y-auto overflow-x-hidden px-6 pt-8 pb-6">
<div className="min-h-0 flex-1 overflow-y-auto overflow-x-hidden px-6 pt-6 pb-6">
<MotionConfig reducedMotion="user">
{/* Shares its layout id with the orb on the created view, so it travels there. */}
<div className="flex items-center gap-3">
Expand Down Expand Up @@ -467,7 +467,7 @@ function NewBotFlow({ onCreated }: { onCreated: (agentId: string) => void }) {

<motion.div
animate={{ opacity: 1, x: 0 }}
className="mt-6"
className="mt-5"
initial={{ opacity: 0, x: 24 * direction }}
key={step}
transition={{ duration: 0.2, ease: "easeOut" }}
Expand Down Expand Up @@ -701,7 +701,7 @@ function AboutStep({
personal: boolean;
}) {
return (
<FieldGroup className="gap-7">
<FieldGroup className="gap-5">
<Field>
<FieldLabel className={SECTION_LABEL} htmlFor="new-bot-name">
Name
Expand Down Expand Up @@ -776,7 +776,7 @@ function AboutStep({
{personal ? (
<EveryAppList turnedOn={turnedOn} />
) : apps.length > 0 ? (
<FieldSet>
<FieldSet className="gap-2">
<FieldLegend className={SECTION_LABEL} variant="label">
Apps it can use
</FieldLegend>
Expand All @@ -786,7 +786,7 @@ function AboutStep({
<div className="grid gap-x-4 sm:grid-cols-2">
{apps.map((app) => (
<label
className="flex items-center gap-3 py-1.5 text-sm"
className="flex items-center gap-3 py-1 text-sm"
htmlFor={`new-bot-app-${app.key}`}
key={app.key}
>
Expand Down Expand Up @@ -820,7 +820,7 @@ function EveryAppList({ turnedOn }: { turnedOn: UseQueryResult<PluginsPage> }) {
if (turnedOn.isPending) return null;
const servers = turnedOn.data?.servers ?? [];
return (
<FieldSet>
<FieldSet className="gap-2">
<FieldLegend className={SECTION_LABEL} variant="label">
Apps it can use
</FieldLegend>
Expand All @@ -838,7 +838,7 @@ function EveryAppList({ turnedOn }: { turnedOn: UseQueryResult<PluginsPage> }) {
>
{servers.map((server) => (
<li
className="flex items-center gap-3 py-1.5 text-sm"
className="flex items-center gap-3 py-1 text-sm"
key={server.id}
>
<PluginLogo logo={server.logo} />
Expand Down Expand Up @@ -959,7 +959,7 @@ const SECTION_LABEL =
* cards. The border is there but transparent until chosen, so choosing one never shifts the rows.
*/
const OPTION_CLASSES =
"flex w-full items-center gap-3 rounded-lg border border-transparent bg-muted/50 p-3 text-left transition-colors hover:bg-muted disabled:pointer-events-none disabled:opacity-50 aria-pressed:border-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset";
"flex w-full items-center gap-3 rounded-lg border border-transparent bg-muted/50 px-3 py-2.5 text-left transition-colors hover:bg-muted disabled:pointer-events-none disabled:opacity-50 aria-pressed:border-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset";

/**
* Choose a custom framework and advance immediately to its connection settings.
Expand Down Expand Up @@ -990,7 +990,7 @@ const RUNTIME_HEADING =
* Pressing one acts at once, so there is no chosen state to draw.
*/
const FRAMEWORK_ROW_CLASSES =
"-mx-3 flex items-center gap-3 rounded-lg px-3 py-2.5 text-left transition-colors hover:bg-muted disabled:pointer-events-none disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset";
"-mx-3 flex items-center gap-3 rounded-lg px-3 py-1.5 text-left transition-colors hover:bg-muted disabled:pointer-events-none disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset";

/** Every framework the sheet offers, one line each, name then summary; pressing one picks it. */
function FrameworkRows({
Expand Down Expand Up @@ -1036,9 +1036,11 @@ function showCustom(reduceMotion: boolean | null) {

/**
* The first screen's second way to make a Bot, below the simple form: RuntimeStep's rows under a
* section name like the form's own, without taking focus from Name. A wide gap sets it apart from the
* form, and on a desktop sheet its name and first rows show above the footer before anything is
* scrolled. Each row acts on its own press, so Create Bot stays the form's.
* section name like the form's own, without taking focus from Name. A wider gap than the form's own
* sets it apart. In an 800px-tall window (OpenBot Desktop's common height on a Mac) the four
* frameworks named first show above the footer before anything is scrolled, which is why the form
* above is spaced as tightly as it is; new-bot-sheet-fit.test.tsx measures it in Chromium. Each row
* acts on its own press, so Create Bot stays the form's.
*/
function CustomAgents({
disabled,
Expand All @@ -1050,11 +1052,11 @@ function CustomAgents({
return (
<section
aria-labelledby={CUSTOM_HEADING_ID}
className="mt-12 flex scroll-mt-6 flex-col"
className="mt-8 flex scroll-mt-6 flex-col"
id={CUSTOM_ID}
>
<h2
className={cn("mb-2 font-medium outline-none", SECTION_LABEL)}
className={cn("mb-1.5 font-medium outline-none", SECTION_LABEL)}
id={CUSTOM_HEADING_ID}
tabIndex={-1}
>
Expand Down
2 changes: 2 additions & 0 deletions app/src/components/approvals/rules.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,8 @@ export function RuleRow({
: folderRule
? folderWords(rule.scope, folders.data?.grants)
: `on ${rule.scope}`,
// A rule saved for one of several accounts at the app covers that account only.
rule.account ? `from ${rule.account}` : null,
rule.botId === "*"
? null
: rule.botId.includes("*")
Expand Down
46 changes: 39 additions & 7 deletions app/src/components/audit/audit-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ const FACETS = [
export function AuditTable({
deploymentName = "this deployment",
events,
footer,
moreToLoad = false,
nameFor,
query,
storedNames = false,
Expand All @@ -105,13 +107,18 @@ export function AuditTable({
/** What OpenBot itself is called in a row it started: "this deployment" in Admin. */
deploymentName?: string;
events: AuditEvent[];
/** Drawn under the last row, inside the scroll: where older events are asked for. */
footer?: React.ReactNode;
/** Whether the server has older events than `events`, so the count says these are the newest. */
moreToLoad?: boolean;
nameFor: NameFor;
query: string;
/** Whether a row's What shows the trail's stored name on hover: Admin's, not a person's. */
storedNames?: boolean;
/**
* Drawn above the table, given how many rows it shows ("3 events", "1 of 3 events") and the filter
* buttons to place beside the search. It stays put; only the rows under it scroll.
* Drawn above the table, given how many rows it shows ("3 events", "1 of 3 events", "Newest 50
* events" while older ones are still to load) and the filter buttons to place beside the search.
* It stays put; only the rows under it scroll.
*/
toolbar: (parts: {
count: string;
Expand Down Expand Up @@ -144,7 +151,7 @@ export function AuditTable({
return (
<>
{toolbar({
count: countLabel(rows.length, events.length),
count: countLabel(rows.length, events.length, moreToLoad),
filters: FACETS.map((facet) => {
const column = table.getColumn(facet.id);
const selected = (column?.getFilterValue() as string[]) ?? [];
Expand Down Expand Up @@ -217,13 +224,24 @@ export function AuditTable({
</TableRow>
);
})}
{footer && rows.length > 0 ? (
<TableRow className="hover:bg-transparent">
<TableCell
className="p-0"
colSpan={columns.length - UNDRAWN.size}
>
{footer}
</TableCell>
</TableRow>
) : null}
</TableBody>
</Table>
{events.length > 0 && rows.length === 0 ? (
<div className="px-4">
<PageEmpty icon={IconSearch} title="No matches">
Nothing loaded matches this search and these filters.
</PageEmpty>
{footer}
</div>
) : null}
</>
Expand Down Expand Up @@ -315,10 +333,24 @@ function Facet({
);
}

/** How many rows the table is showing, for the toolbar. */
function countLabel(shown: number, total: number): string {
const noun = total === 1 ? "event" : "events";
return shown === total ? `${total} ${noun}` : `${shown} of ${total} ${noun}`;
/**
* How many rows the table is showing, for the toolbar. While the server has older events than those
* loaded, the count names them the newest, so it never reads as the size of the whole trail.
*/
function countLabel(
shown: number,
loaded: number,
moreToLoad: boolean,
): string {
const noun = loaded === 1 ? "event" : "events";
if (moreToLoad) {
return shown === loaded
? `Newest ${loaded} ${noun}`
: `${shown} of the newest ${loaded} ${noun}`;
}
return shown === loaded
? `${loaded} ${noun}`
: `${shown} of ${loaded} ${noun}`;
}

function auditColumns(
Expand Down
9 changes: 9 additions & 0 deletions app/src/components/browser/browser-screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
useOpenedTab,
} from "./browser-bar";
import { useFittedWidth } from "./fitted-window";
import { LearningNextNotice, savedToLearning } from "./learning-next";
import { NarrationNotice, NarrationSwitch } from "./narration";
import { NarrationChoiceDialog, useNarrationChoice } from "./narration-choice";
import {
Expand Down Expand Up @@ -184,6 +185,14 @@ export function ScreenBrowser({
{recording.stopped ? (
<StoppedNotice stopped={recording.stopped} />
) : null}
{/* Once it is in the Learning space: what happens to it next, and the way to the skill it becomes. */}
{recording.stopped && savedToLearning(recording.stopped) ? (
<LearningNextNotice
// A new recording follows its own skill, not the last one's.
key={recording.stopped.recordingId}
stopped={recording.stopped}
/>
) : null}
{failure ? (
<p
className="border-b border-border bg-background px-3 py-2 text-destructive text-sm"
Expand Down
Loading
Loading