|
1 | | -# Feature: Artifact Panel & Agent File Return |
| 1 | +# Feature: Agent File Return in Thread Messages |
2 | 2 |
|
3 | 3 | ## Problem Statement |
4 | 4 |
|
5 | | -The workspace thread UI currently has two gaps in file handling: |
| 5 | +When an agent uploads a file to the workspace (via `workspace_write_file` MCP tool), the file appears in the Files panel but is **not linked to the agent's chat response message**. The file upload and the response message are completely disconnected events. |
6 | 6 |
|
7 | | -1. **Agents don't return files in thread messages.** When an agent generates content (markdown documents, code files, HTML), it dumps everything into `payload.content` as inline text. The plumbing for file attachments exists end-to-end (SDK → backend → frontend), but nothing in the agent orchestration layer creates files and attaches them to responses. |
| 7 | +Additionally, the thread UI only treats images and HTML as previewable — markdown, code, and text files show as plain download links. |
8 | 8 |
|
9 | | -2. **No artifact/canvas side panel.** When a file attachment does exist on a message, clicking it navigates away from the chat (`setViewMode('files')`). There is no way to view a file alongside the conversation, unlike Claude Desktop's artifact panel. |
| 9 | +## What Was Implemented |
10 | 10 |
|
11 | | -## Current Architecture |
| 11 | +### 1. File tracking in base adapter (`sdk/src/openagents/adapters/base.py`) |
12 | 12 |
|
13 | | -### How thread messages work (end-to-end) |
| 13 | +- Added `_channel_uploaded_files` dict to track files uploaded during message handling |
| 14 | +- Added `track_uploaded_file(channel, file_info)` method |
| 15 | +- Modified `_send_response()` to pop tracked files and pass them as `attachments` to `client.send_message()` |
14 | 16 |
|
15 | | -``` |
16 | | -User sends message |
17 | | - → ChatInput → workspaceApi.sendMessage() |
18 | | - → POST /v1/events { type: "workspace.message.posted", source: "human:user" } |
19 | | - → Backend Pipeline: |
20 | | - 1. AuthMod (verify token) |
21 | | - 2. WorkspaceMod (route: parse @mentions, LLM router picks target agent) |
22 | | - 3. PersistenceMod (save EventRecord to DB) |
23 | | - → Agent polls GET /v1/events (every ~1s via AgentRunner._async_loop) |
24 | | - → react() → orchestrate_agent() → LLM + tools |
25 | | - → Agent posts response: WorkspaceClient.send_message() |
26 | | - → POST /v1/events { type: "workspace.message.posted", source: "openagents:agent-name" } |
27 | | - → Same pipeline → persisted to DB |
28 | | - → Frontend polls GET /v1/events?after={cursor} (every 2s) |
29 | | - → eventToMessage() → renders in chat via ChatMessage component |
30 | | -``` |
31 | | - |
32 | | -### How file uploads work |
33 | | - |
34 | | -**User upload (works today):** |
35 | | -1. Upload file → `POST /v1/files` (multipart) → returns `{ id, filename, contentType, size }` |
36 | | -2. Message event includes `payload.attachments: [{ fileId, filename, contentType, url }]` |
37 | | -3. Frontend `Attachments` component renders attachment in thread |
38 | | -4. URL regenerated from `fileId` at render time via `workspaceApi.getFileUrl(fileId)` |
39 | | - |
40 | | -**Agent upload (plumbing exists, unused):** |
41 | | -1. `POST /v1/files/base64` — JSON endpoint designed for agents (base64-encoded content) |
42 | | -2. Returns same `{ id, filename, contentType, size }` |
43 | | -3. `WorkspaceClient.send_message()` accepts `attachments` parameter — never called with it |
44 | | -4. Agent orchestrator puts all output in `payload.content` as plain text |
45 | | - |
46 | | -### Message attachment structure |
47 | | - |
48 | | -```typescript |
49 | | -// In event payload |
50 | | -payload: { |
51 | | - content: "Here's the report", |
52 | | - attachments: [ |
53 | | - { fileId: "uuid", filename: "report.md", contentType: "text/markdown", url: "..." } |
54 | | - ] |
55 | | -} |
56 | | - |
57 | | -// Extracted into message |
58 | | -message.metadata.attachments = [{ fileId, filename, contentType, url }] |
59 | | -``` |
60 | | - |
61 | | -### Thread UI attachment rendering (chat-message.tsx) |
62 | | - |
63 | | -- **Images**: inline thumbnail, click → navigates to files view |
64 | | -- **HTML files**: eye icon button, click → navigates to files view |
65 | | -- **Other files**: download link (`<a href={url}>`) |
66 | | -- **Markdown**: not treated as previewable (only images and HTML pass `isPreviewable()`) |
67 | | - |
68 | | -### Files view (file-preview.tsx) |
69 | | - |
70 | | -Full-page preview supporting HTML (iframe), images, markdown (MarkdownContent), text/code (pre block). Replaces the chat view entirely when `viewMode` switches to `'files'`. |
71 | | - |
72 | | -### Existing split panel precedent |
73 | | - |
74 | | -The browser view already supports a split mode: chat on left + browser on right (50/50). This is toggled via `splitBrowser` state in `LayoutContext` and persisted to localStorage. |
75 | | - |
76 | | -## Identified Gaps |
| 17 | +### 2. File collection in Claude adapter (`sdk/src/openagents/adapters/claude.py`) |
77 | 18 |
|
78 | | -| Gap | Current State | What's Needed | |
79 | | -|-----|--------------|---------------| |
80 | | -| Agent file creation | Agent can upload via `/v1/files/base64` but never does | Agent orchestrator should upload generated files and attach metadata to response events | |
81 | | -| Markdown not previewable in threads | `isPreviewable()` only returns true for images and HTML | Add markdown, code, SVG to previewable types | |
82 | | -| No side panel for artifacts | Clicking attachment navigates away from chat to files view | Add split artifact panel alongside chat (reuse browser split pattern) | |
83 | | -| No inline artifact detection | Long code/markdown blocks live inside message content | Detect fenced code blocks and offer "Open in panel" action | |
84 | | -| File preview is full-page only | `file-preview.tsx` replaces chat entirely | Need a panel-mode variant that coexists with chat | |
| 19 | +- Added `_collect_uploaded_files(channel)` method that queries `GET /v1/files` for files uploaded by this agent to this channel |
| 20 | +- Tracks already-attached file IDs in `_attached_file_ids` to avoid duplicates across responses |
| 21 | +- Called before sending the final response |
85 | 22 |
|
86 | | -## Implementation Plan |
| 23 | +### 3. Backend: filter support for file listing (`workspace/backend/app/routers/files.py`) |
87 | 24 |
|
88 | | -### Phase 1: Agent File Return (Backend/SDK) |
| 25 | +- Added optional `channel_name` and `uploaded_by` query params to `GET /v1/files` |
| 26 | +- Updated `workspace_client.list_files()` to pass these filters |
89 | 27 |
|
90 | | -**Goal:** When an agent generates substantial content, upload it as a file and attach to the response message. |
| 28 | +### 4. Frontend: expanded previewable types (`chat-message.tsx`) |
91 | 29 |
|
92 | | -Key files: |
93 | | -- `sdk/src/openagents/agents/runner.py` — agent orchestration loop |
94 | | -- `sdk/src/openagents/client/workspace_client.py` — `send_message()` and file upload methods |
| 30 | +- `isPreviewable()` now returns true for markdown, text, and common code file types |
| 31 | +- Affected files: `packages/go/` and `workspace/frontend/` (kept in sync) |
95 | 32 |
|
96 | | -Changes: |
97 | | -- In the agent orchestration layer, after LLM generates a response, detect substantial content blocks (markdown docs, code files, HTML) |
98 | | -- Upload via `POST /v1/files/base64` with `source: "openagents:{agent-name}"` |
99 | | -- Include attachment metadata in the `send_message()` call |
100 | | -- Keep the inline `content` as a summary/reference, not the full file |
| 33 | +## Architecture (unchanged) |
101 | 34 |
|
102 | | -### Phase 2: Artifact Side Panel (Frontend) |
103 | | - |
104 | | -**Goal:** View files alongside the conversation, like Claude Desktop's artifact panel. |
105 | | - |
106 | | -Key files: |
107 | | -- `workspace/frontend/components/layout/layout-context.tsx` — add `splitArtifact` state |
108 | | -- `workspace/frontend/components/layout/wrapper.tsx` — add split layout variant |
109 | | -- `workspace/frontend/components/chat/chat-message.tsx` — open attachments in panel instead of navigating away |
110 | | -- New: `workspace/frontend/components/artifacts/artifact-panel.tsx` — panel component |
111 | | - |
112 | | -Changes: |
113 | | -- Add `artifactPanel: { fileId: string } | null` state to LayoutContext |
114 | | -- Reuse the existing 50/50 split pattern from `splitBrowser` |
115 | | -- When clicking an attachment in a thread message, set `artifactPanel` instead of `setViewMode('files')` |
116 | | -- Panel renders file content (reuse rendering logic from `file-preview.tsx`) |
117 | | - |
118 | | -### Phase 3: Inline Artifact Detection |
119 | | - |
120 | | -**Goal:** Detect code fences in agent messages and offer "Open in panel" button. |
121 | | - |
122 | | -Key files: |
123 | | -- `workspace/frontend/components/chat/markdown-content.tsx` — add action buttons to code blocks |
124 | | -- `workspace/frontend/components/chat/chat-message.tsx` — coordinate with artifact panel |
125 | | - |
126 | | -Changes: |
127 | | -- In MarkdownContent, add an "Open in panel" button on large fenced code blocks |
128 | | -- Clicking creates a transient artifact (not persisted to files) and opens in side panel |
129 | | -- Optionally: "Save as file" action to persist to `/v1/files` |
130 | | - |
131 | | -### Phase 4: Rich Artifact Features (Future) |
132 | | - |
133 | | -- Edit-in-panel for markdown/code |
134 | | -- Version history (multiple iterations of same artifact) |
135 | | -- Mermaid diagram rendering |
136 | | -- Live-updating artifacts (agent streams, panel updates) |
| 35 | +``` |
| 36 | +Agent uses workspace_write_file MCP tool |
| 37 | + → POST /v1/files/base64 (uploads file) |
| 38 | + → File appears in Files panel |
| 39 | +
|
| 40 | +Agent finishes processing |
| 41 | + → _collect_uploaded_files() queries GET /v1/files?channel_name=X&uploaded_by=openagents:Y |
| 42 | + → Files tracked via track_uploaded_file() |
| 43 | + → _send_response() includes attachments in message event |
| 44 | + → Frontend renders attachments in thread with eye icon (previewable) |
| 45 | + → Click opens file preview (navigates to files view) |
| 46 | +``` |
137 | 47 |
|
138 | | -## Key Files Reference |
| 48 | +## Future Work |
139 | 49 |
|
140 | | -| File | Role | |
141 | | -|------|------| |
142 | | -| `workspace/frontend/components/chat/chat-message.tsx` | Thread message + attachment rendering | |
143 | | -| `workspace/frontend/components/chat/chat-input.tsx` | Message composition + file upload | |
144 | | -| `workspace/frontend/components/chat/chat-view.tsx` | Main chat view, handles send flow | |
145 | | -| `workspace/frontend/components/chat/markdown-content.tsx` | Markdown rendering in messages | |
146 | | -| `workspace/frontend/components/files/file-preview.tsx` | Full-page file preview | |
147 | | -| `workspace/frontend/components/layout/layout-context.tsx` | UI state (viewMode, splitBrowser) | |
148 | | -| `workspace/frontend/components/layout/wrapper.tsx` | Layout orchestrator | |
149 | | -| `workspace/frontend/hooks/use-polling.ts` | Message polling | |
150 | | -| `workspace/frontend/lib/api.ts` | API client (sendMessage, uploadFile, getFileUrl) | |
151 | | -| `workspace/frontend/lib/types.ts` | Types + eventToMessage conversion | |
152 | | -| `workspace/backend/app/routers/files.py` | File upload/download/list endpoints | |
153 | | -| `workspace/backend/app/routers/events.py` | Event posting + polling endpoints | |
154 | | -| `workspace/backend/app/mods/workspace_mod.py` | Message routing (LLM router) | |
155 | | -| `sdk/src/openagents/agents/runner.py` | Agent loop + orchestration | |
156 | | -| `sdk/src/openagents/client/workspace_client.py` | Agent-side API client | |
| 50 | +- **Artifact side panel**: View files alongside the conversation (canvas-like, reuse browser split pattern) |
| 51 | +- **Inline artifact detection**: Detect large code blocks in message content and offer "Open in panel" |
| 52 | +- **Orchestrator path**: `SimpleAutoAgent` still doesn't send response messages — separate issue |
0 commit comments