93 Commits

Author SHA1 Message Date
Zeren Wang
a58ab484a6
feat(projects): Projects MVP Phase 2 — instructions, document shelf, promotion, trash (#5443)
* feat(projects): Projects MVP Phase 2 — instructions, document shelf, promotion, trash

Implements docs/superpowers/specs/2026-09-12-projects-mvp-phase2-design.md
(issue #5160, tracker #5129) in the slice order of the spec's §16.

Slices:
- A: ProjectsConfig + write-time 422 UTF-8 byte cap; PROJECT_CONTEXT_KEY
  admission pinning (both server-owned sets + worker hoist); latest-only
  request-scoped <project> block via DynamicContextMiddleware
  wrap_model_call/awrap_model_call (idempotent reassembly, reserved ID
  prefix + marker + provenance, never persisted); journal audit
  fingerprints; Instructions tab.
- B: ProjectDocumentRow + migration 0023; ProjectDocumentRepository with
  locked check-and-set; hash-qualified immutable shelf storage with
  Paths helpers; upload/list/content/delete-to-trash routes; project
  delete trashes the shelf in-transaction; request-scoped bounded
  <documents> index with honest count/shown + actionable overflow note;
  list_project_documents/read_project_document tools registered only on
  pinned runs; PAT allowlist + drift guards; blocking-IO anchors.
- C: shared thread-upload ingestion service (uploads router refactored to
  parity); POST from-thread with provenance; attach-to-thread with
  lock-staged copy (archived source allowed); read-only thread-files
  view with per-group truncation reporting.
- D: restore (restored/merged/not_found/no_target/content_missing; no
  file moves), purge (continuous row lock across unlink/delete/commit,
  retryable on FS errors), retention sweep (lazy + startup, 24h orphan
  guard, row-side reconciliation never deletes).
- E: Documents tab (shelf + conversation-files browser, provenance,
  archived banner, content-missing rows), /workspace/trash route,
  sidebar entry, composer attach handoff, i18n (en-US/zh-CN), e2e mocks
  + specs.

Review hardening folded in (10 rounds, all with tests):
- force active shelf content (HTML/XML family) to download; nosniff on
  artifact + content responses; unified unsandboxed-iframe PDF preview
  (fixes the pre-existing Chromium sandbox blank in the artifact viewer)
- scope document trash to the URL project under the document lock
- atomic no-overwrite filename reservation for ALL ingestion (seeded
  claims + os.link commit with suffix retry; same-name re-upload now
  unique-names instead of replacing); hidden staging only, no visible
  placeholders; lease cleanup on setup failure
- serialize conversion under the document lock with post-lock active
  revalidation; drain locked filesystem work on cancellation; preserve
  bytes when an insert's commit state is uncertain (including trashed
  rows)
- original-integrity checks before serving text or cached conversions;
  content_missing surfaced in list responses (UI reads the flag, no
  409-probe); downloads always serve original bytes
- bounded streaming document reads with cached char counts; shelf limits
  declared in middleware release identity
- thread-root confinement for from-thread sources; config fallback
  rejects fractional/infinite values; composer counts staged
  attachments; pending attachments persist until submission or removal;
  in-flight instruction/rename edits survive save refetches; shelf and
  trash pagination; conversation-file and thread-files pages stay
  subscribed to refetches

Docs: README/README_zh, backend API.md/ARCHITECTURE.md, AGENTS.md
contracts, config.example.yaml projects block.

Review follow-ups (head b4807477 → this revision):
- The trash retention sweep is split so repeated lazy triggers stay
  bounded: the indexed expiry purge still runs on every trigger
  (GET /api/trash/documents, POST /api/trash/purge) while the
  O(all rows + all files) reconciliation is throttled to one run per
  user per 15 minutes (process-local, per-user window). The startup
  sweep now runs as a background task instead of blocking gateway
  readiness, and shutdown awaits it (bounded).
- The export scrub (stripInternalMarkers) is fence- and indentation-aware
  like the render path, so a pasted, fenced <project>/<documents> snippet
  survives markdown export while real injected blocks (never fenced) are
  still removed. Fence regexes moved to a dependency-free leaf module to
  avoid the messages↔streamdown import cycle.
- The artifact viewer's PDF iframe no longer carries an added title
  attribute (the upstream e2e contract locates it via :not([title])), and
  the upstream artifact-preview spec now pins the new contract: PDFs
  render unsandboxed, images keep sandbox="".

* fix(projects): round-2 review — cancel an overrun trash sweep, restore the PDF frame title

- Shutdown cancelled only the shield around the background startup sweep,
  so an all-users reconciliation that outlived the 5s budget kept walking
  rows and files while the document repo and DB engine were disposed
  underneath it. The wait now lives in `_shutdown_startup_trash_sweep`,
  which cancels the task and drains it before worker exit: the shield
  keeps the wait bounded, the cancel makes it final (CancelledError lands
  at the sweep's next await, and `_run_startup_trash_sweep` only catches
  `Exception`, so nothing swallows it).
- The browser-preview iframe lost `title={getFileName(filepath)}` in the
  previous fix round, leaving the PDF frame without an accessible name
  while its siblings keep theirs. Restore it (WCAG frame titles), assert
  it in the DOM test, and anchor the e2e on `iframe[title="report.pdf"]`
  instead of `iframe:not([title])`.

* fix(projects): round-3 review — report the sweep's late finish, not a phantom cancel

`Task.cancel()` returns False when the sweep already finished inside the
window between the deadline firing and the cancel, so the shutdown log
claimed a cancellation that never happened. Branch on that outcome: the
warning stays for a real cancel, a late finish is logged at info, and both
paths still reap the task before worker exit.

* fix(projects): round-4 review — make Empty trash delete what it confirms

`POST /api/trash/purge` only ran the retention sweep, and the sweep's
candidate selection is age-gated, so a freshly trashed document survived
"Empty trash" even though the confirmation promises that every listed
document is permanently deleted. With one trashed row the route answered
`{"purged": 0}` and left it in place; `GET /api/trash/documents` sweeps
expired rows before listing, so the visible rows were normally ineligible
for the action by construction.

Empty trash now drives `purge_all_trashed`: the caller's trashed rows
(`list_all_trashed`, no age filter) each go through the same guarded,
row-locked `purge` as the single-document delete — bytes first, then the
row, in one transaction — so a row restored mid-flight is skipped instead of
force-deleted, and an unlink failure rolls that row back and answers 500 with
a retryable message. Retention expiry stays where it was: the sweep's
`purge_candidates` is now the only age-gated selection, and the lazy
retention sweep still runs on the listing and at startup.

Tests: the router suite replaces the retention-gated expectation with the
reviewer's repro (fresh row purged, bytes unlinked, shelf and other users'
trash untouched, a failing unlink stays retryable and 500); a blocking-I/O
anchor drives the new entry point through the offload; the mocked e2e covers
the action end to end; a new real-backend spec performs it against the real
gateway and re-reads `GET /api/trash/documents`. README, API, ARCHITECTURE
and the phase-2 design docs (en+zh) state the age-independent contract.
2026-09-16 18:46:18 +08:00
Ryker_Feng
9fda432ba1
feat(artifacts): preview CSV and TSV files as bounded tables (#5284)
* feat(artifacts): preview CSV and TSV files as bounded tables

* chore: keep preview screenshots out of the PR file diff

* fix(artifacts): detect record newlines outside quoted fields

* test(auth): include project permissions in me contract expectations
2026-09-08 19:11:11 +08:00
Ryker_Feng
8d8ca506ba
feat(artifacts): download run files as zip (#5117)
* feat(artifacts): download run files as zip

* fix(artifacts): address archive review feedback

* fix(artifacts): gate unavailable archive downloads

* fix(artifacts): verify archive availability

* fix(artifacts): harden archive consistency

* fix(artifacts): reject archive path aliases
2026-09-01 22:01:21 +08:00
Nan Gao
73e3699347
feat(frontend): render markdown artifacts in the "open in new window" view (#5056)
* feat(frontend): render markdown artifacts in the new window

The artifacts panel's "open in new window" action handed the browser the
raw Gateway response. For markdown that is a `text/markdown` body the
browser can only show as source, so the new window was a text dump rather
than a reader.

Route markdown artifacts to a new `/artifacts/view` page that renders them
with the same components the panel uses (SafeStreamdown + the artifact
rehype chain + citation links/panel), including the truncated-preview
banner and its "load full file" action. Everything else keeps the raw
Gateway URL — notably HTML/SVG, which the Gateway deliberately serves as a
download so active content never executes in the application origin.

- `core/artifacts/viewer.ts` centralizes which stored artifacts are
  markdown (`.skill` archives included, since they hold a SKILL.md), so
  the panel and the viewer route cannot drift.
- `ArtifactFilePreview` and its siblings move out of
  `artifact-file-detail.tsx` into `artifact-file-preview.tsx`; otherwise
  the standalone route would pull the CodeMirror editor into its bundle.
- The window title comes from the route's `generateMetadata`, not
  `document.title`, which the App Router overwrites after hydration.
- The viewer reads content through `useStandaloneArtifactContent`, which
  shares `useArtifactContent`'s query key but not its `useThread`
  dependency, since a detached window has no thread context.

Claude-Session: https://claude.ai/code/session_013AiCrC5SBc3HdFYNxsp1EC

* fix(frontend): keep the artifact target across re-authentication

Review found the standalone viewer unrecoverable from an expired session.
The window's target lives entirely in `?path=...&thread_id=...`, and both
auth paths dropped it:

- The layout guard redirected to `/login` with no `next` at all. A layout
  cannot read `searchParams`, so the guard moves into the page, which can
  — and rebuilds the full viewer address for `next`. The layout loses its
  AuthProvider along the way: nothing under this route reads `useAuth`,
  and the guard now makes a single `getServerSideUser` call per request.
- The shared fetch wrapper built `next` from `window.location.pathname`,
  which silently truncated the query string. It now carries `search` too,
  so any route holding state in the query survives a 401, not just this
  one. `validateAuthNextPath` already accepts a query string.

`buildArtifactViewerURL` is split out of `resolveArtifactOpenURL`: the
guard needs the route itself, never the Gateway fallback that the latter
takes for non-markdown targets.

Tests: the login round trip (unit — the rebuilt URL survives
`validateAuthNextPath` and parses back to the same target), the fetch
wrapper preserving the query on 401 (unit), and the expired-session
window reaching `/login` with the artifact intact (E2E). The E2E asserts
on the popup's navigation *requests*, since `(auth)/layout` answers
`/login` with a server redirect under DEER_FLOW_AUTH_DISABLED and no
navigation commits.

`tests/unit/core/models/api.test.ts` stubbed `window.location` without
`search`; a real Location always has it.

Claude-Session: https://claude.ai/code/session_013AiCrC5SBc3HdFYNxsp1EC

* fix(frontend): keep public showcase artifacts out of the auth gate

Review found that the viewer's access check regressed `/showcase`. Those
pages render with `isMock`, their artifacts are served by the
unauthenticated demo route, and the raw artifact URL this window replaced
stayed public — so gating the window unconditionally bounced every
logged-out showcase visitor to /login for a document that is already
public.

`requiresAuthenticatedViewer` exempts a mock target only when
`resolveStaticDemoArtifact` would actually serve it. The allowlist is the
authority rather than the flag: `mock=true` is caller-supplied, so a
target the demo route answers with 404 — a non-allowlisted path, or a
thread that is not a demo thread — still needs a session.

Covered in `tests/e2e-auth/`, since the default E2E config disables auth
and cannot see this: a public showcase artifact renders without a
session, while a non-allowlisted path and a missing mock flag both land
on /login. Verified the positive case goes red without the exemption.

Claude-Session: https://claude.ai/code/session_013AiCrC5SBc3HdFYNxsp1EC
2026-08-29 11:07:16 +08:00
陈志谦
8989173c8d
fix(frontend): restore sanitization in custom streamdown rehype chains (#4987)
* fix(frontend): restore sanitization in custom streamdown rehype chains

Streamdown 2.5 replaces its entire default rehype chain
[rehype-raw, rehype-sanitize, rehype-harden] with whatever array the
caller passes via the rehypePlugins prop. Every custom chain in this
repo therefore rendered LLM/stored markdown without any sanitization:

- Artifact markdown previews (markdown-preview-plugins.ts +
  artifact-file-detail.tsx) parse raw HTML via rehypeRaw, so a
  generated .md artifact could inject <style>/<iframe>/on* handlers
  into the workspace DOM (stored XSS; only javascript: anchors were
  blocked by the ArtifactLink component).
- The memory settings summary (memory-settings-page.tsx) spread the
  shared preset without component overrides, so a hostile
  <a href="javascript:..."> in stored memory content rendered as a
  clickable anchor.

Fix strategy:

- Add rehype-sanitize (already resolved in the lockfile via streamdown)
  as a direct dependency and re-insert a [rehypeSanitize, schema] step
  in the shared preset (core/streamdown/plugins.ts). It runs after
  rehypeRaw (raw HTML must be parsed into hast before it can be
  cleaned) and before rehypeKatex/rehypeSlug (their output is trusted
  and would otherwise be filtered or clobbered) - the same
  raw -> sanitize -> math ordering streamdown itself uses.
- The schema extends rehype-sanitize's GitHub-style defaultSchema (the
  base of streamdown's own sanitize schema) so legitimate authored
  artifact HTML (tables, details, images, alignment/size attributes)
  keeps working while script/iframe/style, on* handlers and
  non-allow-listed URL schemes (javascript:, data:, ...) are dropped.
  The only extensions are tel: hrefs and the math-inline/math-display
  class markers remark-math emits and rehype-katex detects.
- Position rehypeSlug after the sanitize step in the artifact chain so
  sanitize's id clobbering (id="x" -> id="user-content-x") cannot break
  the heading anchors it creates.
- Pass a: createMarkdownLinkComponent() on the memory settings page as
  defense in depth, matching the chat rendering path.

Unit tests feed a hostile payload (<a href="javascript:...">,
<img onerror>, <script>, <iframe>, <style>, ontoggle) through both
render paths and assert no executable/clickable equivalent survives,
plus regression guards for heading anchors, legitimate HTML and KaTeX
math rendering.

* fix(frontend): keep the sanitize clobber prefix on heading anchors; minimal lockfile

Review follow-ups on the sanitization change:

- Heading anchors: rehypeScopedSlug replaces rehype-slug in the artifact
  chain. It runs after the sanitize step (so raw-HTML headings are also
  anchored) but keeps rehype-sanitize's user-content- id prefix — an
  untrusted heading like "## current" cannot mint an unprefixed
  id="current" (the DOM-clobbering shape the sanitizer guards against).
  In-page fragment links are translated to the prefixed anchors so they
  still resolve; external URLs, bare "#", already-prefixed fragments and
  sanitize-prefixed raw-HTML ids are left untouched.
- Lockfile: regenerated as a minimal diff — only the two direct-dependency
  importer entries (rehype-sanitize, github-slugger for the scoped slug)
  are added; the libc platform selectors on the 64 native package records
  are preserved byte-for-byte instead of being dropped by lockfile
  normalization.

Full frontend suite: 1034 tests passing; tsc and prettier clean.

* style: reorder github-slugger import ahead of the hast type import

* test(e2e): expect the clobber-prefixed heading anchor in artifact preview

The scoped slug plugin gives generated heading ids rehype-sanitize's
user-content- prefix and translates fragment links to match, so the
anchor-scroll test must locate the prefixed id.

* fix(frontend): reset the scoped slugger per tree; keep footnote anchors single-prefixed

Review follow-ups:

- The scoped slug attacher holds one GithubSlugger, but streamdown
  caches the unified processor by plugin name, so the instance survived
  across parses and repeated renders of the same heading grew -1/-2
  suffixes (the artifact-anchor e2e could not find the id on re-render).
  The transformer now resets the slugger per tree, as rehype-slug does;
  a regression test renders identical artifact markdown twice.
- remark-rehype emits GFM footnote anchors already clobber-prefixed
  (user-content-fn-1); the sanitize step prefixed those ids again while
  their hrefs stayed single-prefixed, breaking footnote navigation in
  every chain built on the shared preset. A new rehypeClobberFragments
  step runs right after sanitize: double-prefixed ids are normalized
  back to one prefix, and unprefixed fragment hrefs are translated to
  the prefixed form (already-prefixed and external links untouched).
  The artifact slug now inserts after this step; covered by a footnote
  regression test on the shared render path.

Unit suite 1036 passing; artifact-preview e2e verified locally
(9/9, including the heading-anchor scroll test).
2026-08-24 22:20:24 +08:00
PiedPiper911
582fa20001
fix(artifacts): serve SHA-256 via ETag so preview/edit work on non-secure contexts (#4865)
* fix(artifacts): serve SHA-256 via ETag so preview/edit work on non-secure contexts

crypto.subtle is only available in secure contexts (HTTPS or localhost). The frontend fell back to it to compute an artifact's SHA-256 when the Gateway did not return one, which threw on http://<lan-ip>:<port> and broke both artifact preview and inline editing (issue #4864).

- Gateway now returns the real SHA-256 as an ETag header for inline text and active-content artifact responses (and skill-archive members).
- Frontend prefers the ETag and only computes a hash as a last resort, falling back gracefully (FNV-1a) instead of throwing when crypto.subtle is missing.

* fix(artifacts): address PR review feedback for #4864

- Cache SHA-256 digests by (path, mtime_ns, size) so the many small Range
  requests a browser issues while scrubbing/paginating a preview do not each
  re-hash a potentially huge artifact from scratch (performance).
- Gate inline editing on a real 64-hex revision: hasRevision requires
  sha256.length === 64, so the FNV-1a fallback on non-secure origins keeps
  preview working but no longer 422s on save (contract).
- Anchor and lowercase the ETag regex and accept the weak W/ prefix gzip
  emits, so uppercase hex and longer digests (sha-384/512) can't masquerade
  as sha-256.
- Cover the forced-download ETag on the backend and add frontend tests for
  weak-ETag parsing and the non-secure-context FNV fallback.

Feedback from reviewer willem-bd on PR #4865.

* style: fix ruff format and prettier issues

- test_artifacts_router.py: collapse two over-split client.get() calls to
  satisfy ruff format (line-length 240)
- loader.ts / artifact-file-detail.tsx / loader.test.ts: apply prettier
  formatting and restore LF line endings

* style: fix ruff format and prettier issues

- test_artifacts_router.py: collapse two over-split client.get() calls to
  satisfy ruff format (line-length 240)
- loader.ts / artifact-file-detail.tsx / loader.test.ts: apply prettier
  formatting and restore LF line endings

* style: fix ruff format and prettier issues

- test_artifacts_router.py: collapse two over-split client.get() calls to
  satisfy ruff format (line-length 240)
- loader.ts / artifact-file-detail.tsx / loader.test.ts: apply prettier
  formatting and restore LF line endings

* style: fix ruff format and prettier issues

- test_artifacts_router.py: collapse two over-split client.get() calls to
  satisfy ruff format (line-length 240)
- loader.ts / artifact-file-detail.tsx / loader.test.ts: apply prettier
  formatting and restore LF line endings

* style: reformat artifact-file-detail.tsx for prettier with tailwind class ordering

* fix: invalidate SHA-256 cache after artifact edit

Clear the LRU cache after os.replace() so the next preview request
computes the new digest. Edits are rare, so clearing the whole
256-entry cache costs nothing (addressing PR review comment #5).

* fix(artifacts): skip ETag for oversized files + CRLF->LF + cache invalidation (#4865)

* fix(loader): use real empty-content SHA-256 for empty 416 range (#4865)

* test(artifacts): assert oversized artifacts carry no SHA-256 ETag (#4865)

* style(frontend): format long sha256 constant (prettier)

* test(backend): fix oversized-artifact ETag assertions and formatting (ruff)

* test(backend): keep oversized-payload line within ruff 240-col config
2026-08-24 07:45:30 +08:00
DanielWalnut
459dd78707
perf(frontend): bound delivery, bundles, and long-running UI work (#4622)
* docs: design frontend performance remediation

* docs: plan frontend performance remediation

* test(frontend): add route asset performance budgets

* perf(nginx): compress textual responses safely

* perf(frontend): lazy load case study media

* perf(frontend): bound static demo file tracing

* perf(frontend): restore static locale boundaries

* perf(frontend): defer closed workspace panels

* perf(frontend): split editors and deduplicate highlighting

* perf(frontend): index incremental message derivation

* perf(frontend): stabilize paged history cache policy

* perf(frontend): bound streaming markdown renders

* perf(frontend): virtualize message history

* perf(frontend): bound and virtualize chat lists

* perf(frontend): suspend inactive decorative animation

* perf(browser): stream latest frames as binary

* perf(artifacts): stream bounded text previews

* docs: finalize performance runtime boundaries

* style(backend): apply test formatting

* fix(frontend): keep translation functions client-side

* perf(frontend): defer decorative animation bundles

* test(frontend): lock optimized route budgets

* fix: harden frontend performance boundaries

* test(frontend): update i18n provider fixture

* fix(frontend): preserve sidebar pagination position

* style(backend): format artifact range test
2026-08-01 22:19:59 +08:00
qin-chenghan
8234370a6a
feat(artifacts): inline editing for text artifacts in the panel (#4596)
* feat(artifacts): inline editing for text artifacts in the panel

Add a PUT /api/threads/{id}/artifacts/{path} endpoint that atomically
replaces an existing UTF-8 text file under /mnt/user-data/outputs after
verifying its SHA-256 revision. Active runs conflict (409); binary,
symlink, oversized, and non-output paths are rejected.

Frontend: edit/save/discard buttons, draft state with conflict detection,
CodeEditor onChange/onSave, loader SHA-256 from ETag, i18n, beforeunload guard.

Backend: PUT endpoint with thread reservation, atomic temp-file replacement,
sandbox sync for non-mounted providers, rollback on failure, ETag on GET.

Tests: 8 backend + 1 blocking-IO + 3 frontend test files.

* fix(artifacts): scope replacement permissions and release sandboxes

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-08-01 09:15:21 +08:00
qin-chenghan
0d8e11ad49
fix(frontend): persist artifact panel state (#4580) 2026-07-30 15:54:15 +08:00
March7
bb0088127b
fix(frontend): prevent streamed word animation replay (#4266)
* fix(frontend): stop replaying streamed word animations

* fix(frontend): restore Streamdown rendering plugins

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-07-22 09:03:46 +08:00
黄云龙
97ca7f88cf
fix(frontend): harden artifact and markdown rendering (#4117)
* fix(frontend): harden artifact and markdown rendering

* fix(#4117): restore allow-scripts for scroll-restore, fix citation XSS bypass

willem-bd identified these issues:

1. [REGRESSION] sandbox removed allow-scripts which broke HTML artifact
   scroll-restoration — the injected postMessage script could not run.
   The prior config (allow-scripts allow-forms without allow-same-origin,
   i.e. opaque origin) was already safe. Restored with corrected comment.

2. [XSS bypass] CitationLink rendered <a href={href}> directly before
   isSafeHref ran, so prompt-injected [citation:x](javascript:...)
   bypassed the safety check. Moved citation block after the guard.

Also:
- Added mailto: and tel: to SAFE_HREF_PROTOCOLS
- Kept anchor-only attributes off the <span> fallback (React DOM warning)

Note: the loadMessages re-throw originally in this commit was dropped
during the rebase — upstream #4065 rewrote useThreadHistory as a
TanStack useInfiniteQuery that already surfaces fetch failures.

* test(e2e): update sandbox assertion to match allow-scripts allow-forms

The artifact preview iframe's sandbox was restored to 'allow-scripts allow-forms'
for scroll-restoration. Update the E2E test to expect the corrected value.

* fix(#4117): address review — ArtifactLink XSS guard, urlOfArtifact sandbox e2e

- Apply the isSafeHref guard to ArtifactLink (artifact-link.tsx) so a
  prompt-injected javascript:/data: href in a .md artifact preview cannot
  reach a real anchor in the main document, matching the guard in
  createMarkdownLinkComponent.
- Add an e2e asserting the urlOfArtifact iframe (non-code
  browser-previewable files like PDF) keeps its empty sandbox.

Note: the loadMessages error-surfacing changes originally in this commit
were dropped during the rebase — upstream #4065 rewrote useThreadHistory
as a TanStack useInfiniteQuery whose queryFn already throws on
!response.ok and surfaces the failure via a toast.

* fix(frontend): let write_file non-code previewable artifacts render in sandboxed iframe

When a write_file artifact such as PDF is clicked in chat, the component receives a path that forced isCodeFile=true, hiding the sandboxed iframe behind the code editor. Now non-code browser-previewable files are detected early so the sandboxed iframe renders correctly.

Fixes the E2E test: renders sandboxed iframe for a browser-previewable non-code file.

* fix(#4117): align PDF artifact route pattern with convention (drop /mock/ prefix)

The test used /mock/api/threads/... for the PDF artifact content route,
but the urlOfArtifact helper generates /api/threads/... (without /mock/)
when isMock=false. The other tests (e.g. presented artifacts) already use
the correct /api/threads/... pattern.

* test(frontend): add render-level coverage for unsafe markdown/artifact links

- Render MarkdownLink and ArtifactLink via renderToStaticMarkup and
  assert an unsafe javascript: href produces a disabled <span> (never an
  <a>), including through the citation-labelled branch, and that safe
  https hrefs render hardened anchors (target=_blank, rel=noopener).
- The new ArtifactLink render test caught the unsafe href leaking onto
  the fallback <span> through the {...rest} spread; drop the spread in
  both span fallbacks so anchor-only attributes (href/target/rel) and
  react-markdown's node prop never reach the span.
- Cover mailto:/tel: in the isSafeHref unit cases and fix the stale
  SAFE_HREF_PROTOCOLS docstring that still claimed http/https-only.

* fix(frontend): route the agent save-hint through the safe localStorage facade

Export safeLocalStorage from core/settings/local and use it for the
agent-create save-hint read/write so blocked browser storage (Safari
private mode, strict containers, embedded WebViews) cannot throw from
the effect. core/agents/feature-cache.ts already guards its
localStorage access with try/catch, so settings + agent pages are now
consistently best-effort.

* fix(frontend): allow scheme-less relative markdown links in isSafeHref
2026-07-21 11:35:44 +08:00
Ryker_Feng
7a985f441c
Fix(frontend): Fix mobile workspace and accessibility blockers (#3740)
* fix(frontend): address mobile workspace polish blockers

* fix(frontend): prevent mobile landing overflow

* fix(frontend): keep landing sections within mobile viewport

Section titles used a fixed text-5xl with no word breaking, and the
<section> flex items had default min-width:auto, so wider Linux font
metrics in CI pushed content past the viewport (scrollWidth 345 > 320).
Make titles/subtitles responsive with break-words, constrain section
width with min-w-0, and add an overflow-x-clip guard on the page root.

* fix(frontend): address review feedback on mobile landing PR

- add hamburger Sheet nav below sm: so mobile users keep docs/blog access
- switch useIsMobile to useSyncExternalStore to avoid hydration swap flash
- memoize artifactContent so it isn't rebuilt on every streamed token
- use slot-based key in HeroWordRotate; drop flex-wrap so SuperAgent never orphans at 320px
- delete unused word-rotate.tsx dead code
- move section gutter to <main> for a uniform mobile padding contract
- harden e2e: per-viewport overflow tests, locale-stable artifact selector, focus-ring asserts light vs dark differ

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-07-06 07:15:35 +08:00
Ryker_Feng
6a4e5a3bb2
feat(frontend): add side conversations for quoted follow-ups (#3934)
* feat(frontend): add side conversations for quoted follow-ups

* style(frontend): apply prettier formatting to sidecar-chat files

* fix(frontend): surface sidecar cascade cleanup failures via console.warn

Previously deleteSidecarThreadsForParent silently swallowed both
lookup errors and per-thread deletion failures, so parent thread
deletions could succeed while orphaning sidecar threads with no
signal to the caller. Log a warning that includes the parent id
and the failed thread ids/reasons so the leak is discoverable in
telemetry, matching the existing console.warn/error pattern in
this file.

* fix(frontend): address all sidecar review feedback

Resolve every reviewer comment on PR #3934:

- input-box/hooks/sidecar-panel: clear quoted references only via an
  `onSent` callback that fires after the in-flight guard, so a dropped
  send no longer silently discards quotes (willem-bd #3550).
- message-list: flip the selection toolbar below the selection when it
  would clip above the viewport (willem-bd #3551).
- reference-metadata/thread/input-box: keep referenced ids, roles, and
  count arrays 1:1 parallel instead of deduping ids (willem-bd #3552).
- message-list: widen selection containment to the shared assistant-turn
  container and hint when a selection crosses messages (willem-bd #3553).
- sidecar/api: coalesce concurrent sidecar creates for one parent behind
  a single in-flight promise to prevent duplicates (willem-bd #3554).
- sidecar-trigger/context: force-restore on trigger click so a sidecar
  deleted elsewhere self-heals instead of opening a dead thread
  (willem-bd #3555).
- threads/hooks: surface sidecar cascade cleanup failures via
  console.warn for both lookup and per-thread deletes (Copilot).

Add unit + e2e coverage for parallel metadata, atomic create, and
trigger self-healing.
2026-07-05 00:12:16 +08:00
Ryker_Feng
8a26b5c9a4
feat(frontend): add citation sources evidence panel (#3907)
* feat(frontend): add citation sources evidence panel

Inline [citation:Title](URL) links render as badges, but with many
citations the reader has no consolidated, deduplicated list of what a
message or report actually drew on. Add a collapsible "sources" panel
that extracts citation links from AI messages and markdown artifacts,
dedupes by URL, counts occurrences, and offers per-source copy of a
reusable markdown reference.

- extractCitationSources: parse [citation:...](url) links, skip images
  and fenced code, dedupe by normalized URL, fall back to domain for
  generic labels
- CitationSourcesPanel: collapsible list with per-source cite counts,
  internal scroll for long lists, and copy-to-clipboard reference button
- Wire panel into AI message content and markdown artifact preview
- Add en-US/zh-CN citation strings and types
- Unit tests for extraction and panel rendering

* fix(frontend): preserve default link styling in message content override

MessageContent_ passes a custom `a` renderer to MarkdownContent, whose
default `a` (primary underline + external target/rel) is overridden
because MarkdownContent spreads props components last. Restore that
styling/external behavior in the fallback branch so normal links in
messages aren't regressed, while keeping citation: and /mnt/ handling.

* fix(frontend): harden citation source extraction and dedupe link renderer

Address review findings on the citation sources panel:
- Use a non-consuming lookbehind so back-to-back citations no longer drop
  every other source.
- Match balanced parenthetical groups in URLs so disambiguation links like
  .../Foo_(a)_(b) are no longer truncated.
- Mask inline code (and unclosed streaming fences) so example citations in
  code aren't scraped as real sources; masking preserves indices.
- Extract a shared createMarkdownLinkComponent factory used by both message
  content and markdown content, removing the duplicated `a` renderer.
2026-07-03 18:03:43 +08:00
Yi Deng
331c949b95
fix(gateway): require admin for global skills management endpoints (#3855)
* fix(gateway): require admin for global skills management endpoints

The skills router exposed its management endpoints with no authorization
check (only Depends(get_config)), while the MCP router guards the
equivalent global extensions_config mutations with require_admin_user
(added in the #3425 security hardening). Skills storage is global/shared
across users (no per-user path), lives in the same extensions_config.json
as MCP servers, and custom skill SKILL.md content is injected into every
user's agent system prompt. Any authenticated non-admin user could mutate
or read global skills that affect all tenants:

- POST /api/skills/install: install from an arbitrary thread_id, writing
  into the global custom skills tree
- PUT /api/skills/custom/{skill_name}: rewrite a global skill, injecting
  instructions into all users' agent prompts (cross-tenant prompt injection)
- DELETE /api/skills/custom/{skill_name} and rollback: tamper with global skills
- GET /api/skills/custom, GET .../{skill_name}, GET .../history: read raw
  global custom skill bodies/history
- PUT /api/skills/{skill_name} (enable toggle): writes the shared
  extensions_config.json and refreshes the system prompt for every tenant,
  so a non-admin could enable/disable any skill globally. There is no
  per-user skill state, so this is a global mutation, not a preference.

Add require_admin_user to every endpoint above, mirroring the MCP router.
The shared read path used internally by update/rollback was extracted into
a non-auth helper (_read_custom_skill_response) so internal reuse does not
double-check auth.

Only the read-only GET /api/skills and GET /api/skills/{skill_name} stay
open to normal users: they return just name/description/enabled and back
the user-facing settings UI.

Tests:
- New tests/test_skills_router_authz.py: a non-admin user gets 403 on every
  guarded endpoint (including the enable toggle); basic listing stays open;
  admins can still toggle.
- Update tests/test_skills_custom_router.py to authenticate as admin.
- pytest tests/ -k skill -> 350 passed, 1 skipped; ruff clean.

Signed-off-by: DengY11 <151997860+DengY11@users.noreply.github.com>

* fix(frontend): gate skill enable/install UI behind admin + handle 403

Follow-up to the backend change that made the global skills mutations
admin-only. Without a matching UI change, a non-admin user would hit a
silent 403 when toggling a skill in Settings -> Skills or clicking
"Install skill" on a .skill artifact.

- core/skills/api.ts: add SkillRequestError (status + isAdminRequired),
  throw it from loadSkills/enableSkill on non-ok responses and from
  installSkill on 403 (other install errors keep the soft-failure
  contract).
- core/skills/hooks.ts: useSkills no longer retries on SkillRequestError.
- skill-settings-page.tsx: show an "admin required" message on 403, and
  disable the enable toggle for non-admins (mirrors the MCP tools page).
- artifact-file-detail.tsx / artifact-file-list.tsx: only render the
  "Install skill" action for admins, and surface an admin-required toast
  if a 403 still occurs.
- i18n: add settings.skills.adminRequired / installAdminRequired (en + zh).

Auth/no-auth and static-website modes synthesize an admin user, so these
gates do not affect single-user/local deployments.

Verified locally: pnpm check (eslint + tsc) passes with no new errors,
pnpm build succeeds, and the dev server renders / and /login (200) with
no compile/runtime errors.

Signed-off-by: DengY11 <151997860+DengY11@users.noreply.github.com>

* style(frontend): format skills hook for prettier

Keep the admin-guard skills hook aligned with Prettier output so the frontend format check passes in CI.

---------

Signed-off-by: DengY11 <151997860+DengY11@users.noreply.github.com>
2026-07-02 11:17:23 +08:00
Huixin615
b3c312b795
fix(frontend): retain presented artifacts in header dropdown (#3854)
* fix(frontend): retain presented artifacts in dropdown

* refactor(agents): trim file editing prompt context

* Revert "refactor(agents): trim file editing prompt context"

This reverts commit cad105265024335222e374692dde37424d11024b.
2026-06-28 23:30:40 +08:00
Huixin615
3e2f1bbe49
fix(frontend): show filename for presented artifacts missing from thread.values.artifacts (#3198)
Closes #3192

Root cause
----------
The artifact preview header is driven by a Radix Select whose <SelectValue>
renders the label of the <SelectItem> matching the current value. The option
list was built solely from `artifacts` in ArtifactsContext, which is only
synced from `thread.values.artifacts` (chat-box.tsx). Artifacts surfaced via
the message-layer `present_files` tool call are never written back to
`thread.values.artifacts`, so when such a file is selected its filepath has
no matching <SelectItem>. Radix then renders an empty trigger and the header
filename appears blank, even though the preview body loads correctly.

Fix
---
Compute `artifactOptions` as a defensive union: if the currently selected
filepath is missing from `artifacts`, prepend it so a matching <SelectItem>
always exists. This keeps the header label in sync with the active file
without changing context semantics or coupling the UI to a future
auto-discovery mechanism (see existing TODO in chat-box.tsx).

Tests
-----
Add an e2e case that mocks a thread whose only artifact is delivered via
`present_files` (thread.values.artifacts = []) and asserts both the header
title and preview content render. All 21 e2e tests pass.
2026-06-27 23:07:12 +08:00
Huixin615
b5cac5e721
fix: add heading anchors to markdown artifact previews (#3805)
* fix: add markdown heading anchors for artifacts

* fix: scope markdown anchors to artifact previews

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-06-27 22:59:47 +08:00
Boooobby
4a8f94eb28
fix(frontend): improve chat math rendering (#3557)
* fix(frontend): improve chat math rendering

* fix(frontend): refine math rendering stability

* fix(frontend): address review feedback on math preprocessing

- Fix escaped-backslash blind spot: consume \\\\ as a unit so \\( and
  \\[ are not mis-interpreted as math delimiters when the backslash
  itself is escaped.
- Thread inInlineCode state across lines so multi-line backtick code
  spans are protected from delimiter conversion.
- Remove double math normalization: preprocessStreamdownMarkdown now
  only handles Mermaid; math normalization lives solely in
  ClipboardSafeStreamdown, preventing non-idempotent double passes.
- Wire parseIncompleteMarkdown to isLoading in MarkdownContent so
  Streamdown's streaming-incomplete-math handling is actually active
  during streaming.

* fix(frontend): address streamdown math review issues

* refactor(frontend): move streamdown preprocessing out of ai elements
2026-06-24 17:17:36 +08:00
vantanco
90328d5651
Avoid blank previews for unsupported artifacts (#3644) 2026-06-20 00:15:11 +08:00
Admire
5b81588b87
fix(frontend): fallback Streamdown clipboard copy (#3397)
* fix(frontend): fallback streamdown clipboard copy

* fix(frontend): address clipboard fallback review

* fix(frontend): normalize clipboard fallback rejection

* fix(frontend): harden clipboard fallback install

* fix(frontend): clarify clipboard fallback errors

* fix(frontend): cover clipboard fallback edge cases

* fix(frontend): tighten clipboard fallback cleanup

* fix(frontend): reduce clipboard fallback copy window

* fix(frontend): guard clipboard item fallback install

* fix(frontend): clean up clipboard fallback on selection errors

* Address clipboard fallback review feedback

* fix(frontend): guard clipboard fallback install during SSR
2026-06-09 22:09:13 +08:00
Admire
f68bcb771c
fix(frontend): guard message copy clipboard access (#3211)
* fix(frontend): guard message copy clipboard access

* fix(frontend): reuse clipboard guard across copy actions
2026-05-26 09:37:51 +08:00
AochenShen99
604fcbb9d2
Stabilize write artifact previews (#3172) 2026-05-23 16:56:14 +08:00
JeffJiang
b103d1a7f5
feat(frontend): support static website demo mode (#3170)
* feat(frontend): support static website demo mode

* fix(frontend): render html artifact previews from blob content

* chore(frontend): apply pre-commit formatting

* fix(frontend): address static demo PR review comments

* Update the release information of DeerFlow

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-05-23 00:10:56 +08:00
Admire
140907ce1d
Fix abnormal preview of HTML files (#1986)
* Fix HTML artifact preview rendering

* Add after screenshot for HTML preview fix

* Add before screenshot for HTML preview fix

* Update before screenshot for HTML preview fix

* Update after screenshot for HTML preview fix

* Update before screenshot to Tsinghua homepage repro

* Update after screenshot to Tsinghua homepage preview

* Address PR review on HTML artifact preview

* Harden HTML artifact preview isolation
2026-04-09 16:32:01 +08:00
yangzheli
3acdf79beb
fix(frontend): resolve invalid HTML nesting and tabnabbing vulnerabilities (#1904)
* fix(frontend): resolve invalid HTML nesting and tabnabbing vulnerabilities

Fix `<button>` inside `<a>` invalid HTML in artifact components and add
missing `noopener,noreferrer` to `window.open` calls to prevent reverse
tabnabbing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(frontend): address Copilot review on tabnabbing and double-tab-open

Remove redundant parent onClick on web_fetch ChainOfThoughtStep to
prevent opening two tabs on link click, and explicitly null out
window.opener after window.open() for defensive tabnabbing hardening.

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 09:44:17 +08:00
Zhou
f5088ed70d
fix(frontend): artifact download action bounds and lint errors (#1899)
* fix: keep artifact download action in bounds

* fix: fix lint error
2026-04-06 16:34:40 +08:00
yangzheli
636053fb6d
fix(frontend): add missing rel="noopener noreferrer" to target="_blank" links (#1741)
* fix(frontend): add missing rel="noopener noreferrer" to target="_blank" links

Prevent tabnabbing attacks and referrer leakage by ensuring all
external links with target="_blank" include both noopener and
noreferrer in the rel attribute.

Made-with: Cursor

* style: fix code formatting
2026-04-02 17:32:52 +08:00
JeffJiang
cf43584d24
fix(artifact): enhance artifact content loading to include URL for non-write files (#1678) 2026-04-01 11:38:55 +08:00
lailoo
9809af1f26
feat: add citation/reference support to deep research reports (#1143)
* feat: add citation/reference support to deep research reports (#1141)

- Enhance lead agent system prompt with mandatory citation requirements
  after web_search/web_fetch tool usage
- Add citation examples and best practices to GitHub Deep Research skill
- Add citation hints to report template (Executive Summary, Key Analysis)
- Style regular markdown links in frontend for visual distinction
  (color, underline, hover effect)
- Fix TitleMiddleware being registered when title generation is disabled

* fix: address PR review comments

- Revert TitleMiddleware conditional registration (agent.py) to avoid
  sync/async incompatibility with DeerFlowClient
- Fix markdown link rendering: merge classNames instead of overwriting,
  only set target=_blank for external http(s) URLs
- Remove unrelated package.json/pnpm-lock.yaml changes

* fix: use plain markdown links in Sources section for cleaner rendering

Inline citations in report body use [citation:Title](URL) for pill/badge style.
Sources section uses plain [Title](URL) for simple underlined link style.

* fix(frontend): render plain links as underlined text in artifact markdown

Only links with citation: prefix render as Badge pills.
Regular links in Sources section now render as underlined text links.

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-03-17 09:51:08 +08:00
Ashwek-Werghi
4f0a8da2ee
fix(frontend): enable HTML preview for generated artifacts using srcDoc (#1001)
* fix(frontend): enable HTML preview for generated artifacts using srcDoc

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-09 15:34:44 +08:00
JeffJiang
cf9af1fe75
Enhance chat UI and compatible anthropic thinking messages (#1018) 2026-03-08 20:19:31 +08:00
JeffJiang
1b3939cb78
fix(chat): handle empty uploaded files case and improve artifact selection logic (#979)
* fix(chat): handle empty uploaded files case and improve artifact selection logic

* Update frontend/src/components/workspace/chats/chat-box.tsx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix: address code review suggestions from PR #979 (#3)

* Initial plan

* fix: address PR #979 review suggestions

- utils.ts: scope (empty) check inside <uploaded_files> tag content
- chat-box.tsx: remove stale `artifacts` from useEffect deps
- context.tsx: wrap select/deselect with useCallback for stable refs
- test: add test_empty_new_files_produces_empty_marker

Co-authored-by: foreleven <4785594+foreleven@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: foreleven <4785594+foreleven@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: foreleven <4785594+foreleven@users.noreply.github.com>
2026-03-05 17:45:25 +08:00
JeffJiang
14d1e01149
Refactor hooks and improve error handling in chat functionality (#962)
* refactor: update useThreadChat and useThreadStream hooks for improved state management

* fix: improve error handling in agent configuration loading and enhance chat page functionality

* fix: enhance error handling in agent configuration loading

* Update frontend/src/app/workspace/agents/[agent_name]/chats/[thread_id]/page.tsx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-04 09:50:45 +08:00
JeffJiang
7de94394d4
feat(agent):Supports custom agent and chat experience with refactoring (#957)
* feat: add agent management functionality with creation, editing, and deletion

* feat: enhance agent creation and chat experience

- Added AgentWelcome component to display agent description on new thread creation.
- Improved agent name validation with availability check during agent creation.
- Updated NewAgentPage to handle agent creation flow more effectively, including enhanced error handling and user feedback.
- Refactored chat components to streamline message handling and improve user experience.
- Introduced new bootstrap skill for personalized onboarding conversations, including detailed conversation phases and a structured SOUL.md template.
- Updated localization files to reflect new features and error messages.
- General code cleanup and optimizations across various components and hooks.

* Refactor workspace layout and agent management components

- Updated WorkspaceLayout to use useLayoutEffect for sidebar state initialization.
- Removed unused AgentFormDialog and related edit functionality from AgentCard.
- Introduced ArtifactTrigger component to manage artifact visibility.
- Enhanced ChatBox to handle artifact selection and display.
- Improved message list rendering logic to avoid loading states.
- Updated localization files to remove deprecated keys and add new translations.
- Refined hooks for local settings and thread management to improve performance and clarity.
- Added temporal awareness guidelines to deep research skill documentation.

* feat: refactor chat components and introduce thread management hooks

* feat: improve artifact file detail preview logic and clean up console logs

* feat: refactor lead agent creation logic and improve logging details

* feat: validate agent name format and enhance error handling in agent setup

* feat: simplify thread search query by removing unnecessary metadata

* feat: update query key in useDeleteThread and useRenameThread for consistency

* feat: add isMock parameter to thread and artifact handling for improved testing

* fix: reorder import of setup_agent for consistency in builtins module

* feat: append mock parameter to thread links in CaseStudySection for testing purposes

* fix: update load_agent_soul calls to use cfg.name for improved clarity

* fix: update date format in apply_prompt_template for consistency

* feat: integrate isMock parameter into artifact content loading for enhanced testing

* docs: add license section to SKILL.md for clarity and attribution

* feat(agent): enhance model resolution and agent configuration handling

* chore: remove unused import of _resolve_model_name from agents

* feat(agent): remove unused field

* fix(agent): set default value for requested_model_name in _resolve_model_name function

* feat(agent): update get_available_tools call to handle optional agent_config and improve middleware function signature

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-03-03 21:32:01 +08:00
CHANGXUBO
9f74589d09
fix: HTML artifact preview renders blank in preview mode (#876)
The condition guarding ArtifactFilePreview only allowed markdown files
through, which prevented HTML files from reaching the preview component.
Added `language === "html"` to the condition so HTML artifacts render
correctly in preview mode.

Fixes #873

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-18 10:06:21 +08:00
LofiSu
c89bd9edc9 Merge upstream/experimental: resolve conflicts (keep feat/citations)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 21:56:02 +08:00
LofiSu
2f50e5d969 feat(citations): inline citation links with [citation:Title](URL)
- Backend: add citation format to lead_agent and general_purpose prompts
- Add CitationLink component (Badge + HoverCard) for citation cards
- MarkdownContent: detect citation: prefix in link text, render CitationLink
- Message/artifact/subtask: use MarkdownContent or Streamdown with CitationLink
- message-list-item: pass img via components prop (remove isHuman/img)
- message-group, subtask-card: drop unused imports; fix import order (lint)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 21:40:20 +08:00
LofiSu
46048c76ce chore: 移除所有 Citations 相关逻辑,为后续重构做准备
- Backend: 删除 lead_agent / general_purpose 中的 citations_format 与引用相关 reminder;artifacts 下载不再对 markdown 做 citation 清洗,统一走 FileResponse,保留 Response 用于二进制 inline
- Frontend: 删除 core/citations 模块、inline-citation、safe-citation-content;新增 MarkdownContent 仅做 Markdown 渲染;消息/artifact 预览与复制均使用原始 content
- i18n: 移除 citations 命名空间(loadingCitations、loadingCitationsWithCount)
- 技能与 demo: 措辞改为 references,demo 数据去掉 <citations> 块
- 文档: 更新 CLAUDE/AGENTS/README 描述,新增按文件 diff 的代码变更总结

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 16:24:01 +08:00
ruitanglin
8747873b8d chore: 移除所有 Citations 相关逻辑,为后续重构做准备
- Backend: 删除 lead_agent / general_purpose 中的 citations_format 与引用相关 reminder;artifacts 下载不再对 markdown 做 citation 清洗,统一走 FileResponse,保留 Response 用于二进制 inline
- Frontend: 删除 core/citations 模块、inline-citation、safe-citation-content;新增 MarkdownContent 仅做 Markdown 渲染;消息/artifact 预览与复制均使用原始 content
- i18n: 移除 citations 命名空间(loadingCitations、loadingCitationsWithCount)
- 技能与 demo: 措辞改为 references,demo 数据去掉 <citations> 块
- 文档: 更新 CLAUDE/AGENTS/README 描述,新增按文件 diff 的代码变更总结

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 16:24:01 +08:00
LofiSu
cef8d389fd refactor(frontend): consolidate citation logic, slim exports and impl
- SafeCitationContent: add loadingOnly and renderBody props.
  - loadingOnly: show only loading indicator or null (e.g. write_file step).
  - renderBody(parsed): custom body renderer (e.g. artifact preview).

- message-group write_file: use SafeCitationContent(content, isLoading,
  rehypePlugins, loadingOnly) instead of local useParsedCitations +
  shouldShowCitationLoading + CitationsLoadingIndicator. Pass rehypePlugins
  into ToolCall.

- artifact-file-detail markdown preview: use SafeCitationContent with
  renderBody((p) => <ArtifactFilePreview ... cleanContent={p.cleanContent}
  citationMap={p.citationMap} />). Remove local shouldShowCitationLoading
  and CitationsLoadingIndicator branch.

- core/citations: inline buildCitationMap into use-parsed-citations, remove
  from utils; stop exporting hasCitationsBlock (internal to shouldShowCitationLoading).

- inline-citation: make InlineCitationCard, InlineCitationCardBody,
  InlineCitationSource file-private (no longer exported).

Co-authored-by: Cursor <cursoragent@cursor.com>

---
refactor(前端): 收拢引用逻辑、精简导出与实现

- SafeCitationContent 新增 loadingOnly、renderBody。
  - loadingOnly:仅显示加载或 null(如 write_file 步骤)。
  - renderBody(parsed):自定义正文渲染(如 artifact 预览)。

- message-group write_file:改用 SafeCitationContent(loadingOnly),去掉
  本地 useParsedCitations + shouldShowCitationLoading + CitationsLoadingIndicator,
  并向 ToolCall 传入 rehypePlugins。

- artifact-file-detail 的 markdown 预览:改用 SafeCitationContent +
  renderBody 渲染 ArtifactFilePreview,去掉本地加载判断与
  CitationsLoadingIndicator 分支。

- core/citations:buildCitationMap 内联到 use-parsed-citations 并从 utils
  删除;hasCitationsBlock 不再导出(仅 shouldShowCitationLoading 内部使用)。

- inline-citation:InlineCitationCard/Body/Source 改为文件内私有,不再导出。
2026-02-09 15:58:59 +08:00
ruitanglin
59c8fec7e7 refactor(frontend): consolidate citation logic, slim exports and impl
- SafeCitationContent: add loadingOnly and renderBody props.
  - loadingOnly: show only loading indicator or null (e.g. write_file step).
  - renderBody(parsed): custom body renderer (e.g. artifact preview).

- message-group write_file: use SafeCitationContent(content, isLoading,
  rehypePlugins, loadingOnly) instead of local useParsedCitations +
  shouldShowCitationLoading + CitationsLoadingIndicator. Pass rehypePlugins
  into ToolCall.

- artifact-file-detail markdown preview: use SafeCitationContent with
  renderBody((p) => <ArtifactFilePreview ... cleanContent={p.cleanContent}
  citationMap={p.citationMap} />). Remove local shouldShowCitationLoading
  and CitationsLoadingIndicator branch.

- core/citations: inline buildCitationMap into use-parsed-citations, remove
  from utils; stop exporting hasCitationsBlock (internal to shouldShowCitationLoading).

- inline-citation: make InlineCitationCard, InlineCitationCardBody,
  InlineCitationSource file-private (no longer exported).

Co-authored-by: Cursor <cursoragent@cursor.com>

---
refactor(前端): 收拢引用逻辑、精简导出与实现

- SafeCitationContent 新增 loadingOnly、renderBody。
  - loadingOnly:仅显示加载或 null(如 write_file 步骤)。
  - renderBody(parsed):自定义正文渲染(如 artifact 预览)。

- message-group write_file:改用 SafeCitationContent(loadingOnly),去掉
  本地 useParsedCitations + shouldShowCitationLoading + CitationsLoadingIndicator,
  并向 ToolCall 传入 rehypePlugins。

- artifact-file-detail 的 markdown 预览:改用 SafeCitationContent +
  renderBody 渲染 ArtifactFilePreview,去掉本地加载判断与
  CitationsLoadingIndicator 分支。

- core/citations:buildCitationMap 内联到 use-parsed-citations 并从 utils
  删除;hasCitationsBlock 不再导出(仅 shouldShowCitationLoading 内部使用)。

- inline-citation:InlineCitationCard/Body/Source 改为文件内私有,不再导出。
2026-02-09 15:58:59 +08:00
LofiSu
4f9d1d524e feat(frontend): unify citation logic and prevent half-finished citations
- Add SafeCitationContent as single component for citation-aware body:
  useParsedCitations + shouldShowCitationLoading; show loading until
  citations complete, then render body with createCitationMarkdownComponents.
  Supports optional remarkPlugins, rehypePlugins, isHuman, img.

- Refactor MessageListItem: assistant message body now uses
  SafeCitationContent only; remove duplicate useParsedCitations,
  shouldShowCitationLoading, createCitationMarkdownComponents and
  CitationsLoadingIndicator logic. Human messages keep plain
  AIElementMessageResponse (no citation parsing).

- Use SafeCitationContent for clarification, present-files (message-list),
  thinking steps and write_file loading (message-group), subtask result
  (subtask-card). Artifact markdown preview keeps same guard
  (shouldShowCitationLoading) with ArtifactFilePreview.

- Unify loading condition: shouldShowCitationLoading(rawContent,
  cleanContent, isLoading) is the single source of truth. Show loading when
  (isLoading && hasCitationsBlock(rawContent)) or when
  (hasCitationsBlock(rawContent) && hasUnreplacedCitationRefs(cleanContent))
  so Pro/Ultra modes also show "loading citations" and half-finished
  [cite-N] never appear.

- message-group write_file: replace hasCitationsBlock + threadIsLoading
  with shouldShowCitationLoading(fileContent, cleanContent,
  threadIsLoading && isLast) for consistency.

- citations/utils: parse incomplete <citations> during streaming;
  remove isCitationsBlockIncomplete; keep hasUnreplacedCitationRefs
  internal; document display rule in file header.

Co-authored-by: Cursor <cursoragent@cursor.com>

---
feat(前端): 统一引用逻辑并杜绝半成品引用

- 新增 SafeCitationContent 作为引用正文的唯一出口:内部使用
  useParsedCitations + shouldShowCitationLoading,在引用未就绪时只显示
  「正在整理引用」,就绪后用 createCitationMarkdownComponents 渲染正文;
  支持可选 remarkPlugins、rehypePlugins、isHuman、img。

- 重构 MessageListItem:助手消息正文仅通过 SafeCitationContent 渲染,
  删除重复的 useParsedCitations、shouldShowCitationLoading、
  createCitationMarkdownComponents、CitationsLoadingIndicator 等逻辑;
  用户消息仍用 AIElementMessageResponse,不做引用解析。

- 澄清、present-files(message-list)、思考步骤与 write_file 加载
  (message-group)、子任务结果(subtask-card)均使用
  SafeCitationContent;Artifact 的 markdown 预览仍用同一 guard
  shouldShowCitationLoading,正文由 ArtifactFilePreview 渲染。

- 统一加载条件:shouldShowCitationLoading(rawContent, cleanContent,
  isLoading) 为唯一判断。在「流式中且已有引用块」或「有引用块且
  cleanContent 中仍有未替换的 [cite-N]」时仅显示加载,从而在 Pro/Ultra
  下也能看到「正在整理引用」,且永不出现半成品 [cite-N]。

- message-group 的 write_file:用 shouldShowCitationLoading(
  fileContent, cleanContent, threadIsLoading && isLast) 替代
  hasCitationsBlock + threadIsLoading,与其他场景一致。

- citations/utils:流式时解析未闭合的 <citations>;移除
  isCitationsBlockIncomplete;hasUnreplacedCitationRefs 保持内部使用;
  在文件头注释中说明展示规则。
2026-02-09 15:01:51 +08:00
ruitanglin
a4268cb6d3 feat(frontend): unify citation logic and prevent half-finished citations
- Add SafeCitationContent as single component for citation-aware body:
  useParsedCitations + shouldShowCitationLoading; show loading until
  citations complete, then render body with createCitationMarkdownComponents.
  Supports optional remarkPlugins, rehypePlugins, isHuman, img.

- Refactor MessageListItem: assistant message body now uses
  SafeCitationContent only; remove duplicate useParsedCitations,
  shouldShowCitationLoading, createCitationMarkdownComponents and
  CitationsLoadingIndicator logic. Human messages keep plain
  AIElementMessageResponse (no citation parsing).

- Use SafeCitationContent for clarification, present-files (message-list),
  thinking steps and write_file loading (message-group), subtask result
  (subtask-card). Artifact markdown preview keeps same guard
  (shouldShowCitationLoading) with ArtifactFilePreview.

- Unify loading condition: shouldShowCitationLoading(rawContent,
  cleanContent, isLoading) is the single source of truth. Show loading when
  (isLoading && hasCitationsBlock(rawContent)) or when
  (hasCitationsBlock(rawContent) && hasUnreplacedCitationRefs(cleanContent))
  so Pro/Ultra modes also show "loading citations" and half-finished
  [cite-N] never appear.

- message-group write_file: replace hasCitationsBlock + threadIsLoading
  with shouldShowCitationLoading(fileContent, cleanContent,
  threadIsLoading && isLast) for consistency.

- citations/utils: parse incomplete <citations> during streaming;
  remove isCitationsBlockIncomplete; keep hasUnreplacedCitationRefs
  internal; document display rule in file header.

Co-authored-by: Cursor <cursoragent@cursor.com>

---
feat(前端): 统一引用逻辑并杜绝半成品引用

- 新增 SafeCitationContent 作为引用正文的唯一出口:内部使用
  useParsedCitations + shouldShowCitationLoading,在引用未就绪时只显示
  「正在整理引用」,就绪后用 createCitationMarkdownComponents 渲染正文;
  支持可选 remarkPlugins、rehypePlugins、isHuman、img。

- 重构 MessageListItem:助手消息正文仅通过 SafeCitationContent 渲染,
  删除重复的 useParsedCitations、shouldShowCitationLoading、
  createCitationMarkdownComponents、CitationsLoadingIndicator 等逻辑;
  用户消息仍用 AIElementMessageResponse,不做引用解析。

- 澄清、present-files(message-list)、思考步骤与 write_file 加载
  (message-group)、子任务结果(subtask-card)均使用
  SafeCitationContent;Artifact 的 markdown 预览仍用同一 guard
  shouldShowCitationLoading,正文由 ArtifactFilePreview 渲染。

- 统一加载条件:shouldShowCitationLoading(rawContent, cleanContent,
  isLoading) 为唯一判断。在「流式中且已有引用块」或「有引用块且
  cleanContent 中仍有未替换的 [cite-N]」时仅显示加载,从而在 Pro/Ultra
  下也能看到「正在整理引用」,且永不出现半成品 [cite-N]。

- message-group 的 write_file:用 shouldShowCitationLoading(
  fileContent, cleanContent, threadIsLoading && isLast) 替代
  hasCitationsBlock + threadIsLoading,与其他场景一致。

- citations/utils:流式时解析未闭合的 <citations>;移除
  isCitationsBlockIncomplete;hasUnreplacedCitationRefs 保持内部使用;
  在文件头注释中说明展示规则。
2026-02-09 15:01:51 +08:00
LofiSu
0fff2880d3 Merge upstream/experimental and resolve conflicts; citations + path_utils + mode-hover
## 冲突解决 (Resolve conflicts)
- input-box.tsx: 保留 ModeHoverGuide 包裹的模式选择器(PR #26 的 mode-hover-guide)
- message-group.tsx: 保留 getCleanContent / hasCitationsBlock / useParsedCitations
- message-list-item.tsx: 保留 useParsedCitations,移除重复的 MessageLink(使用 CitationAwareLink)
- artifact-file-detail.tsx: 保留 CitationAwareLink、useParsedCitations、contentWithoutCitationsFromParsed
- artifacts.py: 保留 path_utils 与 _extract_citation_urls + remove_citations_block 精简实现
- citations/index.ts: 保留并补充 contentWithoutCitationsFromParsed 导出
- en-US.ts: 保留 Ultra 模式描述 "Reasoning, planning and execution with subagents..."
- zh-CN.ts: 保留「超级」标签,描述保留「思考、计划并执行,可调用子代理分工协作...」

## PR #26 代码改动汇总

### 1. Citations(引用)
- lead_agent prompt: 增加 Web search 与子代理合成时的 citation 提示
- general_purpose: 子代理 system prompt 增加 <citations_format> 说明
- frontend utils: 新增 contentWithoutCitationsFromParsed,removeAllCitations 基于单次解析
- frontend artifact: 使用 contentWithoutCitationsFromParsed(parsed) 避免对同一内容解析两次
- backend artifacts: _extract_citation_urls + remove_citations_block,json 提到顶部

### 2. path_utils(路径解析)
- 新增 backend/src/gateway/path_utils.py:resolve_thread_virtual_path,防 path traversal
- artifacts.py / skills.py:删除内联路径解析,统一使用 path_utils

### 3. Mode hover guide
- input-box: 模式选择器外包 ModeHoverGuide,悬停展示模式说明

### 4. i18n
- en: ultraModeDescription 与 zh: ultraMode / ultraModeDescription 与上游对齐并保留 PR 文案

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 13:01:01 +08:00
ruitanglin
58e10b0bca Merge upstream/experimental and resolve conflicts; citations + path_utils + mode-hover
## 冲突解决 (Resolve conflicts)
- input-box.tsx: 保留 ModeHoverGuide 包裹的模式选择器(PR #26 的 mode-hover-guide)
- message-group.tsx: 保留 getCleanContent / hasCitationsBlock / useParsedCitations
- message-list-item.tsx: 保留 useParsedCitations,移除重复的 MessageLink(使用 CitationAwareLink)
- artifact-file-detail.tsx: 保留 CitationAwareLink、useParsedCitations、contentWithoutCitationsFromParsed
- artifacts.py: 保留 path_utils 与 _extract_citation_urls + remove_citations_block 精简实现
- citations/index.ts: 保留并补充 contentWithoutCitationsFromParsed 导出
- en-US.ts: 保留 Ultra 模式描述 "Reasoning, planning and execution with subagents..."
- zh-CN.ts: 保留「超级」标签,描述保留「思考、计划并执行,可调用子代理分工协作...」

## PR #26 代码改动汇总

### 1. Citations(引用)
- lead_agent prompt: 增加 Web search 与子代理合成时的 citation 提示
- general_purpose: 子代理 system prompt 增加 <citations_format> 说明
- frontend utils: 新增 contentWithoutCitationsFromParsed,removeAllCitations 基于单次解析
- frontend artifact: 使用 contentWithoutCitationsFromParsed(parsed) 避免对同一内容解析两次
- backend artifacts: _extract_citation_urls + remove_citations_block,json 提到顶部

### 2. path_utils(路径解析)
- 新增 backend/src/gateway/path_utils.py:resolve_thread_virtual_path,防 path traversal
- artifacts.py / skills.py:删除内联路径解析,统一使用 path_utils

### 3. Mode hover guide
- input-box: 模式选择器外包 ModeHoverGuide,悬停展示模式说明

### 4. i18n
- en: ultraModeDescription 与 zh: ultraMode / ultraModeDescription 与上游对齐并保留 PR 文案

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 13:01:01 +08:00
LofiSu
30e1760211 refactor(frontend): simplify and deduplicate Citation-related code
- Extract removeCitationsBlocks in utils, reuse in parseCitations and removeAllCitations
- Add hasCitationsBlock; isCitationsBlockIncomplete now uses it
- Add useParsedCitations hook (parseCitations + buildCitationMap) for message/artifact
- Add CitationAwareLink to unify link rendering (message-list-item + artifact-file-detail)
- Add getCleanContent helper; message-group uses it and useParsedCitations
- ArtifactFileDetail: single useParsedCitations, pass cleanContent/citationMap to Preview
- Stop exporting buildCitationMap and removeCitationsBlocks from citations index
- Remove duplicate MessageLink and inline link logic in artifact preview

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 12:13:06 +08:00
ruitanglin
175c1d2e3b refactor(frontend): simplify and deduplicate Citation-related code
- Extract removeCitationsBlocks in utils, reuse in parseCitations and removeAllCitations
- Add hasCitationsBlock; isCitationsBlockIncomplete now uses it
- Add useParsedCitations hook (parseCitations + buildCitationMap) for message/artifact
- Add CitationAwareLink to unify link rendering (message-list-item + artifact-file-detail)
- Add getCleanContent helper; message-group uses it and useParsedCitations
- ArtifactFileDetail: single useParsedCitations, pass cleanContent/citationMap to Preview
- Stop exporting buildCitationMap and removeCitationsBlocks from citations index
- Remove duplicate MessageLink and inline link logic in artifact preview

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 12:13:06 +08:00
LofiSu
2d70aaa969 fix(frontend): citations display + refactor link/citation utils
- Citations: no underline while streaming (message links); artifact markdown external links as citation cards
- Refactor: add isExternalUrl, syntheticCitationFromLink in core/citations; shared externalLinkClass in lib/utils; simplify message-list-item and artifact-file-detail link rendering

修复引用展示并抽离链接/引用工具
- 引用:流式输出时链接不这下划线;Artifact 内 Markdown 外链以引用卡片展示
- 重构:core/citations 新增 isExternalUrl、syntheticCitationFromLink;lib/utils 共享 externalLinkClass;精简消息与 Artifact 中的链接渲染逻辑

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 04:03:15 +08:00
ruitanglin
509ea874f7 fix(frontend): citations display + refactor link/citation utils
- Citations: no underline while streaming (message links); artifact markdown external links as citation cards
- Refactor: add isExternalUrl, syntheticCitationFromLink in core/citations; shared externalLinkClass in lib/utils; simplify message-list-item and artifact-file-detail link rendering

修复引用展示并抽离链接/引用工具
- 引用:流式输出时链接不这下划线;Artifact 内 Markdown 外链以引用卡片展示
- 重构:core/citations 新增 isExternalUrl、syntheticCitationFromLink;lib/utils 共享 externalLinkClass;精简消息与 Artifact 中的链接渲染逻辑

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 04:03:15 +08:00