Skip to content

ai_list_artifacts: thread_id filter + ArtifactMeta.thread_id field #3226

Description

@oxoxDev

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:

  1. 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`.

  2. 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

  • `ArtifactMeta.thread_id` populated at create-time — `finalize_artifact` doesn't need to fill it because `create_artifact` writes the snapshot first.
  • `ai_list_artifacts` accepts `thread_id` filter — match-on-equality; absent filter returns all artifacts (current behaviour).
  • `ChatFilesPanel` calls `ai_list_artifacts` on mount keyed on the current thread; results merge into the redux slice.
  • Legacy `meta.json` files load with `thread_id = None` (via `#[serde(default)]`) and are filtered out of thread-scoped listings.
  • Tests: `store.rs` round-trip with thread_id; `schemas.rs` handler with + without filter; vitest spec for `ChatFilesPanel` repopulating from a cold redux slice.
  • Diff coverage ≥ 80%.

Related

Metadata

Metadata

Assignees

Labels

taskWork item that is not primarily a bug or a feature.

Type

No type
No fields configured for issues without a type.

Projects

Status
Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions