Skip to content

Commit fb56a51

Browse files
committed
fix: /wait unblocks on stopMonitoring, clarify email reply two-PUT and paragraph format
1 parent c9a030e commit fb56a51

2 files changed

Lines changed: 48 additions & 31 deletions

File tree

packages/server/src/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -762,8 +762,9 @@ app.get('/api/sessions/:id/wait', async (req, res) => {
762762
while (Date.now() - start < TIMEOUT_MS) {
763763
const session = getSession(req.params.id)
764764
if (!session) return res.status(404).json({ error: 'Session not found' })
765-
if (session.status === 'completed' || session.status === 'rewriting') {
766-
console.log(`[agentclick] /wait returning ${session.status} for ${session.id} (revision=${session.revision})`)
765+
const ps = session.pageStatus as Record<string, unknown> | undefined
766+
if (session.status === 'completed' || session.status === 'rewriting' || ps?.stopMonitoring === true) {
767+
console.log(`[agentclick] /wait returning ${session.status} for ${session.id} (revision=${session.revision}, stopMonitoring=${ps?.stopMonitoring ?? false})`)
767768
return res.json(session)
768769
}
769770
await new Promise(r => setTimeout(r, POLL_MS))

skills/clickui-email/SKILL.md

Lines changed: 45 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -170,9 +170,8 @@ else
170170
fi
171171
```
172172

173-
After each poll:
174-
- Inspect `status`, `result`, and `pageStatus`
175-
- If `pageStatus.stopMonitoring` is `true` → stop
173+
After each poll result:
174+
- If `pageStatus.stopMonitoring` is `true`**stop immediately** (the `/wait` endpoint unblocks on this)
176175
- If `status` is `"completed"` → stop
177176
- If `status` is `"rewriting"` → handle the request (see Rewrite / Update Rules below), then poll again
178177
- Otherwise → wait 1 second (`sleep 1` as a separate exec), then poll again
@@ -192,39 +191,55 @@ The UI is not only a final approval screen. The user may:
192191

193192
### Reply requests
194193

195-
If the result indicates `requestReplyDraft: true` for an email:
196-
- update the payload quickly so the UI can show that email as loading
197-
- generate the reply draft yourself as the agent — use the email body already present in the poll response payload (do not re-fetch from Gmail)
198-
- PUT the finished draft back into the same session
199-
- do not create a new session
194+
When the result has `requestReplyDraft: true`, follow this exact two-PUT sequence:
200195

201-
**Important:** The reply draft must be placed **on the email item itself**, not in `payload.draft`. The UI reads `replyState`, `replyDraft`, and `replyUnread` from the individual email object in the inbox array.
196+
**PUT 1 — mark as loading** (do this immediately so the UI shows a spinner):
202197

203-
Reply draft fields on the email item:
204-
```json
198+
```bash
199+
curl -s -X PUT "$AGENTCLICK_BASE/api/sessions/${SESSION_ID}/payload" \
200+
-H "Content-Type: application/json" \
201+
-d "{\"payload\":{\"inbox\":[{\"id\":\"${EMAIL_ID}\",\"replyState\":\"loading\"}]}}"
202+
```
203+
204+
**Generate the reply** — use the email body from the poll response payload. Do not re-fetch from Gmail.
205+
206+
**PUT 2 — deliver the draft** (`replyState: "ready"` with the full draft):
207+
208+
```bash
209+
cat > /tmp/reply_put.json <<JSON
205210
{
206-
"id": "gmail-message-id",
207-
"replyState": "ready",
208-
"replyUnread": false,
209-
"replyDraft": {
210-
"replyTo": "sender@example.com",
211-
"to": "sender@example.com",
212-
"subject": "Re: Original subject",
213-
"paragraphs": [
214-
{"id": "p1", "content": "Paragraph 1"},
215-
{"id": "p2", "content": "Paragraph 2"}
211+
"payload": {
212+
"inbox": [
213+
{
214+
"id": "TARGET_EMAIL_ID",
215+
"replyState": "ready",
216+
"replyUnread": true,
217+
"replyDraft": {
218+
"replyTo": "TARGET_EMAIL_ID",
219+
"to": "sender@example.com",
220+
"subject": "Re: Original subject",
221+
"paragraphs": [
222+
{"id": "p1", "content": "First paragraph text"},
223+
{"id": "p2", "content": "Second paragraph text"}
224+
]
225+
}
226+
}
216227
]
217228
}
218229
}
230+
JSON
231+
curl -s -X PUT "$AGENTCLICK_BASE/api/sessions/${SESSION_ID}/payload" \
232+
-H "Content-Type: application/json" \
233+
-d @/tmp/reply_put.json
219234
```
220235

221-
Valid `replyState` values: `"idle"` (default), `"loading"` (agent is generating), `"ready"` (draft available).
236+
> **CRITICAL — paragraph format:** `paragraphs` must be an array of `{"id": "p1", "content": "..."}` objects. Plain strings will break the UI. The `id` can be any unique string (e.g. `"p1"`, `"p2"`).
237+
238+
> **CRITICAL — placement:** The draft goes inside the email item in `inbox`, NOT in `payload.draft`. The UI reads `replyState` and `replyDraft` from the individual email object.
239+
240+
Valid `replyState` values: `"idle"` (default), `"loading"` (generating), `"ready"` (draft available).
222241

223-
The UI expects:
224-
- lazy reply generation
225-
- folded reply draft by default
226-
- the user can keep browsing while reply is loading
227-
- the email row can later show a ready state and unread dot when the draft arrives
242+
Why two PUTs: The server keeps the session in `rewriting` state as long as any email has `replyState: "loading"`. PUT 1 sets loading to lock the rewriting state open; PUT 2 delivers the finished draft. Without PUT 1, the session drops back to `pending` between calls and the second PUT is rejected.
228243

229244
### Read more requests
230245

@@ -286,8 +301,9 @@ curl -s -X PUT "$AGENTCLICK_BASE/api/sessions/${SESSION_ID}/payload" \
286301

287302
Rules:
288303
- Reuse the same `SESSION_ID` for the full interaction.
289-
- **Two-PUT pattern for replies:** first PUT a loading-state update (`replyState: "loading"`), then PUT the completed draft (`replyState: "ready"` with `replyDraft`). The server keeps the session in `rewriting` status as long as any email has `replyState: "loading"`, so the second PUT is accepted.
290-
- If PUT fails, fix it before continuing.
304+
- For reply drafts, always use the two-PUT sequence described in the Reply requests section above.
305+
- If PUT fails with `"Session is not in rewriting state"`, the loading email item was not set — re-trigger by calling `/complete` with `regenerate: true`, then repeat the two-PUT sequence.
306+
- If PUT fails for any other reason, fix it before continuing.
291307

292308
## Completion Rules
293309

0 commit comments

Comments
 (0)