Gateway: per-watch notification switches, model list for new threads, and Ask before acting
Gateway / check (push) Successful in 9s
Gateway / image (push) Successful in 21s

This commit is contained in:
gib committed 2026-11-02 12:02:53 -05:00
1 parent a5222e14e8
commit 3cc7e40852
14 files changed
+472 -20

No files matched your search

+40 -6
View File
@@ -112,7 +112,7 @@ thread". For questions (`kind: "user_input"`), `options` is empty and `questions
```
GET /v1/threads/:hostId/:threadId
200 { "item": FeedItem, "messages": [Message], "canStop": true }
200 { "item": FeedItem, "messages": [Message], "canStop": true, "askFirst": false }
Message { "id": "...", "role": "assistant", "text": "...", "createdAt": "..." }
```
@@ -149,6 +149,16 @@ POST /v1/threads/:hostId/:threadId/settle
Settles a thread, as in T3. A settled thread that finished or failed leaves the feed.
```
POST /v1/threads/:hostId/:threadId/mode
{ "askFirst": true }
202
```
Turns Ask before acting on or off for a thread. On sets T3's `approval-required` mode. Off sets the
host's default mode, or `full-access` when that default is also `approval-required`. `askFirst` in
the thread detail says which mode the thread is in.
```
GET /v1/recent
@@ -188,19 +198,28 @@ GET /v1/summary
GET /v1/projects
200 { "hosts": [{ "id": "...", "label": "Mac mini", "status": "online", "error": null,
"projects": [{ "id": "...", "title": "wristcode", "model": "claude-opus-5-5" }] }] }
"projects": [{ "id": "...", "title": "wristcode", "model": "claude-opus-5-5", "instanceId": "claudeAgent" }],
"providers": [AgentProvider] }] }
AgentProvider { "instanceId": "claudeAgent", "name": "Claude", "canAskFirst": true,
"models": [{ "slug": "claude-opus-5-5", "name": "Claude Opus 5.5" }] }
```
A project with `"model": null` has no default model, so the watch should not offer it.
`model` and `instanceId` are the project's default, or null when it has none. `providers` lists the
providers that can start a thread now, without legacy models. `canAskFirst` is false when a provider
cannot run in `approval-required` mode.
```
POST /v1/threads
{ "hostId": "...", "projectId": "...", "text": "Add a complication for the waiting count" }
{ "hostId": "...", "projectId": "...", "text": "Add a complication for the waiting count",
"model": { "instanceId": "claudeAgent", "slug": "claude-sonnet-5-5" }, "askFirst": true }
201 { "hostId": "...", "threadId": "..." }
```
The new thread uses the project's default model, or the host's, and the host's default runtime mode.
`model` and `askFirst` are optional. Without `model`, the thread uses the project's default model or
the host's, with its options, such as reasoning effort. A picked model the host does not offer is a
400. `askFirst` picks the mode the same way as the mode route above.
## Notifications
@@ -218,6 +237,20 @@ its body starts with "Subagent <title>:", and its `hostId` and `threadId` are th
| `host` | A host's T3 token expires within 7 days, or was rejected | none |
| `test` | The watch asked for a test push | none |
Each watch picks which pushes it gets. The status route returns its choices, and this route changes
them:
```
PUT /v1/device/notifications
{ "subagents": false }
200 { "needsMe": true, "finished": true, "failed": true, "subagents": false }
```
Send any of the four. The ones left out keep their value, and all four start on. `needsMe` covers
`approval` and `input` pushes from your own threads, and `subagents` covers them from subagents.
`finished` and `failed` match their categories. `host` and `test` pushes always go out.
## Status
Everything the settings screens show: each host and its routes, whether the gateway can push, and
@@ -229,7 +262,8 @@ GET /v1/status
200 {
"hosts": [HostStatusDetail],
"gateway": { "startedAt": "...", "pushConfigured": true, "lastPush": PushResult | null },
"device": { "name": "Apple Watch", "pairedAt": "...", "pushRegistered": true, "apnsEnvironment": "production" }
"device": { "name": "Apple Watch", "pairedAt": "...", "pushRegistered": true, "apnsEnvironment": "production",
"notifications": NotificationPrefs }
}
HostStatusDetail {