agent-office-studio 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +47 -1
- package/README.md +1 -1
- package/bin/agentoffice.js +65 -5
- package/dist/assets/index-CiEWLydu.css +32 -0
- package/dist/assets/index-S_6NhxZ0.js +4457 -0
- package/dist/index.html +23 -9
- package/docs/README.md +1 -0
- package/docs/claude-access.md +2 -2
- package/docs/configuration.md +6 -2
- package/docs/development.md +16 -0
- package/docs/getting-started.md +4 -1
- package/docs/guides/troubleshooting.md +13 -2
- package/docs/guides/updating.md +26 -20
- package/docs/jobs.md +21 -0
- package/docs/leaderboard.md +8 -6
- package/docs/linked-offices.md +81 -0
- package/docs/phone.md +6 -2
- package/docs/security.md +37 -5
- package/docs/terminal.md +3 -3
- package/package.json +12 -11
- package/server/changes.js +4 -2
- package/server/chief.js +20 -3
- package/server/guests.js +391 -0
- package/server/index.js +468 -42
- package/server/leaderboard.js +63 -14
- package/server/links.js +350 -0
- package/server/office-mcp.js +5 -0
- package/server/settings.js +85 -0
- package/server/supervise.js +2 -0
- package/server/terminal.js +1 -1
- package/server/voice.js +3 -1
- package/dist/assets/index-CozPk6R9.js +0 -4457
- package/dist/assets/index-i7sU9SN-.css +0 -32
package/dist/index.html
CHANGED
|
@@ -24,47 +24,59 @@
|
|
|
24
24
|
} catch (e) {}
|
|
25
25
|
})();
|
|
26
26
|
</script>
|
|
27
|
-
<title>
|
|
28
|
-
<script type="module" crossorigin src="/assets/index-
|
|
29
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
27
|
+
<title>Office</title>
|
|
28
|
+
<script type="module" crossorigin src="/assets/index-S_6NhxZ0.js"></script>
|
|
29
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CiEWLydu.css">
|
|
30
30
|
</head>
|
|
31
31
|
<body>
|
|
32
32
|
<div class="app" id="app" data-m="office">
|
|
33
33
|
<aside class="side">
|
|
34
|
+
<div class="side-grip" id="side-grip" role="separator" aria-orientation="vertical" aria-label="Resize the side panel" tabindex="0"></div>
|
|
34
35
|
<header class="brand">
|
|
35
36
|
<div class="title">
|
|
36
|
-
<
|
|
37
|
+
<span class="title-start">
|
|
38
|
+
<span class="lv" role="img" id="lv" aria-label="Level 1"><svg viewBox="0 0 36 36" aria-hidden="true"><circle class="trk" cx="18" cy="18" r="15"/><circle class="arc" cx="18" cy="18" r="15" pathLength="100"/></svg><b id="lvNum">1</b></span>
|
|
39
|
+
<h1 id="officeName">Your Office</h1>
|
|
40
|
+
<button type="button" class="fold" id="fold" aria-label="Show usage" aria-expanded="false" aria-controls="gaugesBox" title="Show usage"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg></button>
|
|
41
|
+
</span>
|
|
37
42
|
<span class="title-end">
|
|
38
|
-
<span class="lv" id="lv">Lv 1</span>
|
|
39
43
|
<button type="button" class="theme-btn" id="lbBtn" aria-label="Leaderboard" aria-haspopup="true" aria-expanded="false" title="Leaderboard">
|
|
40
44
|
<svg viewBox="0 0 24 24"><path d="M8 4h8v5a4 4 0 0 1-8 0z"/><path d="M8 6H5v1a3 3 0 0 0 3 3"/><path d="M16 6h3v1a3 3 0 0 1-3 3"/><path d="M12 13v4"/><path d="M8.5 20h7"/></svg>
|
|
41
45
|
</button>
|
|
46
|
+
<button type="button" class="theme-btn" id="linkBtn" aria-label="Linked offices" aria-haspopup="true" aria-expanded="false" title="Linked offices">
|
|
47
|
+
<svg viewBox="0 0 24 24"><path d="M10 14a4.5 4.5 0 0 0 6.4 0l3-3a4.5 4.5 0 0 0-6.4-6.4l-1.2 1.2"/><path d="M14 10a4.5 4.5 0 0 0-6.4 0l-3 3a4.5 4.5 0 0 0 6.4 6.4l1.2-1.2"/></svg>
|
|
48
|
+
</button>
|
|
42
49
|
<button type="button" class="theme-btn" id="acctBtn" aria-label="Claude access" aria-haspopup="true" aria-expanded="false" title="Claude access">
|
|
43
50
|
<svg viewBox="0 0 24 24"><circle cx="8" cy="15" r="4"/><path d="M10.8 12.2 20 3"/><path d="m17 6 3 3"/><path d="m14.5 8.5 2 2"/></svg>
|
|
44
51
|
</button>
|
|
52
|
+
<button type="button" class="theme-btn" id="setBtn" aria-label="Settings" aria-haspopup="dialog" aria-expanded="false" title="Settings">
|
|
53
|
+
<svg viewBox="0 0 24 24"><path d="M4 7h9M17 7h3M4 17h3M11 17h9"/><circle cx="15" cy="7" r="2"/><circle cx="9" cy="17" r="2"/></svg>
|
|
54
|
+
</button>
|
|
45
55
|
<button type="button" class="theme-btn" id="themeBtn" aria-label="Appearance" aria-haspopup="true" aria-expanded="false" title="Appearance">
|
|
46
56
|
<svg viewBox="0 0 24 24"><path d="M12 3a9 9 0 1 0 0 18c1.4 0 2-.9 2-1.8 0-.9-.6-1.3-.6-2.2 0-1 .8-1.6 1.8-1.6H17a4 4 0 0 0 4-4C21 6.7 17 3 12 3z"/><circle cx="7.5" cy="11" r="1"/><circle cx="10" cy="7" r="1"/><circle cx="15" cy="7.5" r="1"/></svg>
|
|
47
57
|
</button>
|
|
48
58
|
</span>
|
|
49
59
|
<div class="theme-pop" id="themePop" role="dialog" aria-label="Appearance" hidden></div>
|
|
50
60
|
<div class="theme-pop lb-pop" id="lbPop" role="dialog" aria-label="Leaderboard" hidden></div>
|
|
61
|
+
<div class="theme-pop link-pop" id="linkPop" role="dialog" aria-label="Linked offices" hidden></div>
|
|
51
62
|
<div class="theme-pop acct-pop" id="acctPop" role="dialog" aria-label="Claude access" hidden></div>
|
|
52
63
|
</div>
|
|
53
64
|
<div class="level">
|
|
54
65
|
<div class="xpbar"><i id="xpbar"></i></div>
|
|
55
66
|
<span class="xp" id="xptext">0 / 100 XP</span>
|
|
56
67
|
</div>
|
|
68
|
+
<div class="gauges" id="gaugesBox" hidden>
|
|
57
69
|
<div class="usage" id="usage" aria-label="Plan usage">
|
|
58
70
|
<div class="meter" id="useSession">
|
|
59
71
|
<span class="k">Session</span>
|
|
60
72
|
<span class="v"></span>
|
|
61
|
-
<
|
|
73
|
+
<svg class="bar" viewBox="0 0 36 36" aria-hidden="true"><circle class="trk" cx="18" cy="18" r="15"/><circle class="arc" cx="18" cy="18" r="15" pathLength="100"/><g class="ico"><path d="M18 13v5l3 2"/></g></svg>
|
|
62
74
|
<span class="r"></span>
|
|
63
75
|
</div>
|
|
64
76
|
<div class="meter" id="useWeek">
|
|
65
77
|
<span class="k">Week</span>
|
|
66
78
|
<span class="v"></span>
|
|
67
|
-
<
|
|
79
|
+
<svg class="bar" viewBox="0 0 36 36" aria-hidden="true"><circle class="trk" cx="18" cy="18" r="15"/><circle class="arc" cx="18" cy="18" r="15" pathLength="100"/><g class="ico"><rect x="13" y="14" width="10" height="9" rx="1.5"/><path d="M13 17.5h10M16 12.5v2.5M20 12.5v2.5"/></g></svg>
|
|
68
80
|
<span class="r"></span>
|
|
69
81
|
</div>
|
|
70
82
|
<p class="u-err" id="useErr" hidden></p>
|
|
@@ -73,16 +85,17 @@
|
|
|
73
85
|
<div class="meter" id="loadCpu">
|
|
74
86
|
<span class="k">CPU</span>
|
|
75
87
|
<span class="v"></span>
|
|
76
|
-
<
|
|
88
|
+
<svg class="bar" viewBox="0 0 36 36" aria-hidden="true"><circle class="trk" cx="18" cy="18" r="15"/><circle class="arc" cx="18" cy="18" r="15" pathLength="100"/><g class="ico"><rect x="14" y="14" width="8" height="8" rx="1.5"/><path d="M16.5 12v2M19.5 12v2M16.5 22v2M19.5 22v2M12 16.5h2M12 19.5h2M22 16.5h2M22 19.5h2"/></g></svg>
|
|
77
89
|
<span class="r"></span>
|
|
78
90
|
</div>
|
|
79
91
|
<div class="meter" id="loadMem">
|
|
80
92
|
<span class="k">Memory</span>
|
|
81
93
|
<span class="v"></span>
|
|
82
|
-
<
|
|
94
|
+
<svg class="bar" viewBox="0 0 36 36" aria-hidden="true"><circle class="trk" cx="18" cy="18" r="15"/><circle class="arc" cx="18" cy="18" r="15" pathLength="100"/><g class="ico"><rect x="12.5" y="14.5" width="11" height="7" rx="1"/><path d="M15.5 21.5v1.5M18 21.5v1.5M20.5 21.5v1.5M15.5 17h1M19.5 17h1"/></g></svg>
|
|
83
95
|
<span class="r"></span>
|
|
84
96
|
</div>
|
|
85
97
|
</div>
|
|
98
|
+
</div>
|
|
86
99
|
</header>
|
|
87
100
|
<nav class="tabs">
|
|
88
101
|
<button type="button" data-tab="chat" class="on">Chat</button>
|
|
@@ -236,5 +249,6 @@
|
|
|
236
249
|
</button>
|
|
237
250
|
</nav>
|
|
238
251
|
</div>
|
|
252
|
+
<dialog class="settings" id="settings" aria-label="Settings"></dialog>
|
|
239
253
|
</body>
|
|
240
254
|
</html>
|
package/docs/README.md
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
- [Using the office](using-the-office.md) — the chat, channels, threads, standup, the break room
|
|
12
12
|
- [Jobs](jobs.md) — clearance, which model, what changed
|
|
13
13
|
- [The leaderboard](leaderboard.md) — an optional board of jobs, tokens and agent time
|
|
14
|
+
- [Linked offices](linked-offices.md) — see a friend's agents in your office, and invite guests to run yours
|
|
14
15
|
|
|
15
16
|
**Setting it up**
|
|
16
17
|
- [Claude access](claude-access.md) — Claude Code login or API key, and the plan meters
|
package/docs/claude-access.md
CHANGED
|
@@ -5,8 +5,8 @@ The key button beside Appearance chooses how the office signs in to Claude:
|
|
|
5
5
|
- **Claude Code login** — the login Claude Code keeps on this machine, so jobs
|
|
6
6
|
run on your Claude plan and the plan meters show how much of it is used.
|
|
7
7
|
- **API key** — an Anthropic API key, billed to that API account. Paste it
|
|
8
|
-
once; it's checked with Anthropic, kept in `
|
|
9
|
-
you) and never sent back to the browser. A key already in
|
|
8
|
+
once; it's checked with Anthropic, kept in `auth.json` in the data folder
|
|
9
|
+
(readable only by you) and never sent back to the browser. A key already in
|
|
10
10
|
`ANTHROPIC_API_KEY` is used until you paste another. The plan meters hide,
|
|
11
11
|
because there's no plan.
|
|
12
12
|
|
package/docs/configuration.md
CHANGED
|
@@ -4,14 +4,17 @@ Everything is set with environment variables when the server starts, e.g.
|
|
|
4
4
|
`PORT=4400 CHIEF_MODEL=haiku npm start`. In-app choices — Claude access, the
|
|
5
5
|
voice toggle, team profiles, theme — are made in the office and remembered.
|
|
6
6
|
|
|
7
|
+
**Settings page.** Open the sliders icon in the office header to change the owner, token limit, compaction point, chief model, standup time and weekends, sharing of job titles, and the break-room talk without restarting. A value saved there wins over the environment variable below, which becomes the default; "Use the default" puts it back. Everything else here is environment-only.
|
|
8
|
+
|
|
7
9
|
## The office
|
|
8
10
|
|
|
9
11
|
| Env | Default | |
|
|
10
12
|
|---|---|---|
|
|
11
13
|
| `OFFICE_ROOT` | the folder above this one; with `agentoffice`, the folder you run it from | its subfolders are the projects |
|
|
12
14
|
| `OFFICE_DATA` | `data/` here; with `agentoffice`, `~/.agent-office` | where the office keeps its state |
|
|
13
|
-
| `PORT` | `4317` | |
|
|
15
|
+
| `PORT` | `4317` | with `agentoffice`, a free port is chosen if this is taken (or `--port <n>`); one you set is used as given |
|
|
14
16
|
| `OFFICE_ALLOWED_HOSTS` | none | extra hostnames to accept, comma-separated; see [On your phone](phone.md) |
|
|
17
|
+
| `OFFICE_SHARE_JOB_TITLES` | unset | `1` lets linked offices see your agents' job titles, not just that they're busy |
|
|
15
18
|
|
|
16
19
|
## Claude
|
|
17
20
|
|
|
@@ -22,6 +25,7 @@ voice toggle, team profiles, theme — are made in the office and remembered.
|
|
|
22
25
|
| `CLAUDE_CODE_OAUTH_TOKEN` | none | a Claude Code login token, for the login mode and the plan meters |
|
|
23
26
|
| `CHIEF_MODEL` | `sonnet` | the chief of staff, and the standup write-up |
|
|
24
27
|
| `AGENT_COMPACT_PCT` | `60` | how full an agent's context gets before Claude Code compacts it |
|
|
28
|
+
| `AGENT_CLEAR_TOKENS` | `150000` | context size past which an agent's next message nudges it to `clear_context` and start fresh; `0` turns the nudge off; the limit set in the office (Claude access menu) overrides it |
|
|
25
29
|
|
|
26
30
|
## Standup and the break room
|
|
27
31
|
|
|
@@ -46,7 +50,7 @@ voice toggle, team profiles, theme — are made in the office and remembered.
|
|
|
46
50
|
|
|
47
51
|
| Env | Default | |
|
|
48
52
|
|---|---|---|
|
|
49
|
-
| `OFFICE_LEADERBOARD_URL` |
|
|
53
|
+
| `OFFICE_LEADERBOARD_URL` | the hosted board | address of a different leaderboard service; see [The leaderboard](leaderboard.md) |
|
|
50
54
|
| `OFFICE_TERMINAL` | unset | `1` opens the in-app terminal |
|
|
51
55
|
| `OFFICE_TERMINAL_SHELL` | your `$SHELL` | |
|
|
52
56
|
| `OFFICE_TERMINAL_MAX` | `4` | sessions at once |
|
package/docs/development.md
CHANGED
|
@@ -68,6 +68,22 @@ so you can check what a job was started with without spending tokens.
|
|
|
68
68
|
When you stop a test server, kill it by PID, not with `pkill -f`, which can't
|
|
69
69
|
tell it from the live one.
|
|
70
70
|
|
|
71
|
+
## Releasing
|
|
72
|
+
|
|
73
|
+
1. Move the changelog's *Unreleased* entries under a new version heading,
|
|
74
|
+
`## 0.2.0 — <date>`.
|
|
75
|
+
2. Set the same version in `package.json` (`npm version 0.2.0 --no-git-tag-version`).
|
|
76
|
+
3. Commit, then tag and push: `git tag v0.2.0 && git push origin master v0.2.0`.
|
|
77
|
+
|
|
78
|
+
The *Release* workflow checks the tag matches `package.json`, runs the build,
|
|
79
|
+
the smoke test and `scripts/pack-smoke.sh` (which installs the packed package
|
|
80
|
+
the way a user would), publishes to npm with provenance, and creates the
|
|
81
|
+
GitHub release from that changelog section. It needs an npm access token in
|
|
82
|
+
the repository secret `NPM_TOKEN`.
|
|
83
|
+
|
|
84
|
+
To try the package locally without publishing: `sh scripts/pack-smoke.sh`, or
|
|
85
|
+
`npm pack` and `npm install -g ./agent-office-studio-*.tgz`.
|
|
86
|
+
|
|
71
87
|
## The launch video
|
|
72
88
|
|
|
73
89
|
`launch/` holds the tooling that recorded the launch video: `setup.sh` resets a
|
package/docs/getting-started.md
CHANGED
|
@@ -18,7 +18,10 @@ npm install
|
|
|
18
18
|
npm start # builds, then serves on http://localhost:4317
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
Open
|
|
21
|
+
Open the link it prints, `Agent Office open on http://localhost:4317/?key=…`.
|
|
22
|
+
The key keeps other accounts on the machine out; your browser remembers it, so
|
|
23
|
+
after that <http://localhost:4317> is enough. Lost the link? `agentoffice --link`,
|
|
24
|
+
or from a clone, the key is in `data/office-key`.
|
|
22
25
|
|
|
23
26
|
## Where your projects are
|
|
24
27
|
|
|
@@ -5,8 +5,9 @@ Find the message you're seeing, or the symptom.
|
|
|
5
5
|
## The office won't start
|
|
6
6
|
|
|
7
7
|
**`Error: listen EADDRINUSE: address already in use 127.0.0.1:4317`**
|
|
8
|
-
Something is already on that port — usually another office.
|
|
9
|
-
|
|
8
|
+
Something is already on that port — usually another office. `agentoffice`
|
|
9
|
+
picks a free port on its own; to choose, use `agentoffice --port 4400`. With
|
|
10
|
+
`npm start`, find the other one with `lsof -i :4317` or set `PORT=4400`.
|
|
10
11
|
|
|
11
12
|
**The page says "No build yet."**
|
|
12
13
|
The server is running but `dist/` is empty. Run `npm run build`, or use
|
|
@@ -18,6 +19,16 @@ remote desktops and VMs), the 3D view can't draw.
|
|
|
18
19
|
|
|
19
20
|
## Jobs don't run
|
|
20
21
|
|
|
22
|
+
**Every message gets "I couldn't route that one".**
|
|
23
|
+
The chief is a short `claude -p` call, and it failed. The words after the colon
|
|
24
|
+
say why: an error from `claude` itself (signed out, out of credit, a model your
|
|
25
|
+
plan can't use), a timeout, or `claude` exiting without answering. To see the
|
|
26
|
+
same thing yourself, run this in the terminal you start the office from:
|
|
27
|
+
`claude -p --model sonnet "hi"`. It should answer within a few seconds. Fix
|
|
28
|
+
whatever it complains about, then send the message again. Versions before
|
|
29
|
+
0.3.0 only ever said "The chief could not decide"; update to see the reason.
|
|
30
|
+
`/standup` and `/restart` don't go through the chief.
|
|
31
|
+
|
|
21
32
|
**A job fails with "Could not start claude: spawn claude ENOENT".**
|
|
22
33
|
Claude Code isn't on the `PATH` the office was started with. Check
|
|
23
34
|
`which claude` in the same terminal. If it lives somewhere unusual, point the
|
package/docs/guides/updating.md
CHANGED
|
@@ -1,36 +1,42 @@
|
|
|
1
1
|
# Updating
|
|
2
2
|
|
|
3
|
-
Your office
|
|
4
|
-
|
|
3
|
+
Your office — jobs, chat, channels, XP, team profiles, Claude access — lives
|
|
4
|
+
in the data folder (`~/.agent-office`), which updates never touch.
|
|
5
5
|
|
|
6
|
-
##
|
|
6
|
+
## The installed command
|
|
7
7
|
|
|
8
8
|
```bash
|
|
9
|
-
|
|
10
|
-
git pull
|
|
11
|
-
npm install
|
|
12
|
-
npm run build
|
|
9
|
+
npm install -g agent-office-studio@latest
|
|
13
10
|
```
|
|
14
11
|
|
|
15
|
-
Then stop the office (Ctrl-C
|
|
16
|
-
|
|
12
|
+
Then stop the office (Ctrl-C) and start it again. Running jobs stop cleanly
|
|
13
|
+
and resume when it comes back.
|
|
14
|
+
|
|
15
|
+
Read the [changelog](../../CHANGELOG.md) for what changed and anything you
|
|
16
|
+
need to do. `agent-office --version` says what you're on.
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
need to do.
|
|
18
|
+
### A particular version
|
|
20
19
|
|
|
21
|
-
|
|
20
|
+
```bash
|
|
21
|
+
npm install -g agent-office-studio@0.2.0
|
|
22
|
+
```
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
## A clone
|
|
24
25
|
|
|
25
26
|
```bash
|
|
26
|
-
|
|
27
|
-
git
|
|
28
|
-
npm install
|
|
27
|
+
cd agent-office
|
|
28
|
+
git pull
|
|
29
|
+
npm install
|
|
30
|
+
npm run build
|
|
29
31
|
```
|
|
30
32
|
|
|
33
|
+
Then restart it with `npm start`. To stay on a release, check out its tag
|
|
34
|
+
(`git checkout v0.2.0`) instead of pulling. A clone's data is in `data/`.
|
|
35
|
+
|
|
31
36
|
## If something breaks
|
|
32
37
|
|
|
33
|
-
-
|
|
34
|
-
the
|
|
35
|
-
|
|
36
|
-
|
|
38
|
+
- **Keep a way back:** copy the data folder before updating. Going back is
|
|
39
|
+
then: stop the office, install the previous version, restore the folder,
|
|
40
|
+
start.
|
|
41
|
+
- **The terminal stops working after an update:** node-pty needs rebuilding;
|
|
42
|
+
see [Troubleshooting](troubleshooting.md#the-terminal-wont-open).
|
package/docs/jobs.md
CHANGED
|
@@ -33,6 +33,27 @@ never moves on its own.
|
|
|
33
33
|
The drawer shows each job's model and how much context its last turn carried,
|
|
34
34
|
so a session that has grown expensive is visible rather than guessed at.
|
|
35
35
|
|
|
36
|
+
An agent can also clear its own context. Its `clear_context` tool takes notes
|
|
37
|
+
to itself (what is done, what is left, what it learned); when it ends its
|
|
38
|
+
turn the office closes the session and starts the job again fresh, carrying the
|
|
39
|
+
brief, those notes and any message that arrived meanwhile. The history is what
|
|
40
|
+
costs tokens on every turn, so this is the way to shed it at a natural break.
|
|
41
|
+
Once a session's context passes `AGENT_CLEAR_TOKENS` (150,000 by default, `0` to
|
|
42
|
+
turn it off), the next message the agent receives carries a one-time nudge to do
|
|
43
|
+
this. Clearing is still the agent's call. The log records each clear, and it is
|
|
44
|
+
capped at five per job.
|
|
45
|
+
|
|
46
|
+
The boss can also just say "clear context" in chat, for everyone or for named
|
|
47
|
+
agents. The chief of staff does it directly: a working agent restarts fresh when
|
|
48
|
+
its current turn ends (carrying its last reply as notes), and a finished job
|
|
49
|
+
forgets its session so the next reply starts clean.
|
|
50
|
+
|
|
51
|
+
Neither kind of clear loses track of the work. The office keeps a short note on
|
|
52
|
+
the job (`lastClear`: the agent's last report, or the brief if it had not
|
|
53
|
+
reported) and shows it in the job's log. A reply to a cleared job carries the
|
|
54
|
+
note and the brief into the fresh session, and any new job the agent takes
|
|
55
|
+
mentions what they were last on, so "what were you doing?" still has an answer.
|
|
56
|
+
|
|
36
57
|
## What changed
|
|
37
58
|
|
|
38
59
|
When a job starts, the office snapshots the project's working tree; when it
|
package/docs/leaderboard.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# The leaderboard
|
|
2
2
|
|
|
3
3
|
An optional board where offices compare how much they got done. It is off
|
|
4
|
-
until you join
|
|
4
|
+
until you join. Every office uses the same hosted service, https://agent-office-leaderboard.agent-office-leaderboard.workers.dev,
|
|
5
|
+
so there is nothing to set up (the service's code is in
|
|
5
6
|
[`leaderboard/`](../leaderboard/README.md)).
|
|
6
7
|
|
|
7
8
|
## What is ranked
|
|
@@ -49,17 +50,18 @@ those day counters. Boards list handles and totals.
|
|
|
49
50
|
## Leaving
|
|
50
51
|
|
|
51
52
|
"Leave and delete my data" asks the service to delete your handle and counters,
|
|
52
|
-
then forgets the secret here. If the service can't be reached, nothing changes
|
|
53
|
+
then forgets the secret here. If you have [linked offices](linked-offices.md), only your counters are deleted: the handle stays, because the links need it. If the service can't be reached, nothing changes
|
|
53
54
|
and you can try again. Your local `data/activity.json` stays; delete it if you
|
|
54
55
|
want that gone too.
|
|
55
56
|
|
|
56
|
-
##
|
|
57
|
+
## Using a different service
|
|
57
58
|
|
|
58
|
-
|
|
59
|
+
The hosted address is built in. To use a board you host yourself instead, set
|
|
60
|
+
its address and restart the office:
|
|
59
61
|
|
|
60
62
|
```
|
|
61
63
|
OFFICE_LEADERBOARD_URL=https://your-service.example npm start
|
|
62
64
|
```
|
|
63
65
|
|
|
64
|
-
It must be `https` (plain `http` works only for `localhost`).
|
|
65
|
-
|
|
66
|
+
It must be `https` (plain `http` works only for `localhost`). Either way,
|
|
67
|
+
nothing is sent until you join.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Linked offices
|
|
2
|
+
|
|
3
|
+
Two Agent Offices on different machines can link with a code. Linked offices see
|
|
4
|
+
each other's agents walk into their 3D office as visitors. An office can also
|
|
5
|
+
invite a **guest**, who can then start jobs with the host's agents, within limits
|
|
6
|
+
the host sets.
|
|
7
|
+
|
|
8
|
+
Linking is off until you make or enter a code. It uses the same hosted service
|
|
9
|
+
as [the leaderboard](leaderboard.md), so there is nothing to set up.
|
|
10
|
+
|
|
11
|
+
## Linking two offices
|
|
12
|
+
|
|
13
|
+
1. One of you opens the chain button in the sidebar header and picks **Invite
|
|
14
|
+
an office**. If your office has no handle yet, it asks for one first. A
|
|
15
|
+
handle doesn't put you on the leaderboard.
|
|
16
|
+
2. Send the code (it looks like `K7QX-M2PD`) to the other person however you
|
|
17
|
+
like. It works once and expires after 24 hours.
|
|
18
|
+
3. They open the chain button, pick **Enter a code**, and paste it in. Case,
|
|
19
|
+
dashes and spaces don't matter.
|
|
20
|
+
|
|
21
|
+
Either of you can **Unlink** from the same panel at any time.
|
|
22
|
+
|
|
23
|
+
## What a linked office sees
|
|
24
|
+
|
|
25
|
+
Your agents' names, roles, looks, what they are doing (working, idle, on a
|
|
26
|
+
break, in a meeting, gone home). That's all: no job titles, projects, files,
|
|
27
|
+
logs, prompts, chat or cost. To let linked offices see the titles of the jobs
|
|
28
|
+
your agents are on, start the office with `OFFICE_SHARE_JOB_TITLES=1`.
|
|
29
|
+
|
|
30
|
+
Their agents walk in at the door, wear a visitor tint, and carry the office's
|
|
31
|
+
handle on their nameplate (`Kai · @friend`). They don't use your desks. When
|
|
32
|
+
they're working they stand at the hot desks, and on a break they join the pool
|
|
33
|
+
table or the coffee. Up to six visitors are shown at once; the rest are counted
|
|
34
|
+
on a sign by the door.
|
|
35
|
+
|
|
36
|
+
## Inviting a guest
|
|
37
|
+
|
|
38
|
+
A guest can start jobs with your agents, in your projects, on your Claude
|
|
39
|
+
account. Pick **Invite a guest** and choose:
|
|
40
|
+
|
|
41
|
+
- **Projects:** at least one. The guest can't see or touch any other project.
|
|
42
|
+
- **Clearance cap:** *Plan only*, or *Edit files* (the default). *Full autonomy*
|
|
43
|
+
has to be ticked on its own. It lets the guest run any command on this
|
|
44
|
+
machine.
|
|
45
|
+
- **Tokens per day:** 2,000,000 by default. Once a guest has used their tokens,
|
|
46
|
+
their running jobs stop and new ones are refused until midnight UTC.
|
|
47
|
+
|
|
48
|
+
A guest can start up to three jobs at a time, reply to them, stop them, and see
|
|
49
|
+
their logs and changes. They can't see your other jobs, chat or channels, and
|
|
50
|
+
they can't use the terminal, restart the office, or change staff or settings.
|
|
51
|
+
Their jobs show `from @handle` in your job list, and you can stop any of them.
|
|
52
|
+
Agents working for a guest can't message or hand work to your other agents.
|
|
53
|
+
They also don't get your MCP servers or claude.ai connectors (mail, Slack,
|
|
54
|
+
calendar and so on), your user-level Claude Code settings and hooks, your
|
|
55
|
+
Claude Code memory, or your attachments.
|
|
56
|
+
Unlinking a guest stops their running jobs straight away.
|
|
57
|
+
|
|
58
|
+
Even at *Edit files*, a guest can change files that run later, such as
|
|
59
|
+
`package.json` scripts, build files or git hooks. Only invite someone you would
|
|
60
|
+
trust with those projects. *Plan only* is the safe choice.
|
|
61
|
+
|
|
62
|
+
## Visiting as a guest
|
|
63
|
+
|
|
64
|
+
When an office has made you its guest, its row in the Links panel has **Visit
|
|
65
|
+
@host**. It opens the host's office, showing their agents and your jobs there.
|
|
66
|
+
A bar at the top shows whose office you're in and your limits, and **Back to my
|
|
67
|
+
office** takes you home. Only the projects and clearances you're allowed are
|
|
68
|
+
offered.
|
|
69
|
+
|
|
70
|
+
## What the service keeps
|
|
71
|
+
|
|
72
|
+
Your handle, a hash of your office's secret, which offices you're linked to, and
|
|
73
|
+
open invites (as hashes of the codes). Presence and messages pass through
|
|
74
|
+
without being stored. Guest limits and token counts stay on the host's machine,
|
|
75
|
+
in `data/links.json` and `data/guests.json`.
|
|
76
|
+
|
|
77
|
+
## Using a different service
|
|
78
|
+
|
|
79
|
+
`OFFICE_LEADERBOARD_URL` points linking at another service too, as described in
|
|
80
|
+
[The leaderboard](leaderboard.md#using-a-different-service). Offices can only
|
|
81
|
+
link if they use the same service.
|
package/docs/phone.md
CHANGED
|
@@ -16,13 +16,17 @@ The supported way is [Tailscale](https://tailscale.com): put the office behind
|
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
18
|
tailscale serve --bg 4317
|
|
19
|
-
OFFICE_ALLOWED_HOSTS=my-mac.tail1234.ts.net
|
|
19
|
+
OFFICE_ALLOWED_HOSTS=my-mac.tail1234.ts.net agent-office
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
`OFFICE_ALLOWED_HOSTS` takes exact hostnames, comma-separated. Every other
|
|
23
23
|
host and origin is still refused.
|
|
24
24
|
|
|
25
|
+
On the phone, open the tailnet address once with the office key on the end,
|
|
26
|
+
`https://my-mac.tail1234.ts.net/?key=…` (the key is in the link the office
|
|
27
|
+
printed, or `agentoffice --link`). The phone keeps it after that.
|
|
28
|
+
|
|
25
29
|
**Read this first.** Whoever can open that address can run Claude in your
|
|
26
|
-
projects with full autonomy, and — if `
|
|
30
|
+
projects with full autonomy, and — if started with `--terminal` — get a shell on
|
|
27
31
|
this machine. Only expose it on a tailnet that is just you. Never put it on
|
|
28
32
|
the open internet. See [Security](security.md).
|
package/docs/security.md
CHANGED
|
@@ -8,7 +8,14 @@ hand out a shell. It is built to be used by one person, on their own machine.
|
|
|
8
8
|
- The server listens on `127.0.0.1` only.
|
|
9
9
|
- Every request's `Host` and `Origin` must be local (or a name in
|
|
10
10
|
`OFFICE_ALLOWED_HOSTS`), which keeps other websites and DNS-rebinding
|
|
11
|
-
attacks out of it.
|
|
11
|
+
attacks out of it. A page on another localhost port counts as another
|
|
12
|
+
website.
|
|
13
|
+
- The office has a key, made on first start and kept in `data/office-key` with
|
|
14
|
+
mode `0600`. Other accounts on the machine can reach `127.0.0.1` too, so the
|
|
15
|
+
page, the API and the socket all need the key. The address the office prints
|
|
16
|
+
when it starts carries the key once; your browser keeps it in a cookie
|
|
17
|
+
(HttpOnly, SameSite=Strict). `agentoffice --link` prints that address again.
|
|
18
|
+
Delete `data/office-key` and restart to sign every browser out.
|
|
12
19
|
- Agents call back into the office (the roster, messages, hand-offs) with a
|
|
13
20
|
per-job token that dies with the job.
|
|
14
21
|
|
|
@@ -28,21 +35,46 @@ command you could.
|
|
|
28
35
|
`claude` it starts reads them. The plan meters read the login's access token
|
|
29
36
|
to ask for usage, and nothing else.
|
|
30
37
|
- With an **API key**, the key is checked with Anthropic, stored in
|
|
31
|
-
`
|
|
38
|
+
`auth.json` in the data folder with mode `0600`, passed only to the `claude` processes the
|
|
32
39
|
office starts, and never sent to the browser, which sees the last four
|
|
33
40
|
characters.
|
|
34
|
-
-
|
|
41
|
+
- The data folder is outside the package (`~/.agent-office`), or git-ignored `data/` in a clone.
|
|
35
42
|
|
|
36
43
|
## The leaderboard
|
|
37
44
|
|
|
38
45
|
Off until you join, and then the only thing besides Claude that the office
|
|
39
46
|
talks to. It sends a handle and three counters per day (jobs, tokens, agent
|
|
40
|
-
time) to the
|
|
47
|
+
time) to the hosted leaderboard service (or the one named by
|
|
48
|
+
`OFFICE_LEADERBOARD_URL`), signed with a random
|
|
41
49
|
secret kept in `data/leaderboard.json` (mode `0600`). Never prompts, projects,
|
|
42
50
|
files, chat or cost. Details in [The leaderboard](leaderboard.md).
|
|
43
51
|
|
|
52
|
+
## Linked offices and guests
|
|
53
|
+
|
|
54
|
+
Off until you make or enter a code. The office only ever connects out to the
|
|
55
|
+
service, so nothing listens for other offices. A link shares your agents' names,
|
|
56
|
+
roles, looks and states (working, on a break and so on), and nothing else. Job
|
|
57
|
+
titles stay home, since a title is the first line of a prompt;
|
|
58
|
+
`OFFICE_SHARE_JOB_TITLES=1` shares them. Everything a linked office
|
|
59
|
+
sends is checked and trimmed before your browser sees it.
|
|
60
|
+
|
|
61
|
+
A **guest** runs jobs on your machine, on your Claude account. Everything is
|
|
62
|
+
enforced on your side and the guest's office is never trusted:
|
|
63
|
+
|
|
64
|
+
- only the projects you picked;
|
|
65
|
+
- at or below the clearance cap you set;
|
|
66
|
+
- within the tokens per day you set;
|
|
67
|
+
- no terminal, settings, chat, or other people's jobs;
|
|
68
|
+
- their agents can't reach your other agents;
|
|
69
|
+
- their jobs don't get your MCP servers or claude.ai connectors, your
|
|
70
|
+
user-level Claude Code settings and hooks, or your Claude Code memory.
|
|
71
|
+
|
|
72
|
+
Even *Edit files* can change scripts or hooks that run later, so invite only
|
|
73
|
+
people you would trust with those projects. Details in
|
|
74
|
+
[Linked offices](linked-offices.md).
|
|
75
|
+
|
|
44
76
|
## The terminal
|
|
45
77
|
|
|
46
|
-
Off unless `OFFICE_TERMINAL=1
|
|
78
|
+
Off unless started with `--terminal` (`OFFICE_TERMINAL=1`). A session needs a single-use ticket from the
|
|
47
79
|
origin-checked API, no agent can open one, and keystrokes are never logged. It
|
|
48
80
|
is not a sandbox. Details in [The terminal](terminal.md).
|
package/docs/terminal.md
CHANGED
|
@@ -10,7 +10,7 @@ the way they do in a terminal app.
|
|
|
10
10
|
It is off until you ask for it:
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
|
-
OFFICE_TERMINAL=1 npm start
|
|
13
|
+
agent-office --terminal # or, in a clone: OFFICE_TERMINAL=1 npm start
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
That switch exists because shell access is the one door in the building that
|
|
@@ -26,8 +26,8 @@ So:
|
|
|
26
26
|
- **Not for the staff.** No agent can open one. There's no office tool and no
|
|
27
27
|
`/api/agent` route that reaches it, and a shell never inherits the job
|
|
28
28
|
credentials the office gives its own children.
|
|
29
|
-
- **Nothing is kept.** Not in `
|
|
30
|
-
main socket. Scrollback lives in the tab and dies with it. `
|
|
29
|
+
- **Nothing is kept.** Not in `state.json`, not in the chat, not on the
|
|
30
|
+
main socket. Scrollback lives in the tab and dies with it. `terminal.log` in the data folder
|
|
31
31
|
records when sessions opened and closed and in which folder — never
|
|
32
32
|
keystrokes, because a log of what you typed into a shell is a file full of
|
|
33
33
|
passwords.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-office-studio",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "A 3D office where the staff are Claude Code agents.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"files": [
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"server",
|
|
10
10
|
"shared",
|
|
11
11
|
"docs",
|
|
12
|
+
"!docs/proposals",
|
|
12
13
|
"CHANGELOG.md",
|
|
13
14
|
"LICENSE",
|
|
14
15
|
"README.md"
|
|
@@ -44,21 +45,21 @@
|
|
|
44
45
|
"start": "vite build && node server/supervise.js",
|
|
45
46
|
"voice:model": "mkdir -p data/whisper && curl -L --progress-bar -o data/whisper/ggml-base.en.bin https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.en.bin",
|
|
46
47
|
"voice:piper": "python3 -m venv data/piper/venv && data/piper/venv/bin/pip install -q piper-tts && data/piper/venv/bin/python -m piper.download_voices --data-dir data/piper/voices en_GB-cori-high en_US-amy-medium en_GB-alan-medium en_US-kristin-medium en_US-ryan-high en_GB-alba-medium en_GB-aru-medium en_US-john-medium en_US-hfc_female-medium",
|
|
47
|
-
"test": "node scripts/smoke.mjs",
|
|
48
|
+
"test": "node scripts/smoke.mjs && node scripts/guests.mjs",
|
|
48
49
|
"prepack": "vite build"
|
|
49
50
|
},
|
|
50
51
|
"dependencies": {
|
|
51
|
-
"
|
|
52
|
-
"@fontsource/jetbrains-mono": "5.1.1",
|
|
53
|
-
"@xterm/addon-fit": "0.10.0",
|
|
54
|
-
"@xterm/xterm": "5.5.0",
|
|
55
|
-
"marked": "^14.1.3",
|
|
56
|
-
"three": "0.169.0",
|
|
57
|
-
"ws": "8.18.0"
|
|
52
|
+
"ws": "8.22.0"
|
|
58
53
|
},
|
|
59
54
|
"devDependencies": {
|
|
60
|
-
"
|
|
61
|
-
"
|
|
55
|
+
"@fontsource-variable/inter": "5.3.0",
|
|
56
|
+
"@fontsource/jetbrains-mono": "5.3.0",
|
|
57
|
+
"@xterm/addon-fit": "0.11.0",
|
|
58
|
+
"@xterm/xterm": "6.0.0",
|
|
59
|
+
"concurrently": "10.0.6",
|
|
60
|
+
"marked": "^18.1.0",
|
|
61
|
+
"three": "0.186.1",
|
|
62
|
+
"vite": "6.4.3"
|
|
62
63
|
},
|
|
63
64
|
"optionalDependencies": {
|
|
64
65
|
"node-pty": "1.0.0"
|
package/server/changes.js
CHANGED
|
@@ -11,7 +11,9 @@ const MAX_FILES = 200;
|
|
|
11
11
|
const MAX_DIFF = 400 * 1024;
|
|
12
12
|
|
|
13
13
|
const git = (cwd, args, env = {}) => new Promise((resolve, reject) => {
|
|
14
|
-
|
|
14
|
+
// A project's own config can name commands git runs (fsmonitor, diff
|
|
15
|
+
// drivers); none of them should run just because a job ended.
|
|
16
|
+
execFile('git', ['-c', 'core.fsmonitor=false', ...args], { cwd, env: { ...process.env, ...env }, maxBuffer: 64 * 1024 * 1024, timeout: 60000 }, (err, out) => {
|
|
15
17
|
if (err) reject(err); else resolve(out);
|
|
16
18
|
});
|
|
17
19
|
});
|
|
@@ -67,6 +69,6 @@ export async function summarize(cwd, base, end) {
|
|
|
67
69
|
// The patch for one file, only when the boss asks for it.
|
|
68
70
|
export async function fileDiff(cwd, changes, file) {
|
|
69
71
|
if (!changes?.files.some((f) => f.path === file)) throw new Error('That file is not part of this job’s changes.');
|
|
70
|
-
const out = await git(cwd, ['diff', '--no-renames', '--no-color', changes.base, changes.end, '--', file]);
|
|
72
|
+
const out = await git(cwd, ['diff', '--no-renames', '--no-color', '--no-ext-diff', '--no-textconv', changes.base, changes.end, '--', file]);
|
|
71
73
|
return out.length > MAX_DIFF ? `${out.slice(0, MAX_DIFF)}\n… diff cut at ${MAX_DIFF / 1024} KB` : out;
|
|
72
74
|
}
|