Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
996136a
Add a persistent human playtest checklist separate from worker tasks
KajoGreenpost Oct 10, 2026
ed5a3c5
Organize playtests by mode and safely transfer manual issue checks
KajoGreenpost Oct 10, 2026
3e1280d
Refresh open checklist immediately after issue transfer
KajoGreenpost Oct 10, 2026
e1144d4
Add human decision pool and Boss Office monitor
KajoGreenpost Oct 11, 2026
3ce6a8d
Begin session-wide XR playtest selection with an implementation plan
KajoGreenpost Oct 11, 2026
f97079e
Implement session-wide Quest playtest companion, device bridge and ev…
KajoGreenpost Oct 11, 2026
96a7a0d
Bound stalled Operator handshakes and verify reconnection timeout
KajoGreenpost Oct 11, 2026
5107295
Add serialized Dev APK builds and authenticated resumable downloads
KajoGreenpost Oct 11, 2026
3933b65
Add separate Store APK builds and rename APK menu
KajoGreenpost Oct 11, 2026
db2ff4c
Add browser USB installation and launch for Quest APKs
KajoGreenpost Oct 11, 2026
d43db0e
Connect global playtest setup to verified Dev APK and browser XR comp…
KajoGreenpost Oct 11, 2026
89f31d5
Expose decisions, playtests and APK in 2D office
KajoGreenpost Oct 11, 2026
9950e01
Keep decision label readable on narrow screens
KajoGreenpost Oct 11, 2026
8a08267
Route APK downloads to local Office when reachable
KajoGreenpost Oct 11, 2026
40c6955
Clarify local Office sign-in address
KajoGreenpost Oct 11, 2026
81e6336
Widen 2D playtests and recover stalled Quest connection
KajoGreenpost Oct 11, 2026
e1ff968
Require worker issues in external review handoff
KajoGreenpost Oct 11, 2026
2e8fbd5
Track decisions through implementation follow-ups
KajoGreenpost Oct 12, 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
23 changes: 22 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,32 @@
> Expect breaking changes between releases: keys that move, screens that get redrawn, features that come and go
> without notice. If it's close to what you want, fork or clone it and bend it into what you need it to be.

Headset-Testbegleitung für die gesamte Spielsession: [Einrichtung und Bedienung](docs/xr-playtests.md). Der globale Einstieg liegt oben in der Playtest-Checkliste; passende Tests folgen dem tatsächlichen Spielmodus und Solo/Koop-Kontext.

<div align="center">

*"Whatever you do, work heartily, as for the Lord and not for men."* — Colossians 3:23 (ESV)

# 🏢 Agent Office

Playtest-Vorbereitung direkt im Browser: **Playtest-Checkliste → XR Operator & Dev-APK** prüft die installierte APK per SHA-256, aktualisiert bei Bedarf auf die neueste erfolgreiche Dev-APK und startet die sessionweite Begleitung über USB. Der Operator ist im instrumentierten Dev-Build enthalten. Im APK-Menü führt ein lokaler Download direkt zum Office-Rechner, wenn der Browser ihn erreichen kann; andernfalls bleibt der Domain-Download und ein Link zum lokalen Office verfügbar. [Ablauf](docs/xr-playtests.md) · [Prompt für externe Reviews und getrennte Übergabe von Tasks, Entscheidungen und Playtests](docs/external-review-prompt.md).

Bei einer externen Code-Review werden konkrete Bugs und umsetzbare Tasks zuerst mit vorhandenen GitHub-Issues abgeglichen und fehlende Issues angelegt. Entscheidungen und Playtests ergänzen diese Issues; die Queue bleibt ausschließlich unter deiner Kontrolle.

APKs für entfernte Tester: **☰ → APK** bietet getrennte Quest-Development- und signierte Store-Builds. Beide Varianten haben ihren eigenen neuesten erfolgreichen Download hinter dem Office-Login, auch über den bestehenden HTTPS-Tunnel. [Einrichtung und Bedienung](docs/dev-apk.md).

Direkt auf die Quest installieren: Im APK-Fenster **Quest verbinden** wählen. Chrome oder Edge verbindet sich per USB mit der Quest am Rechner des jeweiligen Testers. Die neueste Dev-/Store-APK oder eine ausgewählte Datei lässt sich mit Fortschrittsanzeige installieren und anschließend starten. [Browser-Installation](docs/quest-browser-install.md).

In der 2D-Ansicht nutzt die Playtest-Checkliste auf Desktop-Bildschirmen mehr Platz. Bei ausbleibender Quest-Antwort endet der Verbindungsversuch nach 45 Sekunden; **Abbrechen** und ein neuer Verbindungsversuch sind möglich.

**A 3D office your team shares with its coding agents.**

**Personal playtests:** the rolling checklist board and **☰ → Playtest checklist** organize checks by
Allgemein, RB, RIFT, Rooftop, Safe Zone and Gas Station, with play-style filters and build-specific results.
Use **Issues aussortieren** in the checklist or Issue agent terminal to preview transferring human checks
out of open issues. Implementation and documentation stay in issues; manual checks do not block workers
or merges. See [Playtest checklist](docs/playtests.md).

Sit **Claude Code**, **Codex**, **OpenCode**, **Grok**, **Muse**, **DeepSeek Harness** and **Cursor** workers at desks, watch each one's terminal on the laptop in front of it,
and jump into any of them together. Every GitHub repo is a floor of the building.

Expand All @@ -36,10 +54,11 @@ curl -fsSL https://raw.githubusercontent.com/AgentSystemLabs/agent-office/main/i
- **Workers at desks.** Walk up to an empty desk, press **E**, and pick Claude Code, Codex, OpenCode, Grok, Muse, DeepSeek Harness, Pi or Cursor, each with its model and reasoning effort. The agent's live terminal shows on its laptop, and anyone can open it and type.
- **Talk instead of typing.** Hold **Ctrl+Space** (or the **🎤**) in a worker's terminal or a prompt box and say what you want: it's typed in for you to send. Your browser does the listening, so there's nothing to install.
- **You can't miss who needs you.** A worker that stops to ask you something lights a red beacon over its desk, puts a banner on your screen saying who and what for, and sounds an alarm. One that has finished jumps up and down and dings. Press **N** to go straight to whoever is waiting.
- **From your phone, too.** `/lite` is the office in 2D: every worker and what it's waiting on, its terminal with the keys a phone keyboard lacks, and the boards. The 3D office offers it on a phone or a slow computer.
- **From your phone, too.** `/lite` is the office in 2D: every worker and what it's waiting on, its terminal with the keys a phone keyboard lacks, Issues, PRs, Queue, Entscheidungen, Playtests and APK. The 3D office offers it on a phone or a slow computer.
- **GitHub on the walls.** Issues and pull requests hang on cork boards. Hand an issue to a worker, queue tasks, give a worker its own git worktree and open its PR with one key (if one gets deleted behind the office's back, the worker waits at its desk until you rebuild it). One task can span several projects: the worker gets a worktree of each, and a PR in each that links the others.
- **Agents that manage agents.** Every worker can list, hire, message and send home the others, through an `agent-office` MCP server (Claude Code, Codex, OpenCode) or the `office-workers` command. Ask one to "send everyone whose PR merged home" and it does, deleting their worktrees and branches unless they hold unpushed work. A worker that opens its pull request itself (`gh pr create`) shows it at its desk, and one the office missed can be told which is its own (`office-workers pr`).
- **Together.** Voice, chat, screen sharing on the lounge TV and a shared whiteboard.
- **Decisions for the project owner.** The left monitor on the Boss Office desk opens a [decision pool](docs/decisions.md); Minesweeper stays on the right. Workers submit questions with issue links, categories and tags. Real work blockers light up the monitor and appear first. The Issue Agent scans for decisions alongside human playtests. Every saved answer receives a durable follow-up: the Issue Agent checks code, issues and PRs, creates or links an open implementation issue when needed, and records the result. Older answers are audited too. Only the owner may confirm implementation or accept that no work is needed. This never adds work to the queue.

- **Other maps.** Turn the whole building into a castle: sit on a throne of iron blades while your workers line up before you when they're done, send new ones off through the Hand of the King, and watch their beards grow long and grey as they toil. Send one home and the Kingsguard runs up from the dungeon, marches it down the stairs and throws it in a cell, where it starves, dies and rots down to a skeleton. Or into a space station in orbit, the Earth turning outside its windows: you run it from the captain's chair on the bridge, and a worker sent home is marched to the airlock and blown out into space, to drift off past the observation windows with everyone who went before it. Or make a map of your own, with its own way of seeing workers off in JSON ([docs/maps.md](docs/maps.md)).

Expand Down Expand Up @@ -420,3 +439,5 @@ Every change to the app that lands on `main` is published as a GitHub release by
## License

[MIT](LICENSE)

The [XR playtest plan](docs/xr-playtest-plan.md) describes session-wide Quest assistance. The [session guide](docs/xr-playtests.md) covers the global checklist entry, local Quest bridge, headset hints and evidence, including the required development game build.
16 changes: 16 additions & 0 deletions bin/office-decisions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env node
import { pathToFileURL } from 'node:url';
export async function main(args = process.argv.slice(2), env = process.env) {
const command = args[0];
if (!command || command === '--help') { console.log('office-decisions list | add | report (JSON on stdin). Human answers are read-only for workers.'); return; }
if (!['list', 'add', 'report'].includes(command)) throw Error('Use list, add or report. Only a human can answer in the Office.');
if (!env.AGENT_OFFICE_HOOK_URL || !env.AGENT_OFFICE_WORKER_ID || !env.AGENT_OFFICE_HOOK_TOKEN) throw Error('Run inside an Office worker.');
const url = new URL('/office/decisions', env.AGENT_OFFICE_HOOK_URL);
if (!['127.0.0.1', 'localhost', '[::1]'].includes(url.hostname) || url.protocol !== 'http:') throw Error('Expected local Office hook server.');
url.searchParams.set('worker', env.AGENT_OFFICE_WORKER_ID);
let body;
if (command === 'add' || command === 'report') { let text = ''; for await (const chunk of process.stdin) { text += chunk; if (Buffer.byteLength(text) > 24000) throw Error('Question too long.'); } body = JSON.stringify(command === 'report' ? { ...JSON.parse(text), action: 'report' } : JSON.parse(text)); }
const res = await fetch(url, { method: body ? 'POST' : 'GET', body, headers: { authorization: 'Bearer ' + env.AGENT_OFFICE_HOOK_TOKEN, 'content-type': 'application/json' }, signal: AbortSignal.timeout(15000) });
const result = await res.json(); if (!res.ok) throw Error(result.error || 'Office request failed'); console.log(JSON.stringify(result, null, 2));
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) main().catch(e => { console.error(e.message); process.exitCode = 1; });
23 changes: 23 additions & 0 deletions bin/office-playtests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/usr/bin/env node
// Human test handoffs, deliberately separate from office-queue.
const usage = 'office-playtests list | office-playtests add (JSON on stdin: title, steps, expected, category, source)';
async function main() {
const cmd = process.argv[2];
if (!cmd || cmd === '--help') { console.log(usage); return; }
if (!['list', 'add'].includes(cmd) || process.argv.length > 3) throw new Error(usage);
const { AGENT_OFFICE_HOOK_URL: base, AGENT_OFFICE_WORKER_ID: worker, AGENT_OFFICE_HOOK_TOKEN: token } = process.env;
if (!base || !worker || !token) throw new Error('Run this from an existing Office worker terminal');
const url = new URL('/office/playtests', base); url.searchParams.set('worker', worker);
const headers = { authorization: `Bearer ${token}`, 'content-type': 'application/json' };
let body;
if (cmd === 'add') {
let input = '';
for await (const chunk of process.stdin) { input += chunk; if (input.length > 24000) throw new Error('Test too large'); }
body = JSON.stringify({ action: 'add', test: JSON.parse(input.replace(/^\uFEFF/, '')) });
}
const r = await fetch(url, { method: cmd === 'list' ? 'GET' : 'POST', headers, body, signal: AbortSignal.timeout(15000) });
const result = await r.json();
if (!r.ok) throw new Error(result.error ?? `Office returned ${r.status}`);
console.log(JSON.stringify(result, null, 2));
}
main().catch(e => { console.error(e.message); process.exitCode = 1; });
2 changes: 2 additions & 0 deletions bin/office-xr.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/usr/bin/env node
import('../dist/server/server/xr-playtests/bridge-cli.js').then(m => m.runBridge()).catch(err => { console.error(err.message); process.exitCode = 1; });
8 changes: 7 additions & 1 deletion docs/code-layout.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Code layout

The global playtest toolbar registers browser setup in `client/ui/xr-browser`. It reuses the APK USB adapter, exposes only the fixed on-device Operator endpoint, and shares the bounded MCP client in `shared/xr-operator.ts` with the optional PC bridge. `xr-browser.ts` in the HTTP registry validates the signed-in run owner, origin and per-run token before accepting browser telemetry.

The APK dialog mounts its extensions through `src/client/ui/dev-apk/extensions.ts`. Browser-to-Quest installation lives in `src/client/ui/quest-install/`: source streaming, bounded APK manifest parsing, USB adapter, installation lifecycle and UI stay separate. It requires no server-side ADB endpoint and does not add device state to the shared office store.

Back to the [README](../README.md).

Where the code lives, and where a new feature's pieces go. The office used to grow by adding to the middle of a few big files: the server's message switch, the page's key handler, its frame loop and its store. Each of those is a registry now, and a feature is a folder of its own plus one line in each list it joins. If a change has you editing the middle of `main.ts`, `server.ts`, the store or `protocol.ts`, it probably belongs somewhere else.
Expand Down Expand Up @@ -40,7 +44,7 @@ They're in `core/registry.ts`, and each is a field of `ctx`. Every registration
- **`ui/`** is the app shell: the HUD, the menu and the windows (settings, the palette, terminals, changes, the queue, and the GitHub windows in `ui/github/`). Each module imports its own stylesheet (`import './palette.css'`), as a feature's `ui.ts` does (`import './ui.css'`).
- **`world/`** is the engine and the scenery: toon materials and shapes (`toon.ts`), the characters (`world/character/`), the office floor (`world/office/`), the castle (`world/castle/`), the space station (`world/station/`), the scenic loop, the sky and the city. The types they share (what you bump into, what you can use, the seats, `Office`) are in `world/types.ts`.
- **`sound/`** is the office's sound. `OfficeSound` (`sound/index.ts`, the `ctx.sound` every part uses) is a facade over `AudioCore` (`sound/core.ts`: the audio context, its buses, where your ears are) and the recipes, each in a file of its own, here (`weather.ts`, `steps.ts`) or in its feature's folder (`features/gong/sound.ts`).
- **`shared/`** (`src/client/shared/`) is what the 3D office and the 2D view at `/lite` both use: the tab title and hiring. The 2D view loads no three.js and nothing from `core/`, `features/`, `input/`, `world/` or `player/`; `tests/client-structure.test.ts` follows `lite.ts`'s imports to check.
- **`shared/`** (`src/client/shared/`) is what the 3D office and the 2D view at `/lite` both use: the tab title and hiring. The 2D view reuses the feature dialogs for Entscheidungen, Playtests and APK through its navigation bar, while remaining free of three.js and world rendering. `tests/client-structure.test.ts` follows `lite.ts`'s imports to check.

### Stylesheets

Expand Down Expand Up @@ -87,6 +91,8 @@ One adapter file in `src/server/providers/`, one entry in `PROVIDERS` in `src/se

## The size guard

Human decisions live in `src/client/features/decisions`, `src/server/decisions` and `src/shared/decisions.ts`. The world fixture adds the left Boss Office monitor, while the HUD action opens the same modal. `src/server/hooks/routes.ts` registers loopback worker endpoints, and `src/server/workers/prompt-extensions.ts` composes module-owned instructions for all providers. See [Human decisions](decisions.md) for the CLI, permissions and answer workflow.

`tests/size.test.ts` holds every `.ts` and `.css` file under `src/` to 600 lines. It asks `git ls-files` for them (tracked, and new ones that aren't ignored) rather than walking the folder, so a worktree checked out inside the repo can't trip it. The files that were longer when it came in are listed in `CEILINGS`, each with the length it had then: they may shrink, but never grow past it. Once one is down to 600 lines or fewer, or gone, the test fails until you take it off the list, so the list only gets shorter.

When it fails, split the file along the registries: a feature's code goes in its folder, a message handler in its domain's file, a tick or a key in the feature it's for, and what the page keeps of it in its own slice. Raising a ceiling, or adding a file to the list, only hands the problem to the next person, so the honest way to make room is to split the file. When you shrink a listed file, lower its ceiling to its new length in the same change, so it can't grow back.
Loading