agent-office-studio 0.8.0 → 0.9.2
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 +61 -0
- package/README.md +12 -0
- package/dist/assets/code-Dtdckl8K.js +32 -0
- package/dist/assets/index-BcqWv4NW.css +1 -0
- package/dist/assets/index-qdaAZdZT.js +5065 -0
- package/dist/assets/pdf-BtCWNl9F.js +55 -0
- package/dist/assets/pdf-CKZBNrtF.js +1 -0
- package/dist/assets/pdf.worker.min-BJd0dv5W.js +53 -0
- package/dist/assets/pdf.worker.min-D_0FwaWY.js +1 -0
- package/dist/index.html +39 -35
- package/dist/theme-boot.js +13 -0
- package/docs/desktop.md +52 -0
- package/docs/features.md +96 -0
- package/package.json +19 -2
- package/server/apps.js +146 -0
- package/server/changes.js +13 -2
- package/server/chief.js +20 -1
- package/server/economy.js +26 -3
- package/server/files.js +115 -0
- package/server/guests.js +59 -21
- package/server/index.js +897 -72
- package/server/links.js +11 -4
- package/server/machine.js +164 -0
- package/server/office-mcp.js +26 -1
- package/server/procstat.js +12 -6
- package/server/settings.js +2 -0
- package/server/telegram.js +320 -0
- package/server/visits.js +14 -4
- package/shared/shop.js +11 -4
- package/dist/assets/index-BAvX7Imu.css +0 -1
- package/dist/assets/index-C01GpSoa.js +0 -4999
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function r(e){return new Worker("/assets/pdf.worker.min-BJd0dv5W.js",{name:e==null?void 0:e.name})}export{r as default};
|
package/dist/index.html
CHANGED
|
@@ -11,27 +11,14 @@
|
|
|
11
11
|
<link rel="manifest" href="/manifest.webmanifest" />
|
|
12
12
|
<link rel="icon" href="/icon.svg" type="image/svg+xml" />
|
|
13
13
|
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
|
14
|
-
<script>
|
|
15
|
-
// Before first paint, so a saved theme never flashes the default. theme.js owns it after that.
|
|
16
|
-
(function () {
|
|
17
|
-
try {
|
|
18
|
-
var t = localStorage.getItem('office.theme') || 'paper';
|
|
19
|
-
var m = localStorage.getItem('office.mode') || 'auto';
|
|
20
|
-
var d = m === 'dark' || (m === 'auto' && matchMedia('(prefers-color-scheme: dark)').matches);
|
|
21
|
-
var r = document.documentElement;
|
|
22
|
-
r.dataset.theme = t;
|
|
23
|
-
r.dataset.mode = d ? 'dark' : 'light';
|
|
24
|
-
} catch (e) {}
|
|
25
|
-
})();
|
|
26
|
-
</script>
|
|
14
|
+
<script src="/theme-boot.js"></script>
|
|
27
15
|
<title>Office</title>
|
|
28
|
-
<script type="module" crossorigin src="/assets/index-
|
|
29
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
16
|
+
<script type="module" crossorigin src="/assets/index-qdaAZdZT.js"></script>
|
|
17
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BcqWv4NW.css">
|
|
30
18
|
</head>
|
|
31
19
|
<body>
|
|
32
20
|
<div class="app" id="app" data-m="office">
|
|
33
21
|
<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>
|
|
35
22
|
<header class="brand">
|
|
36
23
|
<div class="title">
|
|
37
24
|
<span class="title-start">
|
|
@@ -105,12 +92,14 @@
|
|
|
105
92
|
<button type="button" data-tab="chat" class="on">Chat</button>
|
|
106
93
|
<button type="button" data-tab="jobs">Jobs <span id="jobCount"></span></button>
|
|
107
94
|
<button type="button" data-tab="projects">Projects</button>
|
|
95
|
+
<button type="button" data-tab="apps">Apps</button>
|
|
108
96
|
<button type="button" data-tab="team">Team</button>
|
|
97
|
+
<button type="button" data-tab="needs">Needs you <span id="needsCount"></span></button>
|
|
109
98
|
</nav>
|
|
110
99
|
<section class="pane" id="chatPane">
|
|
111
|
-
<div class="channels" id="channels" role="tablist" aria-label="Channels"></div>
|
|
112
|
-
<div class="msgs" id="msgs"></div>
|
|
113
|
-
<div class="typing" id="typing" hidden><span></span><span></span><span></span> Chief is reading</div>
|
|
100
|
+
<div class="d-fold d-top"><div class="d-fold-in"><div class="channels" id="channels" role="tablist" aria-label="Channels"></div>
|
|
101
|
+
</div></div> <div class="msgs" id="msgs"></div>
|
|
102
|
+
<div class="d-fold d-foot-fold"><div class="d-fold-in"><div class="typing" id="typing" hidden><span></span><span></span><span></span> Chief is reading</div>
|
|
114
103
|
<form class="composer" id="composer">
|
|
115
104
|
<div class="ac" id="ac" hidden></div>
|
|
116
105
|
<div class="tray" id="tray" hidden></div>
|
|
@@ -155,20 +144,20 @@
|
|
|
155
144
|
</select>
|
|
156
145
|
<button class="primary small" type="submit">Send</button>
|
|
157
146
|
</div>
|
|
158
|
-
</form>
|
|
147
|
+
</form></div></div>
|
|
159
148
|
</section>
|
|
160
149
|
<section class="pane" id="threadPane" hidden>
|
|
161
|
-
<header class="thread-head">
|
|
150
|
+
<div class="d-fold d-top"><div class="d-fold-in"><header class="thread-head">
|
|
162
151
|
<button type="button" class="link" id="threadBack">← Chat</button>
|
|
163
152
|
<b id="threadWhere">Thread</b>
|
|
164
|
-
</header>
|
|
153
|
+
</header></div></div>
|
|
165
154
|
<div class="msgs" id="threadMsgs"></div>
|
|
166
|
-
<form class="composer" id="threadComposer">
|
|
155
|
+
<div class="d-fold d-foot-fold"><div class="d-fold-in"><form class="composer" id="threadComposer">
|
|
167
156
|
<textarea id="threadInput" rows="1" placeholder="Reply in thread" aria-label="Reply in thread"></textarea>
|
|
168
157
|
<div class="controls">
|
|
169
158
|
<button class="primary small" type="submit">Reply</button>
|
|
170
159
|
</div>
|
|
171
|
-
</form>
|
|
160
|
+
</form></div></div>
|
|
172
161
|
</section>
|
|
173
162
|
<section class="pane" id="teamPane" hidden>
|
|
174
163
|
<div class="viewbar" role="group" aria-label="View as">
|
|
@@ -184,6 +173,12 @@
|
|
|
184
173
|
</div>
|
|
185
174
|
<div class="projects" id="projects"></div>
|
|
186
175
|
</section>
|
|
176
|
+
<section class="pane" id="appsPane" hidden>
|
|
177
|
+
<div class="home" id="home" aria-label="Apps"></div>
|
|
178
|
+
</section>
|
|
179
|
+
<section class="pane" id="needsPane" hidden>
|
|
180
|
+
<div class="jobs needs" id="needs"></div>
|
|
181
|
+
</section>
|
|
187
182
|
<section class="pane" id="jobsPane" hidden>
|
|
188
183
|
<div class="jobs" id="jobs"></div>
|
|
189
184
|
<button class="ghost" id="clear" hidden>Clear finished</button>
|
|
@@ -195,7 +190,7 @@
|
|
|
195
190
|
<div id="labels"></div>
|
|
196
191
|
|
|
197
192
|
<div class="hud">
|
|
198
|
-
<button type="button" class="chip pot" id="shopBtn" aria-haspopup="dialog" aria-expanded="false" title="Shop and office pot" hidden><b>₦0</b><span>Shop</span></button>
|
|
193
|
+
<button type="button" class="chip pot" id="shopBtn" aria-haspopup="dialog" aria-expanded="false" aria-label="Shop and office pot" title="Shop and office pot" hidden><b>₦0</b><span>Shop</span></button>
|
|
199
194
|
<button type="button" class="chip pot shop-door" data-shop-tab="rich" aria-haspopup="dialog" aria-label="Forbes list: the richest agents" title="Forbes list: the richest agents" hidden><i aria-hidden="true">👑</i><span>Forbes list</span></button>
|
|
200
195
|
<button type="button" class="chip pot shop-door" data-shop-tab="looks" aria-haspopup="dialog" aria-label="Customise the office" title="Customise the office: layout, paint, floor and furniture" hidden><i aria-hidden="true">🎨</i><span>Customise</span></button>
|
|
201
196
|
<div class="chip away" id="away" hidden></div>
|
|
@@ -210,6 +205,9 @@
|
|
|
210
205
|
<button type="button" id="recenter" title="Recenter (R)" aria-label="Recenter">
|
|
211
206
|
<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><path d="M12 2v3M12 19v3M2 12h3M19 12h3"/></svg>
|
|
212
207
|
</button>
|
|
208
|
+
<button type="button" id="arrange" aria-pressed="false" title="Arrange the office: move desks, tables and furniture" aria-label="Arrange the office">
|
|
209
|
+
<svg viewBox="0 0 24 24"><path d="M12 2v20M2 12h20M12 2l-3 3M12 2l3 3M12 22l-3-3M12 22l3-3M2 12l3-3M2 12l3 3M22 12l-3-3M22 12l-3 3"/></svg>
|
|
210
|
+
</button>
|
|
213
211
|
<button type="button" id="standup" title="Call a standup" aria-label="Call a standup">
|
|
214
212
|
<svg viewBox="0 0 24 24"><circle cx="12" cy="5" r="2"/><circle cx="5" cy="15" r="2"/><circle cx="19" cy="15" r="2"/><path d="M8.5 20.5a7.5 7.5 0 0 0 7 0M3.5 10.5a8.5 8.5 0 0 1 4.5-5M20.5 10.5a8.5 8.5 0 0 0-4.5-5"/></svg>
|
|
215
213
|
</button>
|
|
@@ -227,20 +225,26 @@
|
|
|
227
225
|
<aside class="detail" id="detail" hidden></aside>
|
|
228
226
|
|
|
229
227
|
<nav class="mnav" id="mnav" aria-label="Sections">
|
|
230
|
-
<button type="button" data-m="office" class="on">
|
|
231
|
-
<svg viewBox="0 0 24 24"><path d="M3 10.5L12 4l9 6.5"/><path d="M5 9.5V20h14V9.5"/><path d="M10 20v-5h4v5"/></svg>Office
|
|
228
|
+
<button type="button" data-m="office" title="Office" aria-label="Office" class="on">
|
|
229
|
+
<svg viewBox="0 0 24 24"><path d="M3 10.5L12 4l9 6.5"/><path d="M5 9.5V20h14V9.5"/><path d="M10 20v-5h4v5"/></svg><span class="lbl">Office</span>
|
|
230
|
+
</button>
|
|
231
|
+
<button type="button" data-m="chat" title="Chat" aria-label="Chat">
|
|
232
|
+
<svg viewBox="0 0 24 24"><path d="M4 5h16v11H9l-5 4z"/></svg><span class="lbl">Chat</span><i class="dot" id="mChatDot" hidden></i>
|
|
233
|
+
</button>
|
|
234
|
+
<button type="button" data-m="jobs" title="Jobs" aria-label="Jobs">
|
|
235
|
+
<svg viewBox="0 0 24 24"><path d="M4 7h16M4 12h16M4 17h10"/></svg><span class="lbl">Jobs</span><b id="mJobCount"></b>
|
|
232
236
|
</button>
|
|
233
|
-
<button type="button" data-m="
|
|
234
|
-
<svg viewBox="0 0 24 24"><path d="
|
|
237
|
+
<button type="button" data-m="projects" title="Projects" aria-label="Projects">
|
|
238
|
+
<svg viewBox="0 0 24 24"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg><span class="lbl">Projects</span>
|
|
235
239
|
</button>
|
|
236
|
-
<button type="button" data-m="
|
|
237
|
-
<svg viewBox="0 0 24 24"><
|
|
240
|
+
<button type="button" data-m="apps" title="Apps" aria-label="Apps">
|
|
241
|
+
<svg viewBox="0 0 24 24"><rect x="4" y="4" width="6" height="6" rx="1.5"/><rect x="14" y="4" width="6" height="6" rx="1.5"/><rect x="4" y="14" width="6" height="6" rx="1.5"/><rect x="14" y="14" width="6" height="6" rx="1.5"/></svg><span class="lbl">Apps</span>
|
|
238
242
|
</button>
|
|
239
|
-
<button type="button" data-m="
|
|
240
|
-
<svg viewBox="0 0 24 24"><path d="M3
|
|
243
|
+
<button type="button" data-m="team" title="Team" aria-label="Team">
|
|
244
|
+
<svg viewBox="0 0 24 24"><circle cx="9" cy="8" r="3.2"/><path d="M3 20a6 6 0 0 1 12 0"/><circle cx="17" cy="9" r="2.4"/><path d="M17 14.5a4.6 4.6 0 0 1 4 5.5"/></svg><span class="lbl">Team</span>
|
|
241
245
|
</button>
|
|
242
|
-
<button type="button" data-m="
|
|
243
|
-
<svg viewBox="0 0 24 24"><
|
|
246
|
+
<button type="button" data-m="needs" title="Needs you" aria-label="Needs you">
|
|
247
|
+
<svg viewBox="0 0 24 24"><path d="M6 17V11a6 6 0 0 1 12 0v6l1.5 2h-15z"/><path d="M10 21h4"/></svg><span class="lbl">Needs you</span><b id="mNeedsCount"></b>
|
|
244
248
|
</button>
|
|
245
249
|
</nav>
|
|
246
250
|
</div>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Before first paint, so a saved theme never flashes the default. theme.js owns it after that.
|
|
2
|
+
// A file rather than inline, so the page's CSP can be script-src 'self'.
|
|
3
|
+
(function () {
|
|
4
|
+
try {
|
|
5
|
+
var t = localStorage.getItem('office.theme') || 'paper';
|
|
6
|
+
var m = localStorage.getItem('office.mode') || 'auto';
|
|
7
|
+
var d = m === 'dark' || (m === 'auto' && matchMedia('(prefers-color-scheme: dark)').matches);
|
|
8
|
+
var r = document.documentElement;
|
|
9
|
+
r.dataset.theme = t;
|
|
10
|
+
r.dataset.mode = d ? 'dark' : 'light';
|
|
11
|
+
r.dataset.nav = localStorage.getItem('office.nav') === 'sidebar' ? 'sidebar' : 'dock';
|
|
12
|
+
} catch (e) {}
|
|
13
|
+
})();
|
package/docs/desktop.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Desktop app (Tauri)
|
|
2
|
+
|
|
3
|
+
The desktop app is a thin Tauri shell around the same office server and web UI you get from `agentoffice`. The shell lives in `desktop/`; this page covers how the web side behaves inside it.
|
|
4
|
+
|
|
5
|
+
## How the shell starts the office
|
|
6
|
+
|
|
7
|
+
1. Binds `127.0.0.1:0` to get a free port `N`.
|
|
8
|
+
2. Runs `$SHELL -lc 'exec node <Resources>/app/bin/agentoffice.js --port N'`. The login shell means a Finder-launched app still finds `node` and `claude` on `PATH`.
|
|
9
|
+
3. Sets `OFFICE_ROOT` (the folder you pick on first run, remembered), leaves `OFFICE_DATA` at `~/.agent-office`, and sets `OFFICE_DESKTOP=1`.
|
|
10
|
+
4. Waits until `N` accepts connections, reads `~/.agent-office/office-key`, and points the window at `http://127.0.0.1:N/?key=…`.
|
|
11
|
+
5. On quit, sends `SIGTERM` to the server and waits, so it flushes its state.
|
|
12
|
+
|
|
13
|
+
The server needs no desktop-specific behaviour: the phone QR code only prints with `--phone`, which the shell never passes.
|
|
14
|
+
|
|
15
|
+
## External links
|
|
16
|
+
|
|
17
|
+
The webview drops `target="_blank"` links and `window.open`. `web/src/desktop.js` detects `window.__TAURI__` and sends those URLs to the opener plugin (`window.__TAURI__.opener.openUrl`). It handles:
|
|
18
|
+
|
|
19
|
+
- links to other sites (`https:`, `http:`, `mailto:`),
|
|
20
|
+
- `window.open(url)`.
|
|
21
|
+
|
|
22
|
+
It also adds a `desktop` class to `<html>` so CSS can adapt. In a normal browser the module does nothing.
|
|
23
|
+
|
|
24
|
+
Links to the office itself are left alone: the system browser doesn't have the webview's HttpOnly key cookie and would get a 401, and the key is stripped from the page URL so it can't be appended.
|
|
25
|
+
|
|
26
|
+
The shell must enable the opener plugin with a capability that allows the localhost page to call it.
|
|
27
|
+
|
|
28
|
+
## Building it
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
cd desktop && npm install
|
|
32
|
+
npm run dev # runs the repo; needs a built dist/
|
|
33
|
+
npm run build # stages from HEAD, writes src-tauri/target/release/bundle/{macos,dmg}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Rust comes via rustup. The build log goes to `~/.agent-office/desktop.log`.
|
|
37
|
+
|
|
38
|
+
## Opening a downloaded build
|
|
39
|
+
|
|
40
|
+
The dmg is signed ad hoc (`signingIdentity: "-"` in `tauri.conf.json`) and is **not notarized**: that needs an Apple *Developer ID Application* certificate and an Apple notarization login, which the project doesn't have. So on another Mac, Gatekeeper blocks the first launch of a downloaded copy.
|
|
41
|
+
|
|
42
|
+
Before the ad-hoc signing was added, the bundle carried only the linker's signature on the executable, with no sealed resources. Gatekeeper treats that as a broken signature and says "Agent Office is damaged and can't be opened". Now the signature is valid, and the message is the milder "can't be opened because Apple cannot check it for malicious software".
|
|
43
|
+
|
|
44
|
+
For users, any one of:
|
|
45
|
+
|
|
46
|
+
- Terminal: `xattr -cr "/Applications/Agent Office.app"`, then open it (drag it to Applications first). This strips the download quarantine flag.
|
|
47
|
+
- Right-click the app, choose Open, then Open again.
|
|
48
|
+
- macOS 15 and later: try to open it once, then System Settings, Privacy & Security, Open Anyway.
|
|
49
|
+
|
|
50
|
+
To remove the warning properly: enrol in the Apple Developer Program, create a Developer ID Application certificate, then build with `APPLE_SIGNING_IDENTITY="Developer ID Application: …"` and either `APPLE_ID`, `APPLE_PASSWORD` (app-specific) and `APPLE_TEAM_ID`, or `APPLE_API_KEY`, `APPLE_API_ISSUER` and `APPLE_API_KEY_PATH`. Tauri then signs, notarizes and staples the dmg. Those variables take precedence over the ad-hoc identity.
|
|
51
|
+
|
|
52
|
+
The dmg is arm64 only (Apple Silicon).
|
package/docs/features.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Agent Office: every feature
|
|
2
|
+
|
|
3
|
+
Compiled 2026-10-18 from the code, the docs and the changelog (as of 0.9.2).
|
|
4
|
+
|
|
5
|
+
## 1. The office itself (3D)
|
|
6
|
+
- A 3D office (three.js) with desks, library, server room, research window, whiteboard, break room, pool table, reception, a guest wing.
|
|
7
|
+
- Agents walk to where their work happens: Read/Grep/Glob → library, Bash → server room, web/MCP → research window, plans/todos → whiteboard, edits → their desk.
|
|
8
|
+
- Camera: drag, right-drag or Q/E to turn, scroll to zoom, double-click to glide, WASD pan, R recenter; nearest walls drop so you can see in.
|
|
9
|
+
- Themes (Paper, Ocean, Forest, Sunset, Lavender), light/dark/auto.
|
|
10
|
+
- Lite rendering on phones (override with `?lite=0/1`).
|
|
11
|
+
- Build mode: pick up desks, the table, pool table or bought items, move on a snapped grid, saved per layout. Free layouts to switch between.
|
|
12
|
+
- Windows: Chat, Jobs, Projects, Team in draggable, resizable windows on wide screens (remembered); sidebar on phones. Dock mode with draggable job card.
|
|
13
|
+
- Right-click menu on the floor: tidy windows, open/close, recenter, appearance, settings.
|
|
14
|
+
- Header/footer fold away while scrolling lists.
|
|
15
|
+
- Header shows owner, level ring, usage gauges.
|
|
16
|
+
|
|
17
|
+
## 2. Chat and the chief of staff
|
|
18
|
+
- Chat with `@agent` and `#project` tagging. Untagged messages go to the chief of staff.
|
|
19
|
+
- The chief: picks project and agent, hands follow-ups live to a running agent, resumes finished sessions, asks when unsure, can hand out several jobs from one message (one per person), reads screenshots.
|
|
20
|
+
- Chief picks the model (Haiku/Sonnet/Opus) to fit the work; moves a failed job up a rung.
|
|
21
|
+
- Attachments: paperclip, paste, drag-drop. Voice notes.
|
|
22
|
+
- Channels (Slack style): `#general`, `#chill-room`, your own; each message belongs to one channel; unread counts.
|
|
23
|
+
- Threads: job updates collect under the message that started the job.
|
|
24
|
+
- Slash commands: `/standup`, `/restart`, `/telegram`, "clear context".
|
|
25
|
+
- "Needs you" strip and pane: jobs waiting on you; dismiss one or all; answering clears it.
|
|
26
|
+
- Avatars drawn from each agent's look.
|
|
27
|
+
|
|
28
|
+
## 3. Jobs
|
|
29
|
+
- Each job is a real `claude -p` process in a project folder, stdin open so messages reach it mid-task.
|
|
30
|
+
- Clearance per job: Plan only / Edit files / Full autonomy; grant more when blocked; approve a finished plan to build it.
|
|
31
|
+
- Model picker per job, switchable mid-job (resumes the same session).
|
|
32
|
+
- Auto-escalation on failure (Haiku → Sonnet → Opus) with a fresh session and summary.
|
|
33
|
+
- "What changed": git-tree snapshots before/after, files, lines +/-, per-file patch, Edit button per file; "Files edited" where git can't say.
|
|
34
|
+
- Job drawer: log, model, context size, cost and time (folded under Details).
|
|
35
|
+
- Context management: agents' `clear_context` with notes to self; auto-nudge past 150k tokens; the boss can say "clear context"; notes survive clears.
|
|
36
|
+
- Jobs survive server restarts (sessions resume).
|
|
37
|
+
- Machine care: limit on agents working at once (default 4), hold when memory/CPU struggles ("On hold" notice), turn-taking for heavy builds (`heavy_work`), leftover processes killed at job end, `keep_running` to keep one.
|
|
38
|
+
- Quality badges and XP; levels hire more agents (3 at level 1 up to 8).
|
|
39
|
+
|
|
40
|
+
## 4. Agents and the team
|
|
41
|
+
- Roster of named agents with roles, personalities, looks and voices; rename them (one word); Team tab with list/icons toggle; "Meet your team" onboarding.
|
|
42
|
+
- Agent office tools (MCP): `office_roster`, `message_colleague` (optionally wait for reply), `hand_off`, `flag_for_boss`, `attach_files` (6 files, 25 MB each), `restart_office`, `clear_context`, `heavy_work`, `keep_running`, `add_app`, `office_shop`, `buy_item`, `bets`, `place_bet`, `take_bet`, `cancel_bet`, channel tools (`list_channels`, `create_channel`, `invite_to_channel`, `post_to_channel`).
|
|
43
|
+
- Hand-offs only to free colleagues, max depth two; shown as "Ada → Tunde".
|
|
44
|
+
- Daily standup at 9:30 on weekdays (or on demand): done / today / blockers, chief's wrap-up.
|
|
45
|
+
- Break room: small-talk written by a small model, eight-ball pool played shot by shot; results to `#chill-room`; can be turned off.
|
|
46
|
+
- Economy: agents are paid for finished work (more on lighter models), a shop for desk items, and bets between colleagues on jobs (stakes, take, cancel, voided if a party hands the job over).
|
|
47
|
+
|
|
48
|
+
## 5. Projects and files
|
|
49
|
+
- Projects are the subfolders of the root folder; Projects tab (list/icons).
|
|
50
|
+
- Edit files in the office: folder tree and CodeMirror editor (syntax highlighting, line numbers, folding, bracket matching, find, undo, themed); Cmd/Ctrl+S saves; refuses to save if the file changed on disk.
|
|
51
|
+
- PDF viewer (pdf.js), served only if truly a PDF.
|
|
52
|
+
- Image/file attachments from agents shown in chat.
|
|
53
|
+
|
|
54
|
+
## 6. Apps on the home screen
|
|
55
|
+
- Agents build self-contained HTML apps and register them with `add_app`; each gets an icon and opens in a sandboxed window (`data/apps.json`, `/api/apps`).
|
|
56
|
+
|
|
57
|
+
## 7. Telegram
|
|
58
|
+
- Talk to the office from your phone through your own bot (made with @BotFather). The office long-polls, so no tunnel or webhook is needed.
|
|
59
|
+
- Setup: `/telegram <token>` in the office, then `/telegram pair` for a one-time code (valid 15 minutes, 5 tries), then `/pair 123456` to the bot. `/telegram off` forgets the bot; `/telegram` shows status.
|
|
60
|
+
- Only the paired account works; everyone else and any group chat is dropped unread. The token is kept in `data/telegram.json` (0600), not in state or chat.
|
|
61
|
+
- Messages go to the office as chat; replies, finished jobs and flags come back. Reply to a message to answer in its thread.
|
|
62
|
+
- `/grant` (reply to a blocked job) gives full autonomy; `/grant all` for every blocked job; `/auto <request>` starts a job at full autonomy.
|
|
63
|
+
- Photos, videos, voice notes, audio and documents come in (up to 20 MB); agent attachments go out as photos, video, audio or files (up to 50 MB).
|
|
64
|
+
- Long messages are clipped with a pointer to the office; sending retries on rate limits and keeps order. Break-room chatter is never sent.
|
|
65
|
+
|
|
66
|
+
## 8. Talking and hearing
|
|
67
|
+
- Dictation with whisper.cpp and a different Piper voice per agent, all local; mute and chime controls; desktop notifications when a job ships.
|
|
68
|
+
|
|
69
|
+
## 9. Linked offices
|
|
70
|
+
- Pair two offices with a one-time code; agents appear as visitors in a guest wing (12 hot desks, coffee bar, meeting table, up to 10 visitors).
|
|
71
|
+
- Invite guests to use your agents (pick which agents, projects, clearance, daily token limit), or let their agents work in your projects (read only, or edit a copy you review and apply or discard).
|
|
72
|
+
- Tag a host's agents from your chat (`@Ada@host`) and send yours to their project (`#repo@host`). Job titles shared only if you allow it.
|
|
73
|
+
|
|
74
|
+
## 10. Leaderboard
|
|
75
|
+
- Optional board of jobs, tokens and agent time; a handle is separate from sharing; nothing shared until you join.
|
|
76
|
+
|
|
77
|
+
## 11. Claude access and usage
|
|
78
|
+
- Claude Code login or API key; plan usage meters; token limit and compaction point; context limit settable in the office.
|
|
79
|
+
|
|
80
|
+
## 12. Settings
|
|
81
|
+
- Settings page: owner, token limit, compaction point, chief model, standup time and weekends, job-title sharing, break-room talk, agents working at once. Saved values beat environment variables; "Use the default" reverts.
|
|
82
|
+
- Environment options include `OFFICE_ROOT`, `OFFICE_DATA`, `PORT`, `CLAUDE_BIN`, `STANDUP_AT`, `BANTER*`, `WHISPER_*`, `PIPER_BIN`, `FFMPEG_BIN`, `OFFICE_LEADERBOARD_URL`, `OFFICE_MAX_AGENTS`.
|
|
83
|
+
|
|
84
|
+
## 13. Phone, desktop and install
|
|
85
|
+
- `agentoffice --phone` listens on the LAN and prints a QR code; or "Make a public address" starts a Cloudflare quick tunnel with a QR code (off until pressed; the key stays the only way in).
|
|
86
|
+
- Installable as a PWA.
|
|
87
|
+
- Desktop app (Tauri, macOS) with a designed DMG; starts the office on a free port.
|
|
88
|
+
- `npm install -g agent-office-studio`, then `agentoffice` or `agentstudio`; picks a free port; `--link` reprints the key link.
|
|
89
|
+
|
|
90
|
+
## 14. Security
|
|
91
|
+
- Listens on localhost only by default; page, API and socket need a key (`data/office-key`, 0600), the key cookie holds a MAC, and a CSP limits images and forms.
|
|
92
|
+
- Only the office's own page may call the API; malformed URLs don't crash it.
|
|
93
|
+
- Guests' jobs don't get your MCP servers, settings, hooks or memory; their tokens are counted per call; unlinked guests' jobs are stopped.
|
|
94
|
+
- Git runs with fsmonitor and external diff drivers off; snapshots never run clean/smudge filters.
|
|
95
|
+
- Below Full autonomy, `attach_files` takes only files from the job's own project; job tokens stay out of the projects folder.
|
|
96
|
+
- Dependencies pinned and patched (ws, Vite/esbuild); CI plus npm publish on `v*` tags.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-office-studio",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.2",
|
|
4
4
|
"description": "A 3D office where the staff are Claude Code agents.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"files": [
|
|
@@ -42,10 +42,11 @@
|
|
|
42
42
|
"scripts": {
|
|
43
43
|
"dev": "concurrently -k -n server,web -c blue,magenta \"node --watch server/index.js\" \"vite\"",
|
|
44
44
|
"build": "vite build",
|
|
45
|
+
"watch": "vite build --watch",
|
|
45
46
|
"start": "vite build && node server/supervise.js",
|
|
46
47
|
"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",
|
|
47
48
|
"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",
|
|
48
|
-
"test": "node scripts/economy.mjs && node scripts/wagers.mjs && node scripts/smoke.mjs && node scripts/talk.mjs && node scripts/guests.mjs && node scripts/remote.mjs && node scripts/lent-home.mjs && node scripts/visits.mjs && node scripts/phone.mjs && node scripts/wagers-server.mjs",
|
|
49
|
+
"test": "node scripts/economy.mjs && node scripts/wagers.mjs && node scripts/smoke.mjs && node scripts/machine.mjs && node scripts/talk.mjs && node scripts/guests.mjs && node scripts/remote.mjs && node scripts/lent-home.mjs && node scripts/visits.mjs && node scripts/phone.mjs && node scripts/desktop-gate.mjs && node scripts/hardening-gate.mjs && node scripts/guest-gate.mjs && node scripts/wagers-server.mjs && node scripts/chief-gate.mjs && node scripts/files.mjs && node scripts/apps.mjs && node scripts/telegram.mjs",
|
|
49
50
|
"prepack": "vite build"
|
|
50
51
|
},
|
|
51
52
|
"dependencies": {
|
|
@@ -54,10 +55,26 @@
|
|
|
54
55
|
"ws": "8.22.0"
|
|
55
56
|
},
|
|
56
57
|
"devDependencies": {
|
|
58
|
+
"@codemirror/autocomplete": "6.20.3",
|
|
59
|
+
"@codemirror/commands": "6.11.1",
|
|
60
|
+
"@codemirror/lang-css": "6.3.1",
|
|
61
|
+
"@codemirror/lang-html": "6.4.12",
|
|
62
|
+
"@codemirror/lang-javascript": "6.2.5",
|
|
63
|
+
"@codemirror/lang-json": "6.0.2",
|
|
64
|
+
"@codemirror/lang-markdown": "6.5.2",
|
|
65
|
+
"@codemirror/lang-python": "6.2.1",
|
|
66
|
+
"@codemirror/lang-yaml": "6.1.3",
|
|
67
|
+
"@codemirror/language": "6.13.1",
|
|
68
|
+
"@codemirror/search": "6.7.2",
|
|
69
|
+
"@codemirror/state": "6.7.6",
|
|
70
|
+
"@codemirror/view": "6.43.14",
|
|
57
71
|
"@fontsource-variable/inter": "5.3.0",
|
|
58
72
|
"@fontsource/jetbrains-mono": "5.3.0",
|
|
73
|
+
"@lezer/highlight": "1.2.5",
|
|
74
|
+
"codemirror": "6.0.2",
|
|
59
75
|
"concurrently": "10.0.6",
|
|
60
76
|
"marked": "^18.1.0",
|
|
77
|
+
"pdfjs-dist": "6.4.299",
|
|
61
78
|
"three": "0.186.1",
|
|
62
79
|
"vite": "6.4.3"
|
|
63
80
|
}
|
package/server/apps.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// The office's apps: what the home screen shows. Each app is one manifest
|
|
2
|
+
// entry in data/apps.json. The boss asks for an app, an agent builds it and
|
|
3
|
+
// registers it with the add_app tool (or POST /api/apps), and it gets an icon.
|
|
4
|
+
//
|
|
5
|
+
// `kind` says how an app opens. KINDS checks what each kind needs; the web
|
|
6
|
+
// side (web/src/apps.js) has the matching opener. To add a kind, add it in
|
|
7
|
+
// both places.
|
|
8
|
+
//
|
|
9
|
+
// A manifest entry:
|
|
10
|
+
// id slug, unique; made from the name if not given
|
|
11
|
+
// name what the icon says
|
|
12
|
+
// icon an emoji (or a few characters)
|
|
13
|
+
// color the icon's tile, #rgb or #rrggbb (optional)
|
|
14
|
+
// description one line, shown on hover (optional)
|
|
15
|
+
// kind 'page': a self-contained HTML file in a project, run sandboxed
|
|
16
|
+
// repo, path for 'page': the project folder and the file inside it
|
|
17
|
+
// w, h the window's starting size (optional)
|
|
18
|
+
// addedBy, addedAt
|
|
19
|
+
|
|
20
|
+
import fs from 'node:fs';
|
|
21
|
+
import path from 'node:path';
|
|
22
|
+
import * as files from './files.js';
|
|
23
|
+
|
|
24
|
+
const fail = (code, msg) => Object.assign(new Error(msg), { code });
|
|
25
|
+
const MAX_APPS = 200;
|
|
26
|
+
const DATA_MAX = 1e6;
|
|
27
|
+
const ID_RE = /^[a-z0-9][a-z0-9-]{0,39}$/;
|
|
28
|
+
|
|
29
|
+
export const slug = (s) => String(s || '').toLowerCase().normalize('NFKD').replace(/[^\w\s-]/g, '').trim().replace(/[\s_-]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 40);
|
|
30
|
+
|
|
31
|
+
const text = (v, label, max, need = false) => {
|
|
32
|
+
if (v == null || v === '') { if (need) throw fail(400, `An app needs a ${label}.`); return ''; }
|
|
33
|
+
if (typeof v !== 'string') throw fail(400, `The ${label} must be text.`);
|
|
34
|
+
v = v.trim().replace(/\s+/g, ' ');
|
|
35
|
+
if (need && !v) throw fail(400, `An app needs a ${label}.`);
|
|
36
|
+
if (v.length > max) throw fail(400, `The ${label} is limited to ${max} characters.`);
|
|
37
|
+
return v;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// What each kind needs, checked when an app is added. `repoPath` turns a
|
|
41
|
+
// project name into its folder, or null.
|
|
42
|
+
export const KINDS = {
|
|
43
|
+
page: {
|
|
44
|
+
clean(a, { repoPath }) {
|
|
45
|
+
const repo = text(a.repo, 'project folder', 200, true);
|
|
46
|
+
const dir = repoPath(repo);
|
|
47
|
+
if (!dir) throw fail(400, `There is no project folder called "${repo}".`);
|
|
48
|
+
const rel = text(a.path, 'file path', 500, true).replace(/^\/+/, '');
|
|
49
|
+
files.page(dir, rel); // throws if it is missing, not HTML or too big
|
|
50
|
+
return { repo, path: rel };
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export class Apps {
|
|
56
|
+
constructor(dataDir, { repoPath, onChange = () => {} }) {
|
|
57
|
+
this.file = path.join(dataDir, 'apps.json');
|
|
58
|
+
this.repoPath = repoPath;
|
|
59
|
+
this.onChange = onChange;
|
|
60
|
+
this.apps = [];
|
|
61
|
+
try {
|
|
62
|
+
const saved = JSON.parse(fs.readFileSync(this.file, 'utf8'));
|
|
63
|
+
if (Array.isArray(saved.apps)) this.apps = saved.apps.filter((a) => a && ID_RE.test(a.id) && KINDS[a.kind]);
|
|
64
|
+
} catch {}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
list() { return this.apps.map((a) => ({ ...a })); }
|
|
68
|
+
|
|
69
|
+
get(id) { return this.apps.find((a) => a.id === id) || null; }
|
|
70
|
+
|
|
71
|
+
// Adds an app, or replaces the one with the same id (so re-registering an
|
|
72
|
+
// app after a rebuild updates it). Returns the stored entry.
|
|
73
|
+
add(input, by = 'boss') {
|
|
74
|
+
if (!input || typeof input !== 'object') throw fail(400, 'Nothing to add.');
|
|
75
|
+
const name = text(input.name, 'name', 40, true);
|
|
76
|
+
const kind = text(input.kind || 'page', 'kind', 20);
|
|
77
|
+
if (!KINDS[kind]) throw fail(400, `Unknown kind of app "${kind}". Known: ${Object.keys(KINDS).join(', ')}.`);
|
|
78
|
+
const id = input.id ? text(input.id, 'id', 40) : slug(name);
|
|
79
|
+
if (!ID_RE.test(id)) throw fail(400, 'The id must be lowercase letters, numbers and dashes.');
|
|
80
|
+
const icon = text(input.icon, 'icon', 8) || name.slice(0, 1).toUpperCase();
|
|
81
|
+
const color = text(input.color, 'colour', 7);
|
|
82
|
+
if (color && !/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(color)) throw fail(400, 'The colour must be a hex colour like #1d4ed8.');
|
|
83
|
+
for (const v of [input.w, input.h]) if (v != null && !(Number.isInteger(v) && v >= 300 && v <= 4000)) throw fail(400, 'Window sizes are whole pixels from 300 to 4000.');
|
|
84
|
+
const description = text(input.description, 'description', 200);
|
|
85
|
+
const was = this.get(id);
|
|
86
|
+
if (!was && this.apps.length >= MAX_APPS) throw fail(400, `The home screen holds ${MAX_APPS} apps; remove one first.`);
|
|
87
|
+
const app = {
|
|
88
|
+
id, name, icon, kind,
|
|
89
|
+
...(color && { color }),
|
|
90
|
+
...(description && { description }),
|
|
91
|
+
...KINDS[kind].clean(input, { repoPath: this.repoPath }),
|
|
92
|
+
...(input.w && { w: input.w }), ...(input.h && { h: input.h }),
|
|
93
|
+
addedBy: was?.addedBy || by, addedAt: was?.addedAt || Date.now(),
|
|
94
|
+
...(was && { updatedAt: Date.now() }),
|
|
95
|
+
};
|
|
96
|
+
this.apps = was ? this.apps.map((a) => (a.id === id ? app : a)) : [...this.apps, app];
|
|
97
|
+
this.save();
|
|
98
|
+
return app;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
remove(id) {
|
|
102
|
+
if (!this.get(id)) throw fail(404, 'No app with that id.');
|
|
103
|
+
this.apps = this.apps.filter((a) => a.id !== id);
|
|
104
|
+
this.save();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// The order the icons sit in. Ids not given keep their place at the end.
|
|
108
|
+
reorder(ids) {
|
|
109
|
+
if (!Array.isArray(ids)) throw fail(400, 'Give the app ids in order.');
|
|
110
|
+
const rank = new Map(ids.map((id, i) => [id, i]));
|
|
111
|
+
this.apps = [...this.apps].sort((a, b) => (rank.get(a.id) ?? 1e9) - (rank.get(b.id) ?? 1e9));
|
|
112
|
+
this.save();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// What an app keeps for itself (its page has no storage of its own): one
|
|
116
|
+
// JSON value per app in app-data/<id>.json, so every window onto this
|
|
117
|
+
// office (a browser tab, the desktop app on whatever port) sees the same.
|
|
118
|
+
dataFile(id) {
|
|
119
|
+
if (!ID_RE.test(id || '')) throw fail(400, 'No app with that id.');
|
|
120
|
+
return path.join(path.dirname(this.file), 'app-data', `${id}.json`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
readData(id) {
|
|
124
|
+
try { return JSON.parse(fs.readFileSync(this.dataFile(id), 'utf8')); } catch (err) {
|
|
125
|
+
if (err.code === 400) throw err;
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
writeData(id, value) {
|
|
131
|
+
const file = this.dataFile(id);
|
|
132
|
+
const text = JSON.stringify(value ?? null);
|
|
133
|
+
if (text.length > DATA_MAX) throw fail(413, 'That is more than an app can keep (1 MB).');
|
|
134
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
135
|
+
fs.writeFileSync(file + '.tmp', text);
|
|
136
|
+
fs.renameSync(file + '.tmp', file);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
save() {
|
|
140
|
+
fs.mkdirSync(path.dirname(this.file), { recursive: true });
|
|
141
|
+
const tmp = this.file + '.tmp';
|
|
142
|
+
fs.writeFileSync(tmp, JSON.stringify({ apps: this.apps }, null, 2));
|
|
143
|
+
fs.renameSync(tmp, this.file);
|
|
144
|
+
this.onChange(this.list());
|
|
145
|
+
}
|
|
146
|
+
}
|
package/server/changes.js
CHANGED
|
@@ -18,6 +18,16 @@ const git = (cwd, args, env = {}) => new Promise((resolve, reject) => {
|
|
|
18
18
|
});
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
+
// Clean and smudge filters are commands a project's config names, and
|
|
22
|
+
// `add -A`, `write-tree` or a checkout would run them. These blank every one
|
|
23
|
+
// configured, for one call: git then passes the content through as it is.
|
|
24
|
+
export async function noFilters(cwd) {
|
|
25
|
+
let out = '';
|
|
26
|
+
try { out = await git(cwd, ['config', '--name-only', '--get-regexp', '^filter\\.']); } catch { return []; }
|
|
27
|
+
const names = new Set(out.split('\n').map((k) => k.match(/^filter\.(.+)\.[^.]+$/)?.[1]).filter(Boolean));
|
|
28
|
+
return [...names].flatMap((n) => [...['clean', 'smudge', 'process'].map((k) => `filter.${n}.${k}=`), `filter.${n}.required=false`].flatMap((c) => ['-c', c]));
|
|
29
|
+
}
|
|
30
|
+
|
|
21
31
|
// A tree sha for the working tree as it is right now, or null outside a repo.
|
|
22
32
|
export async function snapshotTree(cwd) {
|
|
23
33
|
const tmp = path.join(os.tmpdir(), `office-index-${process.pid}-${Math.random().toString(36).slice(2)}`);
|
|
@@ -26,8 +36,9 @@ export async function snapshotTree(cwd) {
|
|
|
26
36
|
// Seeding from the real index keeps `add -A` quick on a big tree.
|
|
27
37
|
try { fs.copyFileSync(real, tmp); } catch {}
|
|
28
38
|
const env = { GIT_INDEX_FILE: tmp };
|
|
29
|
-
await
|
|
30
|
-
|
|
39
|
+
const nf = await noFilters(cwd);
|
|
40
|
+
await git(cwd, [...nf, 'add', '-A'], env);
|
|
41
|
+
return (await git(cwd, [...nf, 'write-tree'], env)).trim();
|
|
31
42
|
} catch {
|
|
32
43
|
return null;
|
|
33
44
|
} finally {
|
package/server/chief.js
CHANGED
|
@@ -24,7 +24,7 @@ export function canSee(files) {
|
|
|
24
24
|
const SCHEMA = {
|
|
25
25
|
type: 'object',
|
|
26
26
|
properties: {
|
|
27
|
-
action: { type: 'string', enum: ['followup', 'new', 'dismiss', 'recall', 'clear', 'standup', 'ask', 'chat'] },
|
|
27
|
+
action: { type: 'string', enum: ['followup', 'new', 'dismiss', 'recall', 'clear', 'standup', 'restart', 'takeover', 'ask', 'chat'] },
|
|
28
28
|
jobId: { type: 'string' },
|
|
29
29
|
agentId: { type: 'string' },
|
|
30
30
|
agentIds: { type: 'array', items: { type: 'string' } },
|
|
@@ -32,6 +32,22 @@ const SCHEMA = {
|
|
|
32
32
|
brief: { type: 'string' },
|
|
33
33
|
model: { type: 'string', enum: ['haiku', 'sonnet', 'opus'] },
|
|
34
34
|
escalate: { type: 'boolean' },
|
|
35
|
+
// More than one piece of new work from a single message, one per job.
|
|
36
|
+
jobs: {
|
|
37
|
+
type: 'array',
|
|
38
|
+
items: {
|
|
39
|
+
type: 'object',
|
|
40
|
+
properties: {
|
|
41
|
+
agentId: { type: 'string' },
|
|
42
|
+
repo: { type: 'string' },
|
|
43
|
+
brief: { type: 'string' },
|
|
44
|
+
model: { type: 'string', enum: ['haiku', 'sonnet', 'opus'] },
|
|
45
|
+
say: { type: 'string' },
|
|
46
|
+
},
|
|
47
|
+
required: ['repo', 'brief'],
|
|
48
|
+
additionalProperties: false,
|
|
49
|
+
},
|
|
50
|
+
},
|
|
35
51
|
say: { type: 'string' },
|
|
36
52
|
},
|
|
37
53
|
required: ['action', 'say'],
|
|
@@ -43,9 +59,12 @@ const SYSTEM = `You are the chief of staff in an office of coding agents. The bo
|
|
|
43
59
|
Actions:
|
|
44
60
|
- "followup": the message adds to, corrects, or asks about a job that already exists (working, queued or recently finished). Give its jobId. Prefer this whenever the message plausibly relates to an open job, especially the job of an agent the boss tagged. Set "escalate": true only when the boss is saying a finished job's work is wrong, incomplete, or that the agent missed the point — the second go then runs on a stronger model.
|
|
45
61
|
- "new": a new piece of work. Give "repo" (exactly one name from Projects), "brief" (the task in the boss's own words, plus any context from recent chat that the agent needs; never add requirements), "agentId" (the tagged agent's id; otherwise a free agent whose role in Staff clearly fits the work; otherwise "" for whoever is free), and "model" (see Sizing the work). Never pick a busy or dismissed agent unless the boss tagged them.
|
|
62
|
+
You can hand out several jobs from one message. When the boss tags more than one agent for work, or asks for separate pieces of work, give "jobs" instead: one entry per job, each with its own "repo", "brief", "agentId", "model" and a short "say" line for that job. Every tagged agent gets a job: the same brief for each when the boss wants them all on it (e.g. two agents doing a review pass), or a part each when the work splits naturally. Never tell the boss you can only hand out one job at a time.
|
|
46
63
|
- "dismiss": the boss wants agents sent home (dismissed, let go for now, off the floor). Give "agentIds": the ids of exactly who they mean, worked out from Staff (e.g. "everyone who's free except Mira" means every free agent but Mira). Only free agents can go; leave out anyone working.
|
|
47
64
|
- "recall": the boss wants dismissed agents back in. Give "agentIds" (every dismissed agent if they say "everyone").
|
|
48
65
|
- "clear": the boss wants agents to clear their context (start fresh, forget the conversation so far). Do it yourself; never hand it out as a job. Give "agentIds" for the agents they name, or leave it empty when they mean everyone.
|
|
66
|
+
- "restart": the boss says an agent is stuck, frozen or not responding and wants it nudged, unstuck or restarted. Give the jobId of that agent's working job. Do it yourself: a stuck session can't read messages, so never send it a followup.
|
|
67
|
+
- "takeover": the boss wants someone else to help with, take over or finish a working agent's job (often because it is stuck). Give its jobId, and "agentId" if they named who should take it (otherwise "" for whoever is free). Never pick the agent who was on it.
|
|
49
68
|
- "standup": the boss wants the team to hold a standup now (e.g. "standup time", "let's do standup", "gather everyone for a status update").
|
|
50
69
|
- "ask": you can't tell which project new work belongs to, or the request is too unclear to start. Ask one short question.
|
|
51
70
|
- "chat": greetings, thanks, or questions about the office itself (who is doing what). Answer from the context given. If your answer is about one particular job, give its jobId too, so the boss gets a link to where that job's updates are collecting.
|