@oddessentials/agent-guild 1.14.0 → 1.15.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/README.md +31 -16
- package/bin/agent-guild-report.mjs +0 -5
- package/docs/api.md +850 -0
- package/examples/claude-code-settings.json +4 -0
- package/node_modules/node-pty/prebuilds/win32-arm64/conpty.node +0 -0
- package/node_modules/node-pty/prebuilds/win32-arm64/conpty_console_list.node +0 -0
- package/node_modules/node-pty/prebuilds/win32-x64/conpty.node +0 -0
- package/node_modules/node-pty/prebuilds/win32-x64/conpty_console_list.node +0 -0
- package/package.json +3 -2
- package/src/manager/server.mjs +5 -3
- package/src/manager/session-hooks.mjs +3 -4
- package/src/manager/session-manager.mjs +0 -8
- package/src/manager/session.mjs +4 -106
- package/src/report/hooks.mjs +0 -24
- package/web/api-doc.js +30 -0
- package/web/api-explorer.js +164 -0
- package/web/api.html +40 -0
- package/web/app.js +4 -1
- package/web/host.js +1 -0
- package/web/index.html +23 -4
- package/web/yard/art.json +1 -1
- package/web/yard/model.mjs +13 -0
- package/web/yard/photo.js +228 -0
- package/web/yard/renderer.js +46 -1
- package/web/yard/view.js +16 -2
- package/web/yard/yard.css +19 -10
package/docs/api.md
ADDED
|
@@ -0,0 +1,850 @@
|
|
|
1
|
+
# Session manager API (v1)
|
|
2
|
+
|
|
3
|
+
The session manager is the only component that owns terminal processes. The
|
|
4
|
+
web page is one client of this API. Any other front end, such as a future
|
|
5
|
+
Unreal Engine interface, can use the same API to list, start, watch and drive
|
|
6
|
+
the same sessions at the same time.
|
|
7
|
+
|
|
8
|
+
This is the contract a client relies on. How the manager works out each value
|
|
9
|
+
is in the code (see [architecture.md](architecture.md)). A running manager
|
|
10
|
+
serves this doc at `/api.html`, where its read-only routes and sockets can be
|
|
11
|
+
tried.
|
|
12
|
+
|
|
13
|
+
## Discovery and authentication
|
|
14
|
+
|
|
15
|
+
The manager listens on `127.0.0.1` only. Its default port is 47821, and
|
|
16
|
+
`AGENT_GUILD_PORT` overrides it.
|
|
17
|
+
|
|
18
|
+
A client finds a running manager through two files in the per-user data
|
|
19
|
+
directory:
|
|
20
|
+
|
|
21
|
+
| Platform | Data directory |
|
|
22
|
+
| --- | --- |
|
|
23
|
+
| Windows | `%APPDATA%\AgentGuild` |
|
|
24
|
+
| macOS | `~/Library/Application Support/AgentGuild` |
|
|
25
|
+
| Linux | `$XDG_CONFIG_HOME/agent-guild` (default `~/.config/agent-guild`) |
|
|
26
|
+
|
|
27
|
+
* `manager.json` exists while a manager runs. It holds `pid`, `host`, `port`,
|
|
28
|
+
`url`, `version` and `startedAt`.
|
|
29
|
+
* `auth-token` holds the API token. It persists across restarts. Delete it to
|
|
30
|
+
rotate the token.
|
|
31
|
+
|
|
32
|
+
Every endpoint except `GET /api/v1/health` requires the token:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
Authorization: Bearer <token>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
WebSocket clients that cannot set headers, such as browsers, pass
|
|
39
|
+
`?token=<token>` in the URL instead.
|
|
40
|
+
|
|
41
|
+
A device that cannot scan the page's sign-in QR code, such as a TV, obtains
|
|
42
|
+
the token by device sign-in: `POST /sign-in` answers a code to show on screen
|
|
43
|
+
and a `device` secret, the person approves the code in the page's
|
|
44
|
+
**Remote access** dialog, and `POST /sign-in/poll` with that secret then
|
|
45
|
+
answers the token once. Requests expire after five minutes; at most five are
|
|
46
|
+
live, and a new one is accepted every two seconds.
|
|
47
|
+
|
|
48
|
+
The manager rejects requests whose `Host` or browser `Origin` is outside
|
|
49
|
+
its loopback and saved remote-access allowlists. Native clients that send no
|
|
50
|
+
`Origin` still need an accepted Host and the API token. See
|
|
51
|
+
[remote access setup](configuration.md#reverse-proxies).
|
|
52
|
+
|
|
53
|
+
Errors use one shape:
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{ "error": { "code": "provider_unavailable", "message": "Codex CLI (\"codex\") was not found on PATH. ..." } }
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`message` is written for people. The codes below are the ones a client acts
|
|
60
|
+
on; any route can answer others.
|
|
61
|
+
|
|
62
|
+
## Objects
|
|
63
|
+
|
|
64
|
+
### Provider
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"id": "anthropic",
|
|
69
|
+
"vendor": "Anthropic",
|
|
70
|
+
"tool": "Claude Code",
|
|
71
|
+
"command": "claude",
|
|
72
|
+
"package": "@anthropic-ai/claude-code",
|
|
73
|
+
"args": [],
|
|
74
|
+
"resumable": true,
|
|
75
|
+
"installable": true,
|
|
76
|
+
"installedVersion": "2.1.285",
|
|
77
|
+
"latestVersion": "2.1.290",
|
|
78
|
+
"updateAvailable": true,
|
|
79
|
+
"usageSource": "claude",
|
|
80
|
+
"historySource": "claude",
|
|
81
|
+
"memorySource": "claude",
|
|
82
|
+
"accounts": [{ "id": "default", "label": "Default" }, { "id": "work", "label": "Work" }],
|
|
83
|
+
"color": "#D97757",
|
|
84
|
+
"monogram": "A",
|
|
85
|
+
"iconUrl": null,
|
|
86
|
+
"install": "curl -fsSL https://claude.ai/install.sh | bash",
|
|
87
|
+
"docs": "https://code.claude.com/docs/en/setup",
|
|
88
|
+
"usageUrl": "https://claude.ai/settings/usage",
|
|
89
|
+
"billingUrl": "https://claude.ai/settings/billing",
|
|
90
|
+
"cloudUrl": "https://claude.ai/code",
|
|
91
|
+
"available": true,
|
|
92
|
+
"resolvedPath": "/usr/local/bin/claude"
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`available` is false when the command is not installed. A client should show
|
|
97
|
+
the provider as disabled and offer `POST /providers/:id/install` when
|
|
98
|
+
`installable` is true, or the `install` hint otherwise. `resumable` is true
|
|
99
|
+
when one of the tool's own earlier sessions can be resumed by id.
|
|
100
|
+
|
|
101
|
+
`installedVersion` and `latestVersion` are null until the first version check
|
|
102
|
+
finishes; a `providers.updated` event follows. `updateAvailable` is true when
|
|
103
|
+
the latest version is newer, and `POST /providers/:id/install` performs the
|
|
104
|
+
update when `updateCommand` is not null. `versionStatus` is `ok`,
|
|
105
|
+
`unavailable` (the tool printed no version) or `failed`, with `versionError`
|
|
106
|
+
saying why, and null while the tool is missing. `installChannel` is the
|
|
107
|
+
running copy's `channel`, or null. `lastInstall` is the last install, update
|
|
108
|
+
or uninstall session's result,
|
|
109
|
+
`{ kind, outcome, exitCode, verification, before, after, latest, at }`, with
|
|
110
|
+
`outcome` `installed`, `missing`, `updated`, `unchanged`, `done`, `removed`,
|
|
111
|
+
`remaining` or `failed`, and `at` in epoch milliseconds; or null. `warnings`
|
|
112
|
+
are lines the card shows about the copies, such as several being installed.
|
|
113
|
+
`npmNote` qualifies an npm install, or is null. `modelPattern` is the
|
|
114
|
+
provider's, or null. `reporting` is the tool's reporting mode: `claude`,
|
|
115
|
+
`codex`, `antigravity`, `grok`, `docker`, or null.
|
|
116
|
+
|
|
117
|
+
`installs` lists every copy of the tool that was found, each
|
|
118
|
+
`{ path, displayPath, channel, version, versionStatus, active, newer, onPath, uninstall, uninstallGuidance }`.
|
|
119
|
+
`channel` is `npm`, `native`, `brew`, `winget`, `legacy` or `unknown`, and for
|
|
120
|
+
a Docker CLI plugin `download`, `desktop`, `system` or `unknown`. `active`
|
|
121
|
+
marks the copy that runs, `newer` a copy newer than that one, and `onPath`
|
|
122
|
+
whether its folder is on PATH. `uninstall` is null when Agent Guild cannot
|
|
123
|
+
remove that copy, and `uninstallGuidance` then says how to remove it instead.
|
|
124
|
+
`POST /providers/:id/uninstall` removes one copy.
|
|
125
|
+
|
|
126
|
+
For a tool that is a Docker CLI plugin (Docker Agent), `installCommand` is the
|
|
127
|
+
command **Install** and **Update** run. `pluginError` says why docker could not
|
|
128
|
+
list the plugin's copies, or is null; `pluginPending` is true until docker has
|
|
129
|
+
listed them once.
|
|
130
|
+
|
|
131
|
+
`usageSource`, `historySource`, `memorySource` and `instructionsSource` are
|
|
132
|
+
null when `GET /usage`, `GET /providers/:id/history`,
|
|
133
|
+
`GET /providers/:id/memory` or `GET /providers/:id/instructions` cannot report
|
|
134
|
+
the tool. `historyDetails` is true when `GET /providers/:id/history/detail`
|
|
135
|
+
can read saved messages. `accounts` lists the sign-ins the tool can run
|
|
136
|
+
under: `default` is the tool's own, and each further one has its own home
|
|
137
|
+
folder, so it keeps its own sign-in and usage. `POST /sessions` takes an
|
|
138
|
+
account id. `accountsEditable` is true when accounts can be added, renamed
|
|
139
|
+
and removed through `/providers/:id/accounts`.
|
|
140
|
+
|
|
141
|
+
`shells` is null except for the `@shell` provider, where it lists the
|
|
142
|
+
installed shells, each `{ id, label, path, multiplexer }`, and `defaultShell`
|
|
143
|
+
is the id of the one a session runs unless `POST /sessions` names another in
|
|
144
|
+
`shell`. `multiplexer` is true for tmux and herdr, which are never the
|
|
145
|
+
default. Stopping or removing a session in one, or stopping the manager,
|
|
146
|
+
leaves the multiplexer's own session running, and
|
|
147
|
+
`POST /sessions/:id/reattach` attaches it again, also from the next manager.
|
|
148
|
+
`multiplexers` is `[]` for every provider but `@shell`; see
|
|
149
|
+
[Multiplexer installations](#multiplexer-installations).
|
|
150
|
+
|
|
151
|
+
For a tool whose agent or model reporting has to be turned on,
|
|
152
|
+
`reportingEnabled` says whether it is, and `reportingNote` says why that switch
|
|
153
|
+
cannot be used, or is null. `usageUrl`, `billingUrl` and `cloudUrl` are
|
|
154
|
+
`https://` links to the vendor's usage and billing pages and web app, or null.
|
|
155
|
+
|
|
156
|
+
### Manager environment
|
|
157
|
+
|
|
158
|
+
`GET /environment?scope=` returns the cached snapshot of a scope and starts
|
|
159
|
+
its first check if none has finished. `POST /environment/refresh` with
|
|
160
|
+
`{ scope, cwd?, id? }` starts a new check and returns `202` at once; an
|
|
161
|
+
`environment.updated` event carries the snapshot when it changes. `scope` is:
|
|
162
|
+
|
|
163
|
+
* `manager` (the default): what the manager process finds on its PATH.
|
|
164
|
+
* `project`, with `cwd`: the version pin files in that folder, read without
|
|
165
|
+
running anything.
|
|
166
|
+
* `session`, with `id`: the PATH recorded when that session was spawned.
|
|
167
|
+
* `launch`: the manager's PATH, as a new process gets it before shell startup
|
|
168
|
+
files.
|
|
169
|
+
|
|
170
|
+
Every snapshot has `scope`, `host` (the computer running the manager),
|
|
171
|
+
`revision`, `refreshing`, `checkedAt` and `error`, which is null or says why
|
|
172
|
+
the check itself did not finish. A refresh keeps the previous results while
|
|
173
|
+
`refreshing` is true.
|
|
174
|
+
|
|
175
|
+
A project snapshot adds `cwd`, `stale` and `pins[]`, each
|
|
176
|
+
`{ id, label, source, version, status, detail }` with `status` `configured`,
|
|
177
|
+
`unreadable` or `invalid`. `configured` means the file asks for that version,
|
|
178
|
+
not that it is installed. A session snapshot adds `sessionId`, `spawnCwd` and
|
|
179
|
+
`availability`: `ok`, or `unavailable` for tmux and herdr.
|
|
180
|
+
|
|
181
|
+
The manager snapshot adds `platform`, `managerNode: { version, path }`,
|
|
182
|
+
`system`, `runtimes[]`, `designTools[]`, `docker` and `tools[]`. Session and
|
|
183
|
+
launch snapshots have `docker` but not `system`; a tmux or herdr session has
|
|
184
|
+
`docker: null`.
|
|
185
|
+
|
|
186
|
+
* `runtimes[]`: Node.js, Python, Go, .NET SDK, R and Rust, each with `id`,
|
|
187
|
+
`label`, `status`, `version` and `path`, and once checked `command` and an
|
|
188
|
+
optional `detail`. `status` is
|
|
189
|
+
`pending`, `ok`, `not_found`, `unavailable` (a launcher exists but no
|
|
190
|
+
runtime can be safely reported) or `failed`. Python may add
|
|
191
|
+
`alternatives[]`, and .NET `runtimes[]`.
|
|
192
|
+
* `designTools[]`: Blender, FFmpeg, GIMP, Inkscape and ImageMagick, each with
|
|
193
|
+
`id`, `label`, `status`, `version` and `path`, and once checked `command`
|
|
194
|
+
and `detail`.
|
|
195
|
+
`status` is `pending`, `on_path`, `not_on_path` (installed outside PATH),
|
|
196
|
+
`not_found` or `failed`.
|
|
197
|
+
* `tools[]`: `{ id, label, path, status: "detected" }` for nvm, NVM for
|
|
198
|
+
Windows, vfox, uv and pnpm.
|
|
199
|
+
* `system`: null until the first check finishes or when it cannot be read,
|
|
200
|
+
else `{ os, osDetail, arch, hostArch, cpu: { model, threads }, memory, wsl, wslDistributions }`.
|
|
201
|
+
* `docker`: `{ status, version, platform, os, arch, wsl2, context, endpoint, cli, detail }`,
|
|
202
|
+
with `status` `pending`, `running`, `stopped`, `not_found`, `denied`,
|
|
203
|
+
`remote` or `failed`. Fields other than `status` may be missing.
|
|
204
|
+
|
|
205
|
+
Probe output and the environment itself are never returned.
|
|
206
|
+
|
|
207
|
+
### Multiplexer installations
|
|
208
|
+
|
|
209
|
+
An `@shell` provider also describes `multiplexers[]`. Each entry contains
|
|
210
|
+
`id` (`tmux` or `herdr`), `tool`, `docs`, `checked`, `available`,
|
|
211
|
+
`installable`, `installCommand`, `guidance`, `busy`, `pendingCards`,
|
|
212
|
+
`installs` and `lastInstall`; some are missing while `checked` is false.
|
|
213
|
+
Each copy in `installs` has the provider-copy fields except `newer`, and its own
|
|
214
|
+
`latestVersion`, `updateAvailable` and `updateCommand`. Never construct
|
|
215
|
+
commands from these fields: submit the returned `path` to the API.
|
|
216
|
+
|
|
217
|
+
These routes return `201 { session }`, with `task: "install"`:
|
|
218
|
+
|
|
219
|
+
| Route below `/api/v1` | Body |
|
|
220
|
+
| --- | --- |
|
|
221
|
+
| `POST /providers/:provider/multiplexers/:id/install` | `{ force?: boolean }` |
|
|
222
|
+
| `POST /providers/:provider/multiplexers/:id/update` | `{ path: string, force?: boolean }` |
|
|
223
|
+
| `POST /providers/:provider/multiplexers/:id/uninstall` | `{ path: string, force?: boolean }` |
|
|
224
|
+
|
|
225
|
+
409 `multiplexer_in_use`, with `running` and `pending` counts, can be retried
|
|
226
|
+
with `force: true` after the user confirms. Herdr Uninstall refuses with 409
|
|
227
|
+
`herdr_running` while herdr has a session running; `force` does not bypass
|
|
228
|
+
that. Results arrive through `providers.updated`.
|
|
229
|
+
|
|
230
|
+
### Usage
|
|
231
|
+
|
|
232
|
+
```json
|
|
233
|
+
{
|
|
234
|
+
"providerId": "anthropic",
|
|
235
|
+
"accountId": "default",
|
|
236
|
+
"plan": "max",
|
|
237
|
+
"signedIn": true,
|
|
238
|
+
"windows": [
|
|
239
|
+
{ "label": "5-hour", "usedPercent": 42.5, "resetsAt": "2026-09-30T08:00:00.000Z" },
|
|
240
|
+
{ "label": "7-day", "usedPercent": 12, "resetsAt": "2026-10-03T05:00:00.000Z" },
|
|
241
|
+
{ "label": "7-day Fable 5.1", "usedPercent": 48, "resetsAt": "2026-10-03T05:00:00.000Z" }
|
|
242
|
+
],
|
|
243
|
+
"credits": null,
|
|
244
|
+
"fetchedAt": "2026-09-30T03:12:01.120Z",
|
|
245
|
+
"error": null
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
One snapshot per account. Each window is one rate limit of the account's
|
|
250
|
+
subscription. `plan` is the subscription tier. `credits` is a prepaid credit
|
|
251
|
+
balance (Codex), or null. When the lookup failed, `windows` is empty and
|
|
252
|
+
`error` says why; `signedIn` is false when no sign-in was found for that
|
|
253
|
+
account, true when one was read, and null when that is unknown.
|
|
254
|
+
`rateLimited` is true when the vendor rate-limited the lookup.
|
|
255
|
+
|
|
256
|
+
### History
|
|
257
|
+
|
|
258
|
+
```json
|
|
259
|
+
{
|
|
260
|
+
"providerId": "anthropic",
|
|
261
|
+
"accountId": "default",
|
|
262
|
+
"sessions": [
|
|
263
|
+
{ "id": "581893e5-a93d-5e49-968b-1c1c277d3255", "title": "Fix the login bug", "cwd": "/Users/me/src/app", "startedAt": "2026-10-01T13:26:53.713Z", "updatedAt": "2026-10-01T13:49:28.000Z" }
|
|
264
|
+
],
|
|
265
|
+
"total": 42,
|
|
266
|
+
"fetchedAt": "2026-10-01T14:00:00.000Z",
|
|
267
|
+
"error": null
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
The tool's own earlier sessions, newest first. `id` is what the tool resumes
|
|
272
|
+
by (`POST /sessions` with `resume`); `title` is the session's name or first
|
|
273
|
+
prompt, or null; `cwd` is the folder the session ran in, or null. Claude Code
|
|
274
|
+
finds a session only from its own folder, so resume with that `cwd`.
|
|
275
|
+
`sessions` holds at most `limit` entries of the `total` found. `error` says
|
|
276
|
+
why nothing could be listed; a tool that has never run lists no sessions and
|
|
277
|
+
no error. Antigravity CLI entries add `workspaces`, and its history may add a
|
|
278
|
+
`note`.
|
|
279
|
+
|
|
280
|
+
The optional `cwd` and `q` parameters filter by folder and by text (title, id
|
|
281
|
+
or folder) **before** applying `limit`; `total` counts matching entries.
|
|
282
|
+
|
|
283
|
+
#### Saved messages
|
|
284
|
+
|
|
285
|
+
`GET /providers/:id/history/detail?account=&id=&cursor=` returns:
|
|
286
|
+
|
|
287
|
+
```json
|
|
288
|
+
{
|
|
289
|
+
"detail": {
|
|
290
|
+
"id": "581893e5-a93d-5e49-968b-1c1c277d3255",
|
|
291
|
+
"messages": [{ "role": "user", "text": "Fix the login bug" }],
|
|
292
|
+
"nextCursor": null,
|
|
293
|
+
"incomplete": false,
|
|
294
|
+
"omitted": false
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Only the user's requests and the visible answers are included, as plain text.
|
|
300
|
+
Follow `nextCursor` until it is null, even when a page has no visible
|
|
301
|
+
messages. `omitted` says records were left out; `incomplete` says the last one
|
|
302
|
+
was partial, and a later read may complete it. 409 `history_changed` means the
|
|
303
|
+
transcript changed: start again without a cursor.
|
|
304
|
+
|
|
305
|
+
### Memory
|
|
306
|
+
|
|
307
|
+
```json
|
|
308
|
+
{
|
|
309
|
+
"providerId": "xai",
|
|
310
|
+
"accountId": "default",
|
|
311
|
+
"folder": "/Users/me/src/app",
|
|
312
|
+
"scopes": [
|
|
313
|
+
{ "id": "project", "label": "This project", "dir": "/Users/me/.grok/memory-v2/workspaces/app-1c1b5e9a", "note": null, "truncated": false,
|
|
314
|
+
"files": [{ "path": "topics/build.md", "fullPath": "/Users/me/.grok/memory-v2/workspaces/app-1c1b5e9a/topics/build.md", "folder": "/Users/me/.grok/memory-v2/workspaces/app-1c1b5e9a/topics", "title": "Build", "bytes": 2048, "modified": "2026-10-01T13:49:28.000Z" }] },
|
|
315
|
+
{ "id": "global", "label": "Global", "dir": "/Users/me/.grok/memory-v2/global", "note": null, "truncated": false, "files": [] }
|
|
316
|
+
],
|
|
317
|
+
"fetchedAt": "2026-10-01T14:00:00.000Z"
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
The markdown files a tool keeps as memory for the working folder `folder`,
|
|
322
|
+
in the scopes that tool has. `dir` is null when the tool keeps no memory
|
|
323
|
+
there, and `note` says why, or anything else that qualifies the scope. A
|
|
324
|
+
file's `path` is relative to `dir`, `fullPath` is its full path and `folder`
|
|
325
|
+
the folder that holds it; `title` is its first `# ` heading, or null.
|
|
326
|
+
`truncated` says the scope has more files than were listed.
|
|
327
|
+
|
|
328
|
+
### Instruction files
|
|
329
|
+
|
|
330
|
+
```json
|
|
331
|
+
{
|
|
332
|
+
"providerId": "anthropic",
|
|
333
|
+
"accountId": "default",
|
|
334
|
+
"folder": "/Users/me/src/app",
|
|
335
|
+
"count": 2,
|
|
336
|
+
"note": null,
|
|
337
|
+
"scopes": [
|
|
338
|
+
{ "id": "global", "label": "Your instructions", "note": null,
|
|
339
|
+
"files": [{ "index": 0, "path": "/Users/me/.claude/CLAUDE.md", "folder": "/Users/me/.claude", "name": "CLAUDE.md", "location": "~/.claude", "scope": "global", "bytes": 512, "modified": "2026-10-01T13:49:28.000Z", "skipped": null, "note": null }] },
|
|
340
|
+
{ "id": "project", "label": "Project", "note": null,
|
|
341
|
+
"files": [
|
|
342
|
+
{ "index": 1, "path": "/Users/me/src/app/CLAUDE.md", "folder": "/Users/me/src/app", "name": "CLAUDE.md", "location": "app", "scope": "project", "bytes": 2048, "modified": "2026-10-01T13:49:28.000Z", "skipped": null, "note": null },
|
|
343
|
+
{ "index": 2, "path": "/Users/me/src/app/AGENTS.md", "folder": "/Users/me/src/app", "name": "AGENTS.md", "location": "app", "scope": "project", "bytes": 900, "modified": "2026-10-01T13:49:28.000Z", "skipped": "Claude Code reads CLAUDE.md instead.", "note": null }
|
|
344
|
+
] }
|
|
345
|
+
],
|
|
346
|
+
"fetchedAt": "2026-10-01T14:00:00.000Z"
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
The files a tool loads when a session starts in `folder`: managed and global
|
|
351
|
+
files first, then project files from the outermost folder down.
|
|
352
|
+
`scope` is `managed`, `global` or `project`, and `location` is the file's
|
|
353
|
+
folder in short. `count` is the number of files that load; a file that exists
|
|
354
|
+
but does not load has `skipped` set to the reason, and a file's `note`
|
|
355
|
+
qualifies one that loads only in part. A scope's `note` says why it lists no
|
|
356
|
+
files. When the tool cannot be asked, `count` is null, `scopes` is empty and
|
|
357
|
+
`note` says why.
|
|
358
|
+
|
|
359
|
+
### Session
|
|
360
|
+
|
|
361
|
+
```json
|
|
362
|
+
{
|
|
363
|
+
"id": "b45b6822d158",
|
|
364
|
+
"name": "Claude Code",
|
|
365
|
+
"provider": { "id": "anthropic", "vendor": "Anthropic", "tool": "Claude Code", "color": "#D97757", "monogram": "A", "iconUrl": null },
|
|
366
|
+
"cwd": "/Users/me/src/app",
|
|
367
|
+
"resume": null,
|
|
368
|
+
"task": null,
|
|
369
|
+
"account": { "id": "default", "label": "Default" },
|
|
370
|
+
"clone": null,
|
|
371
|
+
"multiplexer": null,
|
|
372
|
+
"pid": 3518,
|
|
373
|
+
"status": "running",
|
|
374
|
+
"exitCode": null,
|
|
375
|
+
"signal": null,
|
|
376
|
+
"activity": "active",
|
|
377
|
+
"label": "Working",
|
|
378
|
+
"lastOutputAt": "2026-09-30T03:12:01.120Z",
|
|
379
|
+
"createdAt": "2026-09-30T03:10:44.001Z",
|
|
380
|
+
"startedAt": "2026-09-30T03:10:44.001Z",
|
|
381
|
+
"exitedAt": null,
|
|
382
|
+
"cols": 120,
|
|
383
|
+
"rows": 32,
|
|
384
|
+
"attachedClients": 1,
|
|
385
|
+
"model": { "name": "claude-opus-4-5", "displayName": "Opus 4.5", "source": "report" },
|
|
386
|
+
"toolSessionId": "581893e5-a93d-5e49-968b-1c1c277d3255",
|
|
387
|
+
"context": { "compactions": 2, "resets": 0 },
|
|
388
|
+
"reply": { "id": 3 },
|
|
389
|
+
"reporting": { "state": "active", "reason": null },
|
|
390
|
+
"agents": [ /* Agent */ ],
|
|
391
|
+
"shells": [{ "id": "shell-3", "kind": "shell" }]
|
|
392
|
+
}
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
* `status` is `running` or `exited`. Exited sessions stay listed, with their
|
|
396
|
+
final screen, until a client removes them.
|
|
397
|
+
* `exitedAt` is null while running, then when the process exited.
|
|
398
|
+
* `pid` is null while the process is still starting and after it exits.
|
|
399
|
+
* `activity` is `active` while the terminal is producing output and `quiet`
|
|
400
|
+
after a short pause.
|
|
401
|
+
* `label` is what the session's card shows, for any client to show the same:
|
|
402
|
+
`Working` while running and `active`, else `Running`. Once exited, it is
|
|
403
|
+
`Closed` for a tmux or herdr session, `Exited (<signal>)` when a signal ended
|
|
404
|
+
it, `Exited (<exit code>)` for an exit code other than 0, and otherwise `Exited`.
|
|
405
|
+
* `resume` is the id of the tool's own session that was resumed, or null.
|
|
406
|
+
* `task` is `install` for a session that installs, updates or uninstalls a
|
|
407
|
+
tool, `upgrade` for the session that upgrades the manager (its
|
|
408
|
+
`provider` id is `agent-guild`), `clone` for a `git clone` (its `provider`
|
|
409
|
+
id is `github`), and null for a session that runs the tool itself.
|
|
410
|
+
* `clone` is `{ repo, path, accountId }` for a clone session, else null.
|
|
411
|
+
* `multiplexer` is `{ label, attach, reattachable }` for a Shell session in
|
|
412
|
+
tmux or herdr: the multiplexer's name, the command that reattaches its
|
|
413
|
+
session from a terminal, and whether the exited session can be reattached.
|
|
414
|
+
Null otherwise.
|
|
415
|
+
* `startedAt` is when the session's current process started: `createdAt`,
|
|
416
|
+
or later once a tmux or herdr session is reattached.
|
|
417
|
+
* `account` is the provider account the tool runs under, or null for an
|
|
418
|
+
install or upgrade session.
|
|
419
|
+
* `model` is the main model the tool is using, or null while unknown.
|
|
420
|
+
`source` is `report`, `screen` or `args`.
|
|
421
|
+
* `toolSessionId` is the id the tool gave its own session, or null. It names
|
|
422
|
+
the session in `GET /providers/:id/history`.
|
|
423
|
+
* `context` counts the times the tool compacted its context and started over
|
|
424
|
+
in a new session (`/clear`, `/new`) since the card started.
|
|
425
|
+
* `reply` names the answer that ended the agent's last turn by `id`, or is
|
|
426
|
+
null. Its text is read with `GET /sessions/:id/reply`.
|
|
427
|
+
* `reporting` says whether the tool's reporting hooks work, or is null for a
|
|
428
|
+
tool Agent Guild supplies no hooks to. `state` is `pending`, `active`,
|
|
429
|
+
`unavailable`, `setup_required` or `unsupported`; `reason` explains every
|
|
430
|
+
state but `active`.
|
|
431
|
+
* `shells` lists the shell commands the tool is running, each `{ id, kind }`
|
|
432
|
+
with `kind` `shell` or `monitor`. Nothing about the command itself is
|
|
433
|
+
included.
|
|
434
|
+
|
|
435
|
+
### Upgrade
|
|
436
|
+
|
|
437
|
+
The manager's own version check, in `GET /info`, the `hello` message and
|
|
438
|
+
`manager.upgrade` events.
|
|
439
|
+
|
|
440
|
+
```json
|
|
441
|
+
{
|
|
442
|
+
"version": "1.2.0",
|
|
443
|
+
"latestVersion": "1.3.0",
|
|
444
|
+
"available": true,
|
|
445
|
+
"command": "/usr/local/bin/npm install -g --prefix /usr/local @oddessentials/agent-guild@1.3.0",
|
|
446
|
+
"guidance": null,
|
|
447
|
+
"pendingVersion": null,
|
|
448
|
+
"installing": false,
|
|
449
|
+
"lastInstall": null,
|
|
450
|
+
"ptyBuild": null
|
|
451
|
+
}
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
`version` is the running manager. `available` is true when a newer release
|
|
455
|
+
can be installed; `command` is then what `POST /upgrade` runs, or null with
|
|
456
|
+
`guidance`. `pendingVersion` is a newer version already on disk, which runs
|
|
457
|
+
after a restart (`POST /shutdown` with `restart`). `lastInstall` is the last
|
|
458
|
+
upgrade session's `{ outcome, exitCode, version, installedVersion, at }`, or
|
|
459
|
+
null. `installing` is true while an upgrade runs, and `available`, `command`,
|
|
460
|
+
`guidance`, `pendingVersion`, `lastInstall` and `ptyBuild` are withheld until
|
|
461
|
+
it ends.
|
|
462
|
+
|
|
463
|
+
After the upgraded manager starts, refresh every open browser page.
|
|
464
|
+
Reconnecting does not reload a page's JavaScript, and stale frontend code is
|
|
465
|
+
not guaranteed to work with the new manager.
|
|
466
|
+
|
|
467
|
+
`ptyBuild` is null where node-pty runs from its bundled builds. Elsewhere,
|
|
468
|
+
such as Alpine, it is `{ command, built }`: the command that compiles
|
|
469
|
+
node-pty, and whether that build is on disk. `POST /shutdown` with `restart`
|
|
470
|
+
answers 409 `pty_unavailable` until it is.
|
|
471
|
+
|
|
472
|
+
### GitHub
|
|
473
|
+
|
|
474
|
+
The GitHub accounts signed in to Agent Guild, in `GET /github`. Tokens never
|
|
475
|
+
leave the manager.
|
|
476
|
+
|
|
477
|
+
```json
|
|
478
|
+
{
|
|
479
|
+
"scopes": ["repo", "write:public_key"],
|
|
480
|
+
"appUrl": "https://github.com/settings/connections/applications/Ov23lif6qqYKtXZTb130",
|
|
481
|
+
"keysUrl": "https://github.com/settings/keys",
|
|
482
|
+
"newKeyUrl": "https://github.com/settings/ssh/new",
|
|
483
|
+
"tools": { "git": true, "ssh": true, "sshKeygen": true },
|
|
484
|
+
"signIn": { "status": "pending", "userCode": "WDJB-MJHT", "verificationUri": "https://github.com/login/device", "expiresAt": "2026-10-01T14:15:00.000Z", "accountId": null, "again": false, "error": null },
|
|
485
|
+
"accounts": [
|
|
486
|
+
{
|
|
487
|
+
"id": 4242, "login": "octo-cat", "name": "Octo Cat", "avatar": "data:image/png;base64,...", "scopes": ["repo", "write:public_key"],
|
|
488
|
+
"needsSignIn": false, "addedAt": "2026-10-01T14:00:00.000Z",
|
|
489
|
+
"ssh": { "status": "ready", "key": "/home/me/.config/agent-guild/github/keys/agent-guild-github-4242", "publicKey": "ssh-ed25519 AAAA... agent-guild github octo-cat (4242)", "verifiedAt": "2026-10-01T14:01:00.000Z", "settingUp": false, "error": null }
|
|
490
|
+
}
|
|
491
|
+
]
|
|
492
|
+
}
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
Sign-in uses GitHub's device flow: `signIn` is `pending` while the user
|
|
496
|
+
enters `userCode` at `verificationUri`, then `done` (with `accountId`, and
|
|
497
|
+
`again` when that GitHub user was already signed in), `expired`, `denied` or
|
|
498
|
+
`failed`; null when none was started or it was cancelled. Accounts are keyed
|
|
499
|
+
by GitHub's numeric user id; `login` is display only. `needsSignIn` is true
|
|
500
|
+
once GitHub refuses the account's token. `ssh.status` is `none`, `unverified`
|
|
501
|
+
or `ready`. `ssh.error` is `{ code, message, manual }`, with `manual` true
|
|
502
|
+
when the user must add `publicKey` on GitHub themselves.
|
|
503
|
+
|
|
504
|
+
### Repository views
|
|
505
|
+
|
|
506
|
+
Issues, workflow runs and open pull requests of one repository, read with one
|
|
507
|
+
signed-in account. Each list holds one page of GitHub's results;
|
|
508
|
+
`truncated` is true when GitHub has more.
|
|
509
|
+
|
|
510
|
+
```json
|
|
511
|
+
{ "issues": [{ "number": 4, "title": "Dock is too narrow", "body": "Steps", "state": "open", "user": "octo-cat", "comments": 2, "updatedAt": "2026-10-01T00:00:00.000Z", "url": "https://github.com/octo-cat/agent-guild/issues/4" }],
|
|
512
|
+
"truncated": false, "url": "https://github.com/octo-cat/agent-guild/issues" }
|
|
513
|
+
{ "runs": [{ "id": 11, "name": "CI", "title": "Fix the gate", "branch": "main", "event": "push", "status": "in_progress", "conclusion": null, "runNumber": 12, "updatedAt": "2026-10-02T00:00:00.000Z", "url": "https://github.com/octo-cat/agent-guild/actions/runs/11" }],
|
|
514
|
+
"running": true, "service": null, "truncated": false, "url": "https://github.com/octo-cat/agent-guild/actions" }
|
|
515
|
+
{ "pulls": [{ "number": 8, "title": "Add viewer", "draft": true, "user": "ada", "head": "viewer", "base": "main", "updatedAt": "2026-10-02T00:00:00.000Z", "url": "https://github.com/octo-cat/agent-guild/pull/8" }],
|
|
516
|
+
"truncated": false, "url": "https://github.com/octo-cat/agent-guild/pulls" }
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
`running` is true while any listed run has not finished. `service` is null
|
|
520
|
+
while GitHub reports Actions operational or its status cannot be read, else `{ status, incident, url }`
|
|
521
|
+
from githubstatus.com. 409 `github_sign_in` means the account must sign in
|
|
522
|
+
again.
|
|
523
|
+
|
|
524
|
+
Branches come a page at a time, as
|
|
525
|
+
`{ branches, nextPage, defaultBranch, metadataError, fetchedAt, url }`, each
|
|
526
|
+
branch `{ name, sha, protected, url }`. Follow `nextPage` until it is null,
|
|
527
|
+
even after a short or empty page. Only page one has `defaultBranch`;
|
|
528
|
+
`metadataError` says why it could not be read.
|
|
529
|
+
|
|
530
|
+
Activity is the account's own work in the repository from `since` (an ISO
|
|
531
|
+
8601 time) on:
|
|
532
|
+
|
|
533
|
+
```json
|
|
534
|
+
{ "since": "2026-10-01T00:00:00.000Z",
|
|
535
|
+
"merged": [{ "repo": "octo-cat/agent-guild", "number": 8, "title": "Add viewer", "mergedAt": "2026-10-02T00:00:00.000Z", "url": "https://github.com/octo-cat/agent-guild/pull/8" }],
|
|
536
|
+
"closed": [{ "number": 4, "title": "Dock is too narrow", "closedAt": "2026-10-02T00:00:00.000Z", "url": "https://github.com/octo-cat/agent-guild/issues/4" }],
|
|
537
|
+
"passed": [{ "id": 11, "name": "CI", "branch": "main", "event": "push", "createdAt": "2026-10-02T00:00:00.000Z", "url": "https://github.com/octo-cat/agent-guild/actions/runs/11" }],
|
|
538
|
+
"truncated": false }
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
`merged` are pull requests the account opened that were merged at or after
|
|
542
|
+
`since`, in the repository or, for a fork, from it into its parent (`repo`
|
|
543
|
+
says which). `closed` are issues the account closed as completed at or after
|
|
544
|
+
`since`. `passed` are workflow runs created at or after `since` by the
|
|
545
|
+
account that succeeded. A run created earlier does not count even if it
|
|
546
|
+
ran, or was re-run successfully, later. Accounts are matched by GitHub's
|
|
547
|
+
numeric user id; the login used to query runs is refreshed first.
|
|
548
|
+
|
|
549
|
+
Each listing reads at most three pages of 100, and at most 20 candidate
|
|
550
|
+
issue closures are checked. `truncated` is true when a cap leaves more to
|
|
551
|
+
examine, so matching work may be missing. Listings are read newest first
|
|
552
|
+
by update time for pull requests and issues, and creation time for runs.
|
|
553
|
+
An empty list is what GitHub returned; during a GitHub service incident it
|
|
554
|
+
has returned empty lists that were not complete, so it is not proof of no
|
|
555
|
+
activity.
|
|
556
|
+
|
|
557
|
+
### Agent
|
|
558
|
+
|
|
559
|
+
An agent is a worker that the coding tool reports inside a session, such as a
|
|
560
|
+
Claude Code sub-agent. See [agent-reporting.md](agent-reporting.md).
|
|
561
|
+
|
|
562
|
+
```json
|
|
563
|
+
{
|
|
564
|
+
"id": "hook-task-c9df2e9f7dd4e090",
|
|
565
|
+
"name": "codebase_investigator",
|
|
566
|
+
"kind": "subagent",
|
|
567
|
+
"status": "working",
|
|
568
|
+
"detail": "Map the auth flow",
|
|
569
|
+
"startedAt": "2026-09-30T03:11:02.000Z",
|
|
570
|
+
"updatedAt": "2026-09-30T03:11:02.000Z",
|
|
571
|
+
"source": "api"
|
|
572
|
+
}
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
`status` is one of `working`, `waiting`, `idle` or `done`. An agent reported
|
|
576
|
+
as `done` stays visible for a few seconds and is then removed. `source` is
|
|
577
|
+
`api` for a report over HTTP, `terminal` for one written to the terminal, and
|
|
578
|
+
`herdr` for an agent in a herdr session's panes. All agents are cleared when
|
|
579
|
+
their session exits.
|
|
580
|
+
|
|
581
|
+
## HTTP endpoints
|
|
582
|
+
|
|
583
|
+
All paths are under `/api/v1`.
|
|
584
|
+
|
|
585
|
+
| Method | Path | Body | Result |
|
|
586
|
+
| --- | --- | --- | --- |
|
|
587
|
+
| GET | `/health` | | `{ ok, name, version, pid }`. No token needed. |
|
|
588
|
+
| POST | `/sign-in` | | `201 { signIn: { code, device, expiresAt, interval } }`: a device sign-in to show as `code` and poll with `device` every `interval` seconds. No token needed. 429 `sign_in_busy` while five are live or one was made under two seconds ago. |
|
|
589
|
+
| POST | `/sign-in/poll` | `{ device }` | `{ signIn: { status } }`: `pending`, `approved` with `token`, or `denied`. An approved or denied answer is given once; after it, and after `expiresAt`, 404 `sign_in_unknown`. No token needed. |
|
|
590
|
+
| GET | `/sign-in` | | `{ signIn: { requests } }`: the pending device sign-ins, each `{ code, requestedAt, expiresAt }`. |
|
|
591
|
+
| POST | `/sign-in/approve` | `{ code }` | `{ signIn }` as above, after approving that code. 404 `sign_in_unknown` when no pending sign-in has it. |
|
|
592
|
+
| POST | `/sign-in/deny` | `{ code }` | `{ signIn }` as above, after denying it. |
|
|
593
|
+
| GET | `/info` | | `name`, `version`, `pid`, platform, start time, provider config warnings, `upgrade` (an Upgrade object), `launcher` (the double-click launcher in a checkout, else null), `folderOpener` (`{ available, label, reason }` for this client) and `remoteAccess` (`{ available: true }` when this manager can configure it, else null). |
|
|
594
|
+
| GET | `/remote-access` | | `{ remoteAccess }`: `revision`, `mode`, `busy`, `pending`, `problem` and connection details. `url` is the active address. `candidate` is the address enabling would use with Tailscale's current settings, `{ url, existing, remembered, standardUrl? }`: `remembered` when it is the address used before, and `standardUrl` when `standard: true` would choose another. |
|
|
595
|
+
| POST | `/remote-access/check` | | `{ remoteAccess }` after refreshing Tailscale's status. |
|
|
596
|
+
| PUT | `/remote-access` | `{ revision, action, adopt?, hosts?, origins? }` | `202`. `action` is `enable` (`adopt: true` manages a matching existing route; `standard: true`, only while remote access is off, skips the remembered address and takes port 443, else the next free port), `disable`, `custom` (with `hosts` and `origins`) or `forget` (leave an inactive route awaiting cleanup in Tailscale). Poll until `busy` is null and read `problem`. 409 `stale_settings` for an old `revision`. Needs the API token. Revoked WebSockets close with code 4403; their terminals keep running. |
|
|
597
|
+
| GET | `/folders?path=` | | `{ path, parent, home, segments, roots, entries, truncated, note }`: the subfolders of `path` (blank or `~` for the home folder). `entries` are `{ name, path, hidden }`. A missing `path` lists the nearest folder above it and names the requested path in `note`. |
|
|
598
|
+
| POST | `/folders` | `{ path, name }` | 201 with the `GET /folders` listing of the new folder `name`, made inside `path`. |
|
|
599
|
+
| GET | `/autostart` | | `{ autostart: { available, enabled, reason, note?, lastRun?, log? } }`: whether the manager starts when the user signs in. `reason` says why it is unavailable. `lastRun` is null or `{ at, outcome }`. On Linux outside WSL it also has `mode` (`off`, `sign-in`, `boot` or `both`) and `boot`, the systemd service's `{ available, enabled, reason, note, user, linger, state, commands }`. |
|
|
600
|
+
| PUT | `/autostart` | `{ enabled }` or `{ mode }` | `{ autostart }` as above. `mode` (`off`, `sign-in` or `boot`) makes that the one way the manager starts; `boot` is offered only on Linux with systemd. |
|
|
601
|
+
| POST | `/open-folder` | `{ cwd? }` | `{ ok }`: opens the folder in the computer's file manager. 403 `local_only` from clients that reach the manager by any address other than its own loopback ones. |
|
|
602
|
+
| GET | `/notes` | | `{ notes: { revision, text } }`. One notepad for every client of this manager. `revision` is null until the first save. |
|
|
603
|
+
| PUT | `/notes` | `{ revision, text }` | `{ notes }` with a new `revision`. `revision` is the one this edit started from, or null to create the notepad. 409 `stale_notes` when that revision is no longer current; `error.notes` is the current notepad, so a client can retry or adopt it. |
|
|
604
|
+
| POST | `/upgrade` | | `201 { session }`: a session with `task` `upgrade` running the Upgrade `command`. Sessions keep running; the new version is used after the manager restarts. |
|
|
605
|
+
| GET | `/providers` | | `{ providers: Provider[], revision }`. `revision` identifies the contents of `providers.json`, or is null when there is no file. |
|
|
606
|
+
| POST | `/providers/reload` | | `{ providers, revision, warnings }`: re-reads `providers.json` and checks every tool's versions again. New sessions use the new settings. |
|
|
607
|
+
| POST | `/providers/:id/accounts` | `{ label, revision }` | `201 { account, dir, providers, revision }`: adds an account to `providers.json`. Its id is made from `label` and does not change. |
|
|
608
|
+
| PATCH | `/providers/:id/accounts/:account` | `{ label, revision }` | `{ account, dir, providers, revision }`: renames an account, including `default`. |
|
|
609
|
+
| DELETE | `/providers/:id/accounts/:account?revision=` | | `{ account, dir, providers, revision }`: removes an account from `providers.json`. Its home folder, `dir`, stays on disk. 409 `default_account` for `default`; 409 `account_in_use` while a running session uses it. |
|
|
610
|
+
| POST | `/providers/:id/reporting` | `{ enabled }` | `{ provider }`: turns agent or model reporting on or off for a tool that has `reportingEnabled`. |
|
|
611
|
+
| POST | `/providers/:id/install` | `{ force? }` | `201 { session }`: a session that installs or updates the tool. 409 `provider_in_use` (with `running`, the session count) while the provider's sessions are running; retry with `force: true` after the user confirms. |
|
|
612
|
+
| POST | `/providers/:id/uninstall` | `{ path, force? }` | `201 { session }`: a session that removes the copy at `path`, one of the provider's `installs`. `force` as for `install`. |
|
|
613
|
+
| GET | `/environment?scope=&cwd=&id=` | | An environment snapshot; see [Manager environment](#manager-environment). |
|
|
614
|
+
| POST | `/environment/refresh` | `{ scope?, cwd?, id? }` | `202` with that scope's snapshot. |
|
|
615
|
+
| GET | `/usage` | | `{ usage: Usage[] }`, one per account of every provider with a `usageSource`. |
|
|
616
|
+
| GET | `/model-stats` | | `{ retrievedAt, stale, error, stats, pool, providers, models, sessions }`: benchmarks for each provider's models, from OpenRouter's public model list. `stats` describes each benchmark; `providers[id]` is `{ featured, models }`; `models[id]` holds a model's name, context, price and, per benchmark, its `value`, `rank`, `place`, `tied`, `of`, `level` (0-100) and `tier` (S to D); `sessions[id]` is the model id a session's model matched, or null. |
|
|
617
|
+
| GET | `/news` | | `{ refreshedAt, refreshing, sources, items }`. `items` are the built-in feeds' recent items, newest first, each `{ id, title, url, discussion, summary, source, sourceId, category, publishedAt }` with `category` `news`, `releases` or `research`. `sources` lists each feed as `{ id, name, category, error, okAt }`. A `news.updated` event follows a refresh. |
|
|
618
|
+
| GET | `/yard-art` | | `{ yardArt }`: the Yard's art. `state` is `ready`, `missing`, `downloading` (with `received` bytes), `installing`, `failed` or `unavailable` (each with `error`), or `cancelled`; `size` is the pack's size in bytes. A checkout serves `web/yard/assets` and is always `ready`. `/yard/assets/` answers 404 until the art is `ready`. |
|
|
619
|
+
| POST | `/yard-art/download` | | `202 { yardArt }`: starts the one download of the art pack named by the package, unless it is running or installed. Progress and the outcome arrive as `yard-art.updated`. |
|
|
620
|
+
| POST | `/yard-art/cancel` | | `{ yardArt }`: stops the download for every client; the state becomes `cancelled`. |
|
|
621
|
+
| GET | `/changelog` | | `{ refreshing, okAt, error, releases }`: Agent Guild's own releases, newest first, each `{ version, url, publishedAt, sections: [{ title, changes }] }`, where `title` may be null and a change is a list of text runs. A `changelog.updated` event follows a refresh. |
|
|
622
|
+
| GET | `/providers/:id/history?account=&limit=&cwd=&q=` | | `{ history }`: a History object for one account (default `default`). |
|
|
623
|
+
| GET | `/providers/:id/history/detail?account=&id=&cursor=` | | `{ detail }`; see [Saved messages](#saved-messages). |
|
|
624
|
+
| GET | `/providers/:id/flags` | | `{ flags, error }`: the provider's `launchFlags` that its installed tool's `--help` lists, each `{ flag, value, placeholder, about, risky, example }`, where `value` is `required`, `optional` or `none`, `placeholder` and `about` are the tool's own, `risky` marks a flag that skips approvals, and `example` is a sample value or null. `error` says why the help could not be read. |
|
|
625
|
+
| GET | `/providers/:id/memory?account=&cwd=` | | `{ memory }`: a Memory object for one account and the working folder `cwd` (the home folder when empty). |
|
|
626
|
+
| GET | `/providers/:id/memory/file?account=&cwd=&scope=&path=` | | `{ file: { scope, path, bytes, modified, truncated, text } }`: the start of one listed file. |
|
|
627
|
+
| GET | `/providers/:id/instructions?account=&cwd=` | | `{ instructions }`: an Instruction files object for one account and the working folder `cwd` (the home folder when empty). |
|
|
628
|
+
| GET | `/providers/:id/instructions/file?account=&cwd=&index=&path=` | | `{ file: { index, scope, path, bytes, modified, truncated, text } }`: the start of file `index` of a freshly resolved list. 404 `instruction_file_gone` when that entry is no longer `path`. |
|
|
629
|
+
| GET | `/github` | | `{ github }`: a GitHub object. |
|
|
630
|
+
| POST | `/github/sign-in` | | `202 { github }`: starts a device-flow sign-in; `github.updated` events follow. |
|
|
631
|
+
| DELETE | `/github/sign-in` | | `{ github }`: cancels it. |
|
|
632
|
+
| DELETE | `/github/accounts/:id` | | `{ github }`: forgets the account's sign-in. |
|
|
633
|
+
| POST | `/github/accounts/:id/ssh` | | `{ account }`: sets up the account's SSH key on GitHub. A failure is reported in `account.ssh.error`. |
|
|
634
|
+
| GET | `/github/accounts/:id/repos?parent=&refresh=1` | | `{ repos: { accountId, fetchedAt, truncated, owners, parent, repos } }`: the account's repositories, most recently pushed first, each `{ fullName, owner, ownerType, name, private, fork, archived, description, language, pushedAt, url, target, local }`. `owners` are where a new repository can be created. With `parent`, `target` is `<parent>/<name>` and `local` is `absent`, `cloned` or `conflict`. |
|
|
635
|
+
| POST | `/github/accounts/:id/repos` | `{ owner, name, description?, private?, readme? }` | `201 { repo }`: creates a repository, private unless `private` is false, with a README unless `readme` is false. |
|
|
636
|
+
| GET | `/github/repos?refresh=1` | | `{ repos, truncated, errors, fetchedAt }`: every signed-in account's repositories in one list, each with `accountId` and `login`. An account that fails adds `{ accountId, login, code, message }` to `errors`. |
|
|
637
|
+
| GET | `/github/origin?cwd=` | | `{ folder, repo }`: `repo` is the lowercase `owner/name` of the GitHub origin of the Git work tree holding `cwd`, else null. |
|
|
638
|
+
| GET | `/github/accounts/:id/repos/:owner/:name/issues?state=` | | Issues (see [Repository views](#repository-views)). `state` is `open` (default), `closed` or `all`. |
|
|
639
|
+
| POST | `/github/accounts/:id/repos/:owner/:name/issues` | `{ title, body? }` | `201 { issue }`. |
|
|
640
|
+
| PATCH | `/github/accounts/:id/repos/:owner/:name/issues/:number` | `{ title?, body?, state? }` | `{ issue }`. `state` is `open` or `closed`. |
|
|
641
|
+
| GET | `/github/accounts/:id/repos/:owner/:name/actions` | | Workflow runs (see [Repository views](#repository-views)). |
|
|
642
|
+
| GET | `/github/accounts/:id/repos/:owner/:name/branches?page=` | | Branches (see [Repository views](#repository-views)). |
|
|
643
|
+
| GET | `/github/accounts/:id/repos/:owner/:name/pulls` | | Open pull requests (see [Repository views](#repository-views)). |
|
|
644
|
+
| GET | `/github/accounts/:id/repos/:owner/:name/activity?since=` | | The account's own recent work (see [Repository views](#repository-views)). 400 `bad_since` unless `since` is a time. |
|
|
645
|
+
| POST | `/github/clone` | `{ account, repo, parent }` | `201 { session }`: a session with `task` `clone` running `git clone` for `repo` (owner/name) into `<parent>/<name>`. |
|
|
646
|
+
| GET | `/sessions` | | `{ sessions: Session[] }` |
|
|
647
|
+
| POST | `/sessions` | `{ providerId, account?, shell?, cwd?, cols?, rows?, name?, args?, resume? }` | `201 { session }`. 429 `too_many_sessions` at the session limit. |
|
|
648
|
+
| GET | `/sessions/:id` | | `{ session }` |
|
|
649
|
+
| GET | `/sessions/:id/reply` | | `{ reply: { id, text, spoken } }`, or `{ reply: null }`: the whole text of the answer `session.reply` names, and `spoken`, the words the page's Read aloud speaks for it: a line per paragraph line, heading, list item and table row, each fenced code block as `Code omitted.` |
|
|
650
|
+
| PATCH | `/sessions/:id` | `{ name }` | `{ session }`. |
|
|
651
|
+
| POST | `/sessions/:id/stop` | | `{ session }`: ends the process. The session stays listed as exited. |
|
|
652
|
+
| POST | `/sessions/:id/reattach` | | `{ session }`: runs an exited tmux or herdr session's multiplexer client again, keeping the session's id and screen. 409 `multiplexer_session_gone` while `multiplexer.reattachable` is false. |
|
|
653
|
+
| DELETE | `/sessions/:id` | | `{ ok: true }`: ends the process if needed and removes the session. |
|
|
654
|
+
| POST | `/sessions/:id/agents` | Agent report | `{ agent }`, or `{ agent: null }`. |
|
|
655
|
+
| POST | `/sessions/:id/model` | `{ model, displayName? }` | `{ model }`. Sets the session's model with source `report`. |
|
|
656
|
+
| POST | `/sessions/:id/tool-session` | `{ toolSessionId, source? }` | `{ toolSessionId }`. Records the id the tool gave its own session. A new id with `source` `clear`, `startup` or `new` counts a reset. |
|
|
657
|
+
| POST | `/sessions/:id/context` | `{ compaction, key? }` | `{ context }`. Counts a compaction, `manual` or `auto`. A repeated `key`, or with none a report within 2 s of the last, is the same one. |
|
|
658
|
+
| POST | `/sessions/:id/reply` | `{ reply }` | `{ reply: { id } }`, or `{ reply: null }`. `reply` is the text of the answer that ended the turn, as Markdown, or null. |
|
|
659
|
+
| POST | `/sessions/:id/reporting` | | `{ reporting }`: the tool's hooks announce themselves. |
|
|
660
|
+
| POST | `/sessions/:id/docker` | `{ event, sessionId, agentName?, toolName?, toolUseId?, command?, model?, reply? }` | `{ ok }`. One Docker Agent hook event. |
|
|
661
|
+
| POST | `/sessions/:id/shells` | `{ shell, key \| task, match?, agentId?, persist?, endsWithAgent?, kind?, tasks? }` | `{ ok }`. A shell command the tool started, ended or put in the background. |
|
|
662
|
+
| POST | `/shutdown` | `{ force?, restart? }` | `202 { ok, running, restart }`: stops the manager and every session, detaching tmux and herdr sessions. 409 `sessions_running` (with `running`) while other sessions run; retry with `force: true` after the user confirms. With `restart`, a new manager takes over on the same port and token, running the version on disk. |
|
|
663
|
+
|
|
664
|
+
`cwd` defaults to the user's home folder, and a leading `~` is expanded.
|
|
665
|
+
`args` are appended to the provider's configured arguments. `resume` is an
|
|
666
|
+
id or name of one of the tool's own sessions; one that starts with `-` is
|
|
667
|
+
refused with 400 `bad_resume`. `account` defaults to `default`.
|
|
668
|
+
|
|
669
|
+
The `POST /sessions/:id/…` report routes (`agents`, `model`, `tool-session`,
|
|
670
|
+
`context`, `reply`, `reporting`, `docker` and `shells`) also accept the per-session
|
|
671
|
+
report token instead of the API token, in an `X-Agent-Guild-Report-Token`
|
|
672
|
+
header. The manager gives that token only to the processes inside that
|
|
673
|
+
session.
|
|
674
|
+
|
|
675
|
+
Request bodies are limited to 64 KiB, except `PUT /notes`,
|
|
676
|
+
`POST /sessions/:id/reply` and `POST /sessions/:id/docker`, which accept
|
|
677
|
+
1 MiB. Messages a client sends on a WebSocket are limited to 1 MiB.
|
|
678
|
+
|
|
679
|
+
## WebSockets
|
|
680
|
+
|
|
681
|
+
Messages in both directions are JSON text frames.
|
|
682
|
+
|
|
683
|
+
### `GET /api/v1/events`
|
|
684
|
+
|
|
685
|
+
This socket pushes changes to every session. A client sends nothing on it
|
|
686
|
+
but `{ type: "ping" }`, which the manager answers with `{ type: "pong" }`.
|
|
687
|
+
|
|
688
|
+
| Message | Meaning |
|
|
689
|
+
| --- | --- |
|
|
690
|
+
| `{ type: "hello", version, pid, platform, startedAt, launcher, folderOpener, remoteAccess, upgrade, yardArt, sessions, heartbeat, notesRevision, notesUnreadable }` | Sent first: the manager (as in `/info`), the full session list and the notepad's revision. |
|
|
691
|
+
| `{ type: "session.created", session }` | A session was started by any client. |
|
|
692
|
+
| `{ type: "session.updated", session }` | Status, activity, agents, name or size changed. |
|
|
693
|
+
| `{ type: "session.removed", sessionId }` | A session was removed. |
|
|
694
|
+
| `{ type: "providers.updated", providers, revision }` | The provider list changed. |
|
|
695
|
+
| `{ type: "environment.updated", environment }` | An environment check started or finished. |
|
|
696
|
+
| `{ type: "news.updated" }` | Fetch `/news` again. |
|
|
697
|
+
| `{ type: "yard-art.updated", yardArt }` | The Yard's art changed state or made download progress, as in `/yard-art`. |
|
|
698
|
+
| `{ type: "changelog.updated" }` | Fetch `/changelog` again. |
|
|
699
|
+
| `{ type: "github.updated" }` | Fetch `/github` again. |
|
|
700
|
+
| `{ type: "remote-access.updated" }` | Fetch `/remote-access` again. |
|
|
701
|
+
| `{ type: "sign-in.updated" }` | Fetch `/sign-in` again. |
|
|
702
|
+
| `{ type: "notes.updated", notes }` | The notepad was saved. `notes` is `{ revision, text }`. |
|
|
703
|
+
| `{ type: "manager.upgrade", upgrade }` | The manager's own version check changed. |
|
|
704
|
+
| `{ type: "manager.stopping", running, restart }` | A client asked the manager to stop; `running` sessions are being ended. `restart` is true when a new manager will take over. |
|
|
705
|
+
| `{ type: "manager.stopped", remaining, restart }` | The last event before the socket closes. `remaining` is how many sessions had not confirmed their exit. |
|
|
706
|
+
|
|
707
|
+
After a reconnect, treat `hello` as the new source of truth.
|
|
708
|
+
|
|
709
|
+
### `GET /api/v1/sessions/:id/terminal`
|
|
710
|
+
|
|
711
|
+
This socket is the interactive terminal for one session. Any number of
|
|
712
|
+
clients may attach to the same session. A client that may size the terminal
|
|
713
|
+
adds `page=<id>` to the URL, an id of 1 to 64 letters, digits, `-` or `_` that
|
|
714
|
+
names one page load. The terminal has one size: the page that last sent
|
|
715
|
+
`claim` owns it, and only that page's `resize` messages under its latest claim
|
|
716
|
+
change it. The size stays when the owner disconnects.
|
|
717
|
+
|
|
718
|
+
Server to client:
|
|
719
|
+
|
|
720
|
+
| Message | Meaning |
|
|
721
|
+
| --- | --- |
|
|
722
|
+
| `{ type: "snapshot", data, cols, rows, generation, owner, session }` | Always first. `owner` says whether this page owns the size, and `generation` counts claims. `data` is a VT escape sequence stream that redraws the current screen and scrollback. Reset the terminal and write it. |
|
|
723
|
+
| `{ type: "data", data }` | Terminal output, in order, directly after the snapshot. |
|
|
724
|
+
| `{ type: "resize", cols, rows, generation, owner }` | The size changed or was claimed, or this page's own `resize` was refused or changed nothing. |
|
|
725
|
+
| `{ type: "exit", exitCode, signal }` | The process ended. Also sent after the snapshot when attaching to an exited session. |
|
|
726
|
+
| `{ type: "removed" }` | The session was removed. The socket then closes with code 4410. |
|
|
727
|
+
| `{ type: "pong" }` | The answer to a client's `ping`. |
|
|
728
|
+
| `{ type: "error", code: "not_found", message }` | The session does not exist. The socket then closes with code 4404. |
|
|
729
|
+
|
|
730
|
+
Client to server:
|
|
731
|
+
|
|
732
|
+
| Message | Meaning |
|
|
733
|
+
| --- | --- |
|
|
734
|
+
| `{ type: "input", data }` | Keystrokes or pasted text, exactly as a terminal would send them. |
|
|
735
|
+
| `{ type: "claim", cols, rows }` | Take the size for this page and set it. Needs `page`. |
|
|
736
|
+
| `{ type: "resize", generation, cols, rows }` | Resize the terminal. Refused unless this page owns the size and `generation` is the latest. |
|
|
737
|
+
| `{ type: "ping" }` | Ask for a `pong`, to check that the link is alive. |
|
|
738
|
+
|
|
739
|
+
### Terminal queries: clients must not answer
|
|
740
|
+
|
|
741
|
+
Programs ask the terminal questions by writing escape sequences, for
|
|
742
|
+
example "where is the cursor?" (`CSI 6 n`). The manager answers these once
|
|
743
|
+
per session from its own copy of the screen, whether zero or many clients
|
|
744
|
+
are attached. A client that renders the terminal must therefore not answer
|
|
745
|
+
them itself, or the program receives duplicate replies as keyboard input.
|
|
746
|
+
A client built on xterm.js can copy `suppressQueryReplies` from `web/app.js`.
|
|
747
|
+
|
|
748
|
+
Close codes: `4404` means the session does not exist, `4410` means it was
|
|
749
|
+
removed, and `4008` means the client fell too far behind. After a `4008` or a
|
|
750
|
+
network drop, reconnect and the snapshot brings the client up to date.
|
|
751
|
+
|
|
752
|
+
## The page in a VS Code tab
|
|
753
|
+
|
|
754
|
+
The manager's page may be framed only by VS Code webviews: its
|
|
755
|
+
`Content-Security-Policy` has `frame-ancestors vscode-webview: vscode-file:`.
|
|
756
|
+
The frame's `src` is the page URL with `#token=<token>`, as for a browser.
|
|
757
|
+
Adding `&view=yard` or `&view=cards` opens that view and keeps it as the
|
|
758
|
+
user's choice, as Settings does. Adding `&layout=compact` instead shows the
|
|
759
|
+
Yard's scene alone, to fill a small view, and keeps nothing: `&skin=<id>` and
|
|
760
|
+
`&theme=light` or `&theme=dark` show that look, and opening a session asks
|
|
761
|
+
the host for the full page. A host with no full page adds `&expand=none`, and
|
|
762
|
+
the page then neither shows its controls that ask for one nor sends `expand`.
|
|
763
|
+
A host whose device can't run the Yard adds `&yard=none`: the page shows the
|
|
764
|
+
Cards, doesn't offer the Yard, ignores `&view=yard`, `&layout=compact` and
|
|
765
|
+
the `view` message's `"yard"`, and never loads the scene or downloads the
|
|
766
|
+
Yard's art.
|
|
767
|
+
A browser ignores all of these.
|
|
768
|
+
|
|
769
|
+
A framed page cannot open windows, so it posts messages to `window.parent`.
|
|
770
|
+
A host must accept a message only from its own frame and the manager's
|
|
771
|
+
origin, and open only `http:` and `https:` URLs.
|
|
772
|
+
|
|
773
|
+
| Message | Meaning |
|
|
774
|
+
| --- | --- |
|
|
775
|
+
| `{ source: "agent-guild", type: "ready", version }` | The page reached the manager. `version` is the manager's. |
|
|
776
|
+
| `{ source: "agent-guild", type: "open", url }` | The user followed a link; open `url` in the browser. |
|
|
777
|
+
| `{ source: "agent-guild", type: "look", skin, theme }` | The page's skin and theme, on load and whenever they change. A compact page does not send it. |
|
|
778
|
+
| `{ source: "agent-guild", type: "expand", session }` | A compact page asks for the full page, showing the terminal of `session` when it is present. |
|
|
779
|
+
|
|
780
|
+
A host may post these messages to the page. The page accepts them only from
|
|
781
|
+
`window.parent`. Send them once the frame has fired `load`; the page listens
|
|
782
|
+
from then on.
|
|
783
|
+
|
|
784
|
+
| Message | Meaning |
|
|
785
|
+
| --- | --- |
|
|
786
|
+
| `{ source: "agent-guild-vscode", type: "view", view }` | Show `view`, `"cards"` or `"yard"`, and keep it as the user's choice. An open terminal closes first; its sessions keep running. A page opened with `&yard=none` ignores `"yard"`. |
|
|
787
|
+
| `{ source: "agent-guild-vscode", type: "session", id }` | Open the terminal of session `id` once, as soon as the page knows that session. |
|
|
788
|
+
|
|
789
|
+
## The page in the Agent Guild app
|
|
790
|
+
|
|
791
|
+
The app for iPhone, iPad and Apple Vision Pro loads the page URL with
|
|
792
|
+
`#token=<token>` in a `WKWebView` and registers a script message handler
|
|
793
|
+
named `agentGuild`. When the page is not framed and that handler exists, the
|
|
794
|
+
page treats the app as its host: `&view=yard`, `&view=cards`,
|
|
795
|
+
`&layout=compact` and `&yard=none` work as for VS Code, and the page posts the same `ready` and `open` messages to
|
|
796
|
+
`window.webkit.messageHandlers.agentGuild` instead of `window.parent`.
|
|
797
|
+
Without the handler, the page behaves as in a browser.
|
|
798
|
+
|
|
799
|
+
The page's checks do not protect the app. A script message handler is
|
|
800
|
+
reachable from every frame, and the page embeds a YouTube frame, so the app
|
|
801
|
+
must:
|
|
802
|
+
|
|
803
|
+
- accept a message only when `WKScriptMessage.frameInfo.isMainFrame` is true
|
|
804
|
+
and the frame's `securityOrigin` is the manager's (CVE-2026-44698 is this
|
|
805
|
+
bug in Home Assistant's iOS app);
|
|
806
|
+
- accept only the messages in the table above, with a string `type` and the
|
|
807
|
+
fields it names;
|
|
808
|
+
- open only `http:` and `https:` URLs, outside the `WKWebView`, and keep the
|
|
809
|
+
`WKWebView` from navigating away from the manager's origin, redirects
|
|
810
|
+
included.
|
|
811
|
+
|
|
812
|
+
To show a view while the page is open, the app runs
|
|
813
|
+
`window.postMessage({ source: "agent-guild-app", type: "view", view }, location.origin)`
|
|
814
|
+
in the page with `evaluateJavaScript` once navigation has finished. The page
|
|
815
|
+
accepts it only from its own window at its own origin, with the behavior of
|
|
816
|
+
the VS Code message.
|
|
817
|
+
|
|
818
|
+
### Android
|
|
819
|
+
|
|
820
|
+
The Android app loads the same URL in a `WebView` and registers a web message
|
|
821
|
+
listener named `agentGuild` with AndroidX WebKit's
|
|
822
|
+
`WebViewCompat.addWebMessageListener`, whose allowed origin rules name only the
|
|
823
|
+
manager's origin. When the page is not framed, has no Apple handler, and
|
|
824
|
+
`window.agentGuild.postMessage` is a function, the page treats the app as its
|
|
825
|
+
host, as above. That object carries only strings, so the page posts each
|
|
826
|
+
message as JSON text: `JSON.stringify({ source: "agent-guild", type, ... })`.
|
|
827
|
+
|
|
828
|
+
The page's checks do not protect the app, so the app must:
|
|
829
|
+
|
|
830
|
+
- accept a message only when `isMainFrame` is true and `sourceOrigin` is
|
|
831
|
+
exactly the manager's origin;
|
|
832
|
+
- parse it as JSON and accept only the messages in the table above, with a
|
|
833
|
+
string `type` and the fields it names;
|
|
834
|
+
- open only `http:` and `https:` URLs, outside the `WebView`, and keep the
|
|
835
|
+
`WebView`'s main frame on the manager's origin: its first load, redirects,
|
|
836
|
+
new windows and loads after a renderer restart included.
|
|
837
|
+
|
|
838
|
+
The app shows a view the same way, with `evaluateJavascript` once
|
|
839
|
+
`onPageFinished` has run.
|
|
840
|
+
|
|
841
|
+
## The page on Meta Ray-Ban Display
|
|
842
|
+
|
|
843
|
+
`/glasses.html` is a 600 × 600 page for Meta's display glasses. It reads
|
|
844
|
+
`#token=<token>` as the page does, then makes only `GET /api/v1/sessions`
|
|
845
|
+
(every 5 seconds) and `GET /api/v1/sessions/:id/reply`, and speaks `spoken`.
|
|
846
|
+
It has no terminal and sends nothing, but the token it holds is the full API
|
|
847
|
+
token. **Settings → Meta glasses** shows a QR code of Meta's install link
|
|
848
|
+
for it, `fb-viewapp://web_app_deep_link?appName=Agent%20Guild&appUrl=<URL>`,
|
|
849
|
+
built from one of the manager's allowed HTTPS origins: the one the page is
|
|
850
|
+
open on, then the Tailscale address, then the other reverse proxy origins.
|