feat: add AI agent workspace (#3062)

* chore(ai): remove local AI store now that providers live server-side

The Zustand-based useAIStore has been replaced by the server-side
aiProviders oRPC router (encrypted credentials persisted in DB).
Delete the dead store + tests, drop the ./store export, and remove
zustand/immer deps which are no longer referenced anywhere in
packages/ai/src/.

* feat(agent): archive/delete actions and read-only state for agent threads

- Backend: mark archived threads as read-only in threads.get and reject
  messages.send with CONFLICT when the thread is archived.
- Frontend: render archived threads in the sidebar with muted styling and
  an Archived badge; add a per-thread dropdown menu in the chat header
  with Archive (non-destructive) and Delete (with confirmation); show a
  read-only banner above the message list that disambiguates archived
  vs. missing-resource causes; suppress the Retry and Stop buttons in
  read-only mode.
- Tests: new packages/api/src/services/agent.test.ts covering the
  archived-thread isReadOnly flag and the archived-thread send refusal.

* fix(agent): abort run on archive and verify ownership before deleting thread

- threads.archive: before flipping status, abort any in-flight run controller
  and clear the active-run state on the thread; cleanup failures are logged
  but do not block the status update.
- threads.delete: assert thread ownership via getThread before destructive
  work so an authenticated user cannot wipe another user's attachment rows
  by passing a foreign threadId.

Adds focused tests for both behaviors.

* feat(agent): display patch diffs and surface revert conflicts

Render apply_resume_patch tool messages with a status-aware card (applied/
reverted/conflicted), expandable operation list, and a Revert button that
correctly handles RESUME_VERSION_CONFLICT responses. Adds unit tests for
the inverse-patch builder and the agentService.actions.revert flow.

* chore(agent): remove out-of-scope attachment tests accidentally added in Task 6

The Task 6 commit (73ef1acca) accidentally re-introduced three attachment-
related tests that belong to a separate task:

- `buildAttachmentModelParts > converts text, image, supported binary, and
  unsupported attachments into model parts`
- `agentService.messages.send > persists the user message with file UI parts
  and links selected attachments to it` (was failing — the `ToolLoopAgent`
  mock is not callable as a constructor)
- `agentService.messages.send > rejects attachments that are missing, foreign,
  or already linked before persisting a message`

These were likely re-added during a stash recovery and were not requested
for Task 6, whose scope was limited to the `agentService.actions.revert`
flow. Remove them along with the helpers/fixtures (`buildAttachment`,
`buildActiveThread`, `selectWhereResult`, `selectOrderByResult`) that they
were the only consumers of. `selectLimitResult` is preserved because it is
used by the revert tests.

* chore(agent): configure runtime dependencies

* feat(db): add agent workspace schema

* feat(api): add agent backend services

* feat(web): add agent workspace UI

* chore(agent): remove legacy builder assistant

* test(agent): make agent stream mocks constructible

* chore(web): remove unused resume replacement hook

* feat(api): add unsafe AI base URL flag

* chore(dev): expose local services in compose

* fix(web): normalize resume preview gaps

* feat(api): improve agent tool handling

* feat(web): polish agent workspace UI

* chore: update dependencies

* fix(api,web): address PR review feedback for agent workspace

Security/correctness:
- Restrict AI provider URLs to http/https even in unsafe mode
- Stop exposing Redis on host network by default
- Make .env.local optional and drop app profile in compose.dev.yml
- Store agent attachments with private ACL on S3
- Reset provider test status when provider/model/baseURL changes
- Decouple non-agent AI endpoints from REDIS_URL requirement
- Fix JSON Patch add inverse for existing object members
- Wrap resume patch + agent action insert in db transaction
- Validate partialMessage at runtime and rate-limit attachment uploads
- Add unique index on agent_messages (thread_id, sequence)

UX/bugs:
- Mark agent thread route as ssr: false and guard SSE chunk parsing
- Show config-specific banner only on known configuration error
- Gate AI provider checks behind loading state in resume import
- Fix relative-time formatter blank gap between 45-59 seconds
- Clarify thread delete confirmation message

Polish:
- Raise ENCRYPTION_SECRET minimum to 32 characters
- Bucket AI rate limits by resumeId/threadId/messageId
- Trim form values before submitting AI provider config
- Use single key identifier and nullish-coalesce baseURL display

* fix: address ai agent review feedback

* fix: preserve mobile agent chat state

* docs: add ai agent workspace guides

* feat: introduce design system for Reactive Resume
This commit is contained in:
Amruth Pillai
2026-05-14 15:00:04 +02:00
committed by GitHub
parent 22c60c64b6
commit 6d8d8f6e55
115 changed files with 15623 additions and 2123 deletions
+30 -6
View File
@@ -90,7 +90,6 @@ OAUTH_SCOPES=""
# BETTER_AUTH_URL="https://auth.example.com"
# BETTER_AUTH_SECRET=""
# --- Email (optional) ---
# If all keys are disabled, the app logs the email to be sent to the console instead.
SMTP_HOST=""
@@ -112,10 +111,21 @@ S3_BUCKET=""
# Set to "false" for virtual-hosted-style URLs (https://bucket.endpoint), common with AWS S3, Cloudflare R2, etc.
S3_FORCE_PATH_STYLE="false"
# --- AI features (optional) ---
# ENCRYPTION_SECRET is required for saved AI providers. REDIS_URL is also required for the AI Agent workspace.
# The rest of Reactive Resume can run without these.
REDIS_URL=""
# Generated using `openssl rand -hex 32`
ENCRYPTION_SECRET=""
# Optional fallback for URL extraction. Not required for normal agent operation.
CLOUDFLARE_ACCOUNT_ID=""
CLOUDFLARE_API_TOKEN=""
# --- Feature Flags ---
FLAG_DISABLE_SIGNUPS="false"
FLAG_DISABLE_EMAIL_AUTH="false"
FLAG_DISABLE_IMAGE_PROCESSING="false"
# Allows unsafe/private/non-public AI provider base URLs. Keep false unless this is a trusted self-hosted deployment.
FLAG_ALLOW_UNSAFE_AI_BASE_URL="false"
```
@@ -251,6 +261,9 @@ docker compose logs -f reactive-resume
<li>
S3 storage (<code>S3_&#42;</code>)
</li>
<li>
AI providers and AI Agent workspace (<code>ENCRYPTION_SECRET</code>, <code>REDIS_URL</code>)
</li>
<li>
Feature flags (<code>FLAG_&#42;</code>)
</li>
@@ -326,17 +339,28 @@ openssl rand -hex 32
- **`LOCAL_STORAGE_PATH`** (optional): Overrides the local data directory. Defaults to `/app/data` in the official Docker image and `<workspace>/data` in development. The container validates this path is writable at startup and refuses to start otherwise.
- **Rootless Docker**: `/app/data` remains the container path. Prefer the named volume from the example Compose file, or make sure a bind-mounted host directory is writable by the container's `node` user mapping.
- **S3/S3-compatible**: Configure `S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY`, `S3_REGION`, `S3_ENDPOINT`, and `S3_BUCKET`.
- **Agent attachments/private objects**: The AI Agent workspace requires S3-compatible storage for private objects. Local storage rejects private objects.
- **`S3_FORCE_PATH_STYLE`** controls bucket addressing (defaults to `"false"`):
- `"true"` for path-style URLs (`https://endpoint/bucket`) common with MinIO/SeaweedFS.
- `"false"` for virtual-hosted-style URLs (`https://bucket.endpoint`) common with AWS S3 / Cloudflare R2.
</Accordion>
<Accordion title="AI features (optional)">
Saved AI provider management is usable only when **`ENCRYPTION_SECRET`** is configured. The AI Agent workspace also requires **`REDIS_URL`**. The rest of Reactive Resume can run without them.
- **`REDIS_URL`**: Redis connection string used by the AI Agent workspace.
- **`ENCRYPTION_SECRET`**: Secret used to encrypt saved AI provider credentials. Generate with `openssl rand -hex 32`.
- **`CLOUDFLARE_ACCOUNT_ID`** / **`CLOUDFLARE_API_TOKEN`** (optional): Enables the Cloudflare URL extraction fallback. Cloudflare is not required for normal agent operation.
If you use the Postgres-only Compose example above and want the AI Agent workspace, add a Redis service or use managed Redis, then set `REDIS_URL`.
</Accordion>
<Accordion title="Feature Flags">
- **`FLAG_DISABLE_SIGNUPS`**: Disables new signups (web app and server). Useful for private instances.
- **`FLAG_DISABLE_EMAIL_AUTH`**: Disables email/password login entirely. Also disables email verification, forgot password, and reset password flows. Users can still sign up via social auth (Google/GitHub/LinkedIn/Custom OAuth), unless FLAG_DISABLE_SIGNUPS is also set to true. Useful when only SSO is required.
- **`FLAG_DISABLE_IMAGE_PROCESSING`**: Disables image processing. This is useful if you are using a machine with limited resources, like a Raspberry Pi.
- **`FLAG_ALLOW_UNSAFE_AI_BASE_URL`**: Allows AI providers to be configured with `http://` URLs and private/loopback addresses (e.g. a local Ollama instance at `http://192.168.1.10:11434`). **Warning: enabling this on a multi-tenant deployment is an SSRF risk.** Only enable on a trusted, single-tenant self-hosted instance.
</Accordion>
- **`FLAG_DISABLE_SIGNUPS`**: Disables new signups (web app and server). Useful for private instances.
- **`FLAG_DISABLE_EMAIL_AUTH`**: Disables email/password login entirely. Also disables email verification, forgot password, and reset password flows. Users can still sign up via social auth (Google/GitHub/LinkedIn/Custom OAuth), unless FLAG_DISABLE_SIGNUPS is also set to true. Useful when only SSO is required.
- **`FLAG_DISABLE_IMAGE_PROCESSING`**: Disables image processing. This is useful if you are using a machine with limited resources, like a Raspberry Pi.
- **`FLAG_ALLOW_UNSAFE_AI_BASE_URL`**: Allows AI providers to be configured with unsafe, private, or non-public base URLs, including `http://` and private/loopback addresses (for example, a local Ollama instance at `http://192.168.1.10:11434`). Public HTTPS provider URLs remain the safe default. **Warning: enabling this on a multi-tenant deployment is an SSRF risk.** Only enable on trusted, self-hosted deployments.
</Accordion>
</AccordionGroup>
## Updating your installation