Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ deploy/dokploy.sh ssh 'node /opt/agent-office/bin/agent-office.js accounts invit
| C | See a worker's changes: diff, commit, open a PR |
| N | Go to the next worker that's waiting on you |
| X | Send a worker home |
| L | Hang a sign over a desk ("Operations", "Code cleanup") |
| T / Enter | Chat |
| V | Join voice; then hold V to talk |
| M | Mute / unmute in voice |
Expand Down
3 changes: 2 additions & 1 deletion docs/controls.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ Back to the [README](../README.md).
| W A S D / arrows | Walk (hold Shift to run); on the ladder, W and S climb; in a car, W is the gas, S brakes and reverses, A and D steer |
| Space | Jump (you can land on desks, couches and the cars in the garage); in a car, brake |
| Mouse drag / wheel | Orbit / zoom the camera |
| E | Interact: hire a worker, open its terminal, read a board, take an issue's note off the board, prompt a board agent, call a meeting in the meeting room, draw on the whiteboard, read the docs at the bookshelf, watch the TV, sit down (or get up), ride the elevator, climb the ladder (or get off it), slide down a fire pole, grab a coffee, take a smoke break, tee off at the golf tee, pet the dog, pick up the basketball (then hold E and let go to shoot), order a drink at the rooftop bar, blow the DJ's air horn, step up to the dart board or the axe lane on the roof (then hold Space and let go to throw), get into one of the cars in the garage (behind the wheel, or beside whoever's driving) or out of it. In the [castle](maps.md#the-castle): sit on the throne, where E is for whoever's first in line (or the Hand of the King, with nobody waiting), and speak to the Hand to send out a new worker |
| E | Interact: hire a worker, open its terminal, read a board, take an issue's note off the board, prompt a board agent, call a meeting in the meeting room, draw on the whiteboard, read the docs at the bookshelf, watch the TV, sit down (or get up), ride the elevator, climb the ladder (or get off it), slide down a fire pole, grab a coffee, take a smoke break, tee off at the golf tee, pet the dog, pick up the basketball (then hold E and let go to shoot), order a drink at the rooftop bar, blow the DJ's air horn, step up to the dart board or the axe lane on the roof (then hold Space and let go to throw), get into one of the cars in the garage (behind the wheel, or beside whoever's driving) or out of it, knock through the north wall past the gong for 2 more desks (at the **🚧 Room to grow** sign). In the [castle](maps.md#the-castle): sit on the throne, where E is for whoever's first in line (or the Hand of the King, with nobody waiting), and speak to the Hand to send out a new worker |
| K | On the castle's throne: speak to the Hand of the King, to send out a new worker |
| P | Prompt: give a task to a new worker, or to the one at this desk |
| C | Changes: the files the worker at this desk changed and their diff; commit, discard or open a PR |
| B | Open a shared shell at an empty desk |
| R | Resume a sleeping worker (or restart a shell) |
| X | Send a worker home (frees the desk; a worker with its own worktree asks what to do with it) |
| L | Hang a big sign over the desk you face (*Operations*, *Code cleanup*), in one of seven colors; again to change it or take it down |
| O | Open a pull request for a worker on its own branch, or see the one it has (a worker across several projects gets one in each) |
| N | Go to the worker that has waited longest on someone; again for the next one |
| F | Hang a picture from the web on a wall (scroll to size it, click to hang it) |
Expand Down
4 changes: 3 additions & 1 deletion docs/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ Everything in the office, room by room. Back to the [README](../README.md).
- **Minesweeper on the boss's monitor.** In the boss's chair, **E** plays Minesweeper on the monitor: the camera moves in close and the board sits right on the screen. Click to dig, right-click (or Shift-click) to flag, and click a number to dig around it once its mines are flagged. The first click is never a mine. **Stop playing** leaves your game up on the monitor, with its clock paused, until you come back. Walk off to get up.
- **Pick your character.** The first time you join, a character select screen lets you choose your name, skin tone, hair style, hair color and shirt, with a spinning preview. No name in mind? Leave it blank (or skip the screen) and you go by the made-up one shown in the box, like *Sunny Otter*, or press **🎲** for another. The office remembers your name and look in that browser, so you only pick once. Change it any time from **⚙️** or by clicking your name under *In the office* (turn on **👥 People** in the **☰** menu).
- **Hire workers.** Walk up to an empty desk and press **E** to choose Claude Code, OpenCode or Codex, or press **P** to write a task first. A little worker sits down, a laptop opens, and the agent's live screen appears on it. Choose the agent for each queue task too.
- **Bean bags for the overflow.** There are 16 desks. Once they're all taken, a bean bag comes out on the floor with a low lap desk in front of it. Hire a worker there the same way as at a desk, and the next bean bag comes out, up to 12 around the room. The task queue seats workers on them too. A bean bag stays out while a worker sits on it, and the empty one goes away again as soon as a desk frees up.
- **Signs over the desks.** Press **L** at any desk, empty or not, to hang a big sign from the ceiling over it: *Operations*, *Code cleanup*, *Frontend*, whatever that desk is for. Type up to 32 characters (or pick one of the ideas), choose one of seven colors and press **Enter**. It hangs over the far edge of the desk, facing the chair, so it reads over the shoulder of whoever sits there; while the desk back to back with it has no sign of its own, it says the same on the back, so you can read it from across the room either way. Everyone on the floor sees it, the hint at the desk names it, and it stays up across restarts. **L** again changes it or takes it down. Each floor has its own.
- **Room to grow.** When a floor needs more desks, the north wall past the gong comes down. Walk up to the yellow **🚧 Room to grow** sign on it and press **E**, then **🔨 Knock through**: the wall goes, and a back office opens behind it with a pair of desks, a rug, a lamp and a window, in a puff of confetti. Knock through once more and it goes back another row, for two more desks (two rows is as far as it goes before the street behind the building). New workers, and the task queue, take its desks once the room's are full, before any bean bag comes out. The sign moves to the back wall, where **E** can also wall the last row back up, once nobody's working there. Each floor is built out on its own, and from outside you see it: the back office sticks out of that storey, on posts down to the lawn.
- **Bean bags for the overflow.** There are 16 desks, and up to 4 more in the back office. Once they're all taken, a bean bag comes out on the floor with a low lap desk in front of it. Hire a worker there the same way as at a desk, and the next bean bag comes out, up to 12 around the room. The task queue seats workers on them too. A bean bag stays out while a worker sits on it, and the empty one goes away again as soon as a desk frees up.
- **Shared shells.** Press **B** at an empty desk to open a plain login shell for dev servers, git or tests. It's shared the same way as a Claude terminal.
- **Isolated branches.** When you hire with a task, you can tick *own git worktree*. The worker then gets its own `office/<name>` branch under `.agent-office/worktrees/`, so parallel workers never share a checkout. The branch starts from the latest on GitHub, pull requests merged since anyone last pulled included, so a task never undoes the one before it. When you send that worker home you choose whether to keep the worktree and branch, delete the worktree, or delete both, and the office warns you first about uncommitted changes or commits no remote has. `agent-office prune` clears out whatever was kept, once it is safe. Under **⚙️** → *Workers whose pull request merged*, anyone can have them go home by themselves once their pull request merges: as soon as one isn't working or waiting on you and nobody has its terminal open, it leaves and its worktree and branch are deleted. Uncommitted changes, or commits that aren't on GitHub, keep the worktree, as when you send it home. It's off until someone turns it on, and turning it on sends home the ones already merged.
- **One task across several projects.** Many systems are split over repositories (an api, an admin, a web app), and one change often needs two or three of them. When you hire a worker in its own worktree, **🗂️ Also work in** lists the building's other floors: pick the projects the task spans (in the hire dialog, or in **🤖 Hand to a worker** / **✍️ Ask a worker** from the boards). The worker starts in a workspace folder with a worktree of each project in it, all on the same `office/<name>` branch, and a short brief (as `CLAUDE.md` and `AGENTS.md`) saying which folder is which. It works across them, tests them together and commits in each. **O** then opens a pull request in every project it committed to, and each description lists all of them, so they're reviewed and merged as one change. The issue the task came from is closed by the home floor's PR and linked from the others. Press **O** again for each project's PR, with a button to open the ones still missing. The Changes window has a tab per project, sending the worker home checks and deletes every worktree, and *Workers whose pull request merged* waits until the PRs in every project have merged. The brief can be rewritten in ⚙️ Settings like the office's other prompts.
Expand Down
6 changes: 3 additions & 3 deletions docs/maps.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ The easy way is to start from the castle and change only what you want. This one

To change the lists (move a pillar, resize the hall and everything in it), start from a copy of the whole castle instead: [`docs/maps/castle.json`](maps/castle.json) is it, as a map of your own called *My castle*. Copy it into the folder and it's in Settings; change what you like from there.

Or write one from nothing. This is about the least a map can be: a hall, a door, tables to seat 28, the board agents, a meeting table and the four boards. Everything else (the throne, the line, the herald, the props) is optional:
Or write one from nothing. This is about the least a map can be: a hall, a door, tables to seat 32, the board agents, a meeting table and the four boards. Everything else (the throne, the line, the herald, the props) is optional:

```json
{
Expand All @@ -50,7 +50,7 @@ Or write one from nothing. This is about the least a map can be: a hall, a door,
"door": { "x": 0, "z": 13.6 },
"tables": [
{ "name": "West table", "x": -5, "z": 0, "length": 12, "seats": 5 },
{ "name": "Middle table", "x": 0, "z": -2, "length": 10, "seats": 4 },
{ "name": "Middle table", "x": 0, "z": -2, "length": 10, "seats": 6 },
{ "name": "East table", "x": 5, "z": 0, "length": 12, "seats": 5 }
],
"stations": {
Expand Down Expand Up @@ -101,7 +101,7 @@ A map that won't load (bad JSON, something outside the hall, too few seats, a pr

### Seats

Every map has the same seats, by id, so that the server, the task queue, meetings and saved workers work on any of them: 16 regular seats (`desk-1` to `desk-16`), 12 more that come out once those are taken (`beanbag-1` to `beanbag-12`), the three board agents' places and the five meeting chairs. The tables' seats are handed out in order: first the side of every table toward the middle of the hall (its inner side), table by table in the order they're listed, then their other sides the same way. Along a table they go from one end to the other, north to south for one that runs along z. So `desk-1` is the first seat on the first table's inner side, and a map's tables must seat at least 28 between them; any seats past that are just bench.
Every map has the same seats, by id, so that the server, the task queue, meetings and saved workers work on any of them: 16 regular seats (`desk-1` to `desk-16`), 4 for the office's back office (`desk-17` to `desk-20`, only sat at once that floor's back office is built out that far), 12 more that come out once those are taken (`beanbag-1` to `beanbag-12`), the three board agents' places and the five meeting chairs. The tables' seats are handed out in order: first the side of every table toward the middle of the hall (its inner side), table by table in the order they're listed, then their other sides the same way. Along a table they go from one end to the other, north to south for one that runs along z. So `desk-1` is the first seat on the first table's inner side, and a map's tables must seat at least 32 between them; any seats past that are just bench.

### Props

Expand Down
5 changes: 4 additions & 1 deletion src/client/interaction.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { canLabel } from '../shared/floorplan';
import type { GhIssue, WorkerInfo } from '../shared/protocol';
import { isAsleep } from '../shared/status';
import type { Interactable } from './world/office';

export const DESK_KEYS = { KeyE: 'E', KeyP: 'P', KeyR: 'R', KeyX: 'X', KeyB: 'B', KeyC: 'C', KeyO: 'O' } as const;
export const DESK_KEYS = { KeyE: 'E', KeyP: 'P', KeyR: 'R', KeyX: 'X', KeyB: 'B', KeyC: 'C', KeyO: 'O', KeyL: 'L' } as const;
export type DeskKey = (typeof DESK_KEYS)[keyof typeof DESK_KEYS];

export interface InteractionState {
Expand All @@ -23,6 +24,8 @@ export function interactionAvailable(it: Interactable | null, key: DeskKey, stat

if (it.kind === 'desk') {
if (!it.deskId) return false;
// Any desk can have a sign hung over it, whoever sits there (not a bean bag or a meeting chair).
if (key === 'L') return canLabel(it.deskId);
if (!state.worker && state.room) return key === 'E';
if (key === 'B') return !state.worker;
if (key === 'P' || key === 'E') return true;
Expand Down
3 changes: 2 additions & 1 deletion src/client/lite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,8 @@ function hire(deskId: string, prompt: string, worktree: boolean, provider?: Agen

function sendToWorker(title: string, text: { context?: string; initial?: string } = {}) {
if (!store.project) return toast('Pick a floor first', 'warn');
const desk = nextFreeSeat((id) => !!store.workerAtDesk(id))?.id;
// The back office's desks too, as far as the floor's built out (see WING).
const desk = nextFreeSeat((id) => !!store.workerAtDesk(id), store.floorPlan.wing)?.id;
const awake = [...store.workers.values()].filter((w) => w.kind === 'agent' && !isAsleep(w.status));
if (!desk && !awake.length) return toast('Every desk and bean bag is taken — send a worker home first', 'warn');
openAsk({
Expand Down
Loading
Loading