Summary
`ChatFilesPanel` (#3024, PR #3026) lists artifacts the agent has generated in the current chat thread, but the listing today is sourced from the in-memory redux `chatRuntime.artifactsByThread` map (rehydrated through the persisted ready-only filter). On a fresh machine, after a redux-persist purge, or when a thread is opened from another device, the panel shows zero files even if the on-disk `/artifacts/` ledger contains the original `.pptx`s. The producer events (`ArtifactReady` / `ArtifactFailed`) carry the originating `thread_id`, but the persisted `ArtifactMeta` (`src/openhuman/artifacts/types.rs`) does not — so the on-disk artifact has no concept of which thread produced it, and the panel can't repopulate from `ai_list_artifacts`.
Spotted by @graycyrus on PR #3026: #3026 (review) (Note 1, "Cold-load limitation").
Problem
Today: per-chat artifact panel only surfaces what the live redux slice happens to remember. Fresh redux + on-disk ledger = empty panel. Users hit this when:
- They open openhuman on a new device, sync threads, but artifacts don't follow.
- A persist-version bump (`persistedReducer`'s migration path) clears `artifactsByThread`.
- A long-running session triggers a redux-persist eviction.
- They re-open a thread that produced artifacts in a previous boot of the desktop app, on a host where persist storage was cleared (e.g. `pnpm dev:app` with a fresh OPENHUMAN_WORKSPACE).
The on-disk `artifacts//meta.json` is authoritative — it's just not addressable by thread.
Solution (optional)
Two coordinated changes:
-
Add `thread_id: Option` to `ArtifactMeta` in `src/openhuman/artifacts/types.rs`. Populate at `create_artifact` time from the same `APPROVAL_CHAT_CONTEXT` task-local that drives `finalize_artifact`'s `thread_id` extraction (`store.rs::current_chat_context`). `None` for CLI / cron / sub-agent paths — same convention as the existing `thread_id` on `DomainEvent::ArtifactReady`.
-
Add a `thread_id: Option` filter to `ai_list_artifacts` (`src/openhuman/artifacts/schemas.rs`). When provided, the handler walks the on-disk ledger and returns only entries whose `meta.thread_id` matches. `ChatFilesPanel` calls this on mount keyed on the current thread; results merge into `artifactsByThread[threadId]` so a fresh redux slice gets repopulated from disk.
Migration: existing `meta.json` files won't have `thread_id`. `serde(default)` on the field handles legacy reads — they show as `None` and behave like sub-agent-produced artifacts (not surfaced in any thread's panel). A follow-up could backfill from the `audit_log` if needed, but isn't blocking.
Acceptance criteria
Related
Summary
`ChatFilesPanel` (#3024, PR #3026) lists artifacts the agent has generated in the current chat thread, but the listing today is sourced from the in-memory redux `chatRuntime.artifactsByThread` map (rehydrated through the persisted ready-only filter). On a fresh machine, after a redux-persist purge, or when a thread is opened from another device, the panel shows zero files even if the on-disk `/artifacts/` ledger contains the original `.pptx`s. The producer events (`ArtifactReady` / `ArtifactFailed`) carry the originating `thread_id`, but the persisted `ArtifactMeta` (`src/openhuman/artifacts/types.rs`) does not — so the on-disk artifact has no concept of which thread produced it, and the panel can't repopulate from `ai_list_artifacts`.
Spotted by @graycyrus on PR #3026: #3026 (review) (Note 1, "Cold-load limitation").
Problem
Today: per-chat artifact panel only surfaces what the live redux slice happens to remember. Fresh redux + on-disk ledger = empty panel. Users hit this when:
The on-disk `artifacts//meta.json` is authoritative — it's just not addressable by thread.
Solution (optional)
Two coordinated changes:
Add `thread_id: Option` to `ArtifactMeta` in `src/openhuman/artifacts/types.rs`. Populate at `create_artifact` time from the same `APPROVAL_CHAT_CONTEXT` task-local that drives `finalize_artifact`'s `thread_id` extraction (`store.rs::current_chat_context`). `None` for CLI / cron / sub-agent paths — same convention as the existing `thread_id` on `DomainEvent::ArtifactReady`.
Add a `thread_id: Option` filter to `ai_list_artifacts` (`src/openhuman/artifacts/schemas.rs`). When provided, the handler walks the on-disk ledger and returns only entries whose `meta.thread_id` matches. `ChatFilesPanel` calls this on mount keyed on the current thread; results merge into `artifactsByThread[threadId]` so a fresh redux slice gets repopulated from disk.
Migration: existing `meta.json` files won't have `thread_id`. `serde(default)` on the field handles legacy reads — they show as `None` and behave like sub-agent-produced artifacts (not surfaced in any thread's panel). A follow-up could backfill from the `audit_log` if needed, but isn't blocking.
Acceptance criteria
Related