feat(agent): add authenticated durable uploads to backend and web

This commit is contained in:
2026-10-14 17:25:52 -07:00
parent 4cbab487d0
commit 8b125c13a5
16 changed files with 838 additions and 54 deletions
+37
View File
@@ -335,3 +335,40 @@ cursor. Missing agent configuration closes with 4503.
The proxy closes upstream sockets when browsers leave or Sybil shuts down.
History remains durable in Sybil Agent. Shared read position is separate from delivery:
update `GET`/`PUT /v1/agent/read-state` only when messages have actually been viewed.
### Agent attachment history, queued activity and replay
Uploads and attached sends use REST, as specified in [rest.md](./rest.md#agent-attachments-v1). WebSocket authentication, event types, stable message IDs, cursor semantics and raw tool rows are unchanged. Capability discovery is optional `GET /v1/agent` → `capabilities.attachments.version: 1`; absence disables new attachment sends. Upgrade/authenticate the native runtime and backend before advertising it.
Both `messages.messages[]` and `activity.activity.queuedMessages[]` carry attachment descriptors under **`metadata.agentAttachments`**, distinct from ordinary chat's inline `metadata.attachments`:
```json
{
"id": "inbound:sybil:client-stable-id",
"role": "user",
"content": "Read these notes",
"createdAt": "2026-10-14T18:00:00.000Z",
"metadata": {
"kind": "message",
"channel": "sybil",
"status": "queued",
"agentAttachments": [{
"id": "att_12345678-1234-4234-8234-123456789abc",
"kind": "text",
"filename": "notes.txt",
"mimeType": "text/plain",
"sizeBytes": 12,
"sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"workspacePath": "/data/workspace/uploads/att_12345678-1234-4234-8234-123456789abc/notes.txt"
}]
}
}
```
`workspacePath` is optional, authoritative runtime text for display/copy, never a download URL or send parameter. Only `attachments:[{id}]` goes into POST messages. Committed/replayed messages retain the same ordered descriptors and original caption; file-only content is `Attached: notes.txt.`. Internal model manifests/previews and native base64 are excluded from public user message content. Root/worker native inputs and raw authorized model endpoints may contain the real image blocks; native token auth must protect all of them.
Merge queued and committed messages by stable message ID. A matching ID alone does not confirm an attachment-bearing optimistic message: compare the ordered `metadata.agentAttachments[].id` against its local IDs as well. Retain descriptors, error and retry identity on a mismatch or uncertain receipt; show the error inline for touch clients. Matching canonical echoes can acknowledge delivery even if HTTP was lost. Preserve raw tool rows/cursors in client room state and existing transcript filtering. Old clients can ignore the additive metadata and still see a meaningful caption.
Retrieve bytes using authenticated `GET /v1/agent/attachments/:id/content`; fetch with an Authorization header and use/revoke browser blob URLs for previews. Never add credentials to URLs or use a public file route. Persist completed descriptors and pending upload IDs with the composer/outbox; navigation cannot silently turn an attached draft into a text-only send. Incomplete reloaded uploads require verified file reselection or explicit removal, per the REST contract.
Attached input admission and its request binding are atomic. Replay verifies the durable accepted payload, including the ordered attachment IDs, before acknowledging it. Queued attachment metadata survives restart even if placement did not run before the crash; normal queue recovery places the original input without duplicate charge or delivery. Worker preparation saturation retries through durable Reporter checkpoints; permanent admission failures appear through the existing worker-report path.