mirror of
https://github.com/multica-ai/multica.git
synced 2026-08-02 18:13:27 +02:00
agent/lambda/cf286ff2
13 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
39f43a9a98 |
refactor(editor): unify attachment rendering into a single <Attachment> component (#2850)
Collapse the five separate attachment render paths (file-card NodeView,
image NodeView, readonly markdown img/fileCard renderers, AttachmentList
standalone fallback, and the parallel packages/ui/markdown renderer) into
one <Attachment attachment={a} /> dispatcher.
Fixes a P0 visual regression: a PNG attached to a message but not inlined
in the markdown body used to render as a gray "file card" because
getPreviewKind() lacked an "image" branch and image rendering bypassed
the dispatcher entirely. Now every surface routes through <Attachment>,
so the same PNG renders as a real <img> with hover toolbar and
preview-modal everywhere.
Key changes:
- PreviewKind gains "image"; getPreviewKind() detects image/* + common
extensions before the html/text branches (so svg stays image, not text).
- AttachmentPreviewModal gains case "image" (replaces the standalone
ImageLightbox, which is deleted).
- New packages/views/editor/attachment.tsx owns all kind-aware routing
(image | html | file) and dispatches preview modal + download via the
existing useAttachmentPreview / useDownloadAttachment hooks. Subsumes
the deleted AttachmentBlock.
- AttachmentInput.url accepts a forceKind hint so callers that *know*
the structural kind (markdown , Tiptap image node) skip the
filename-based autodetect — fixes a regression where empty or
descriptive alt text would route an image to the file-card chrome.
- Tiptap NodeViews (file-card.tsx, image-view.tsx) shrink to thin
wrappers that forward editor hints (selected, deleteNode, uploading)
to <Attachment>.
- ReadonlyContent and AttachmentList each mount their own
AttachmentDownloadProvider so url → record resolution works outside
ContentEditor's provider.
- packages/ui/markdown gains optional renderImage / renderFileCard slot
props; packages/views/common/markdown.tsx injects <Attachment> into
those slots and threads message attachments through to chat /
skill-file viewers.
- chat-message-list passes message.attachments to every <Markdown> call
site and renders a standalone AttachmentList under each bubble for
attachments not referenced in the body.
Tests: attachment.test.tsx covers 9 scenarios (record image / pdf / html;
url-only image with resolver hit and miss; uploading state; editable
delete; forceKind regression). attachment-preview-modal.test.tsx gains
image-dispatch cases. 652/652 unit tests pass.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
681d720671 |
fix(issues): file-card render for self-host with local storage (#2349)
* fix(issues): file-card render for self-host with local storage Fixes #1520. When self-hosting without S3, the upload handler returns site-relative URLs like /uploads/workspaces/<wsId>/<file>. Four frontend regexes only matched https?://, so persisted !file[name](/uploads/...) markdown failed to parse and leaked through as raw text in the issue view, chat, skill file viewer, and board card preview. Narrow allow-list: the relative branch only accepts /uploads/ — not any /-prefixed href — so protocol-relative //evil.com/x, path-traversal /../api/x, and other internal /api/... paths are rejected. Without this, a stored file-card with an attacker-chosen filename and a //host/x href would turn into a one-click external-site jump via window.open from inside an issue (per review feedback on #2349). Single source of truth: packages/ui/markdown/file-cards.ts now exports isAllowedFileCardHref + FILE_CARD_URL_PATTERN. The four sites use one of them, so the next regression is cheaper than restoring four parallel regexes. - packages/ui/markdown/file-cards.ts: helper + URL pattern. - packages/views/editor/extensions/file-card.tsx: Tiptap tokenizer composes from FILE_CARD_URL_PATTERN. - packages/views/editor/readonly-content.tsx: sanitiser uses helper. - packages/ui/markdown/Markdown.tsx: sanitiser uses helper. - packages/views/issues/components/board-card.tsx: strip markdown tokens from the line-clamped board preview so raw !file[...] no longer leaks there either. - packages/ui/markdown/file-cards.test.ts: covers accept (/uploads/ok, https://cdn/x) and reject (javascript:, data:, //evil.com/x, /../api/x, /api/x, empty, ftp:, bare 'uploads/x') for both the helper and the parser composed from the pattern. javascript:, data:, and other dangerous schemes remain rejected. * test(markdown): move file-card href allow-list test into @multica/views Per review feedback on #2349: keep the test where vitest is already running instead of bootstrapping a new test runner inside @multica/ui. The test now lives at packages/views/editor/file-card-href.test.ts and imports isAllowedFileCardHref / FILE_CARD_URL_PATTERN / preprocessFileCards from the @multica/ui/markdown public surface, exercising the same 30 cases. Reverts the @multica/ui package.json test script + vitest devDep + the local vitest.config.ts that the previous commit added; the package goes back to typecheck + lint only, matching every other ui-only package in the monorepo. --------- Co-authored-by: Lalbadshah <11599756+Lalbadshah@users.noreply.github.com> |
||
|
|
19c40c5d68 |
fix(ui): translate hardcoded English strings in shared ui package (#2526)
The four user-visible strings exposed by packages/ui rendered untranslated on every page that used them: - file-upload-button.tsx — "Attach file" aria-label/title - sidebar.tsx — "Toggle Sidebar" sr-only label/aria-label/title - pagination.tsx — "Go to previous/next page" aria-labels - CodeBlock.tsx — "plain text" language fallback + "Copy code" aria-label/tooltip Root cause: the package had no i18n hookup at all because the package boundary rule forbids importing @multica/core. Replicating the pattern five times would have been the same hack five times. Hooking up react-i18next directly is the structurally clean fix — i18next is a generic library, not business logic, and the upstream I18nextProvider already exposes the instance via context. To let packages/ui typecheck the selector form standalone (i.e. without the views resource-types augmentation in scope), the augmentation is split: views declares everything except the `ui` namespace on a new global `I18nResources` interface, and packages/ui contributes the `ui` slice via declaration merging in packages/ui/types/i18next.ts. Views' resources-types side-effect-imports that file so both packages see the merged shape during downstream typechecks. Scope intentionally excludes: - packages/ui/components/common/error-boundary.tsx — keeping its fallback in English so a render-time crash never depends on i18n being healthy. - apps/desktop/src/renderer/src/components/update-notification.tsx — ships with the next desktop release, not via this PR. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
21e3cfaa01 |
Agent runtime status redesign: split presence into availability + last-task (#1794)
* feat(agent-status): add workspace live-tasks endpoint and TaskFailureReason type Lays the API + type contract for the front-end agent presence cache: - New `GET /api/active-tasks` returns active (queued/dispatched/running) tasks plus failed tasks within the last 2 minutes for the current workspace. The 2-minute window powers a UI-side auto-clearing "Failed" agent state without back-end pollers. - `agent_task_queue` has no workspace_id column, so the query JOINs agent; `SELECT atq.*` keeps `failure_reason` (migration 055) on the wire. - Adds `TaskFailureReason` to `AgentTask` so the UI can map the 5 backend classifiers (agent_error / timeout / runtime_offline / runtime_recovery / manual) to copy without parsing free-text errors. - New `api.getActiveTasksForWorkspace()` client method; workspace is resolved server-side from the X-Workspace-Slug header (no path param, matching /api/agents and /api/runtimes conventions). Includes the joint engineering plan and designer brief that scope the broader Agent / Runtime status redesign — Phase 0 is this contract plus the front-end derivation layer landing in the next commit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(agent-status): derive presence/health states with WS sync and desktop IPC bridge Adds the front-end derivation layer that turns raw server data into the user-facing 5-state agent / 4-state runtime enums. UI files are deliberately untouched in this commit — derivation lives behind hooks (useAgentPresence, useRuntimeHealth) that any component can call with zero additional network traffic. Architecture: - Derivation is pure functions in packages/core/{agents,runtimes}; the back-end stays free of UI translation. Agents algorithm: runtime offline > recent failed (2-min window) > running > queued > available. Runtimes algorithm: status + last_seen_at -> online / recently_lost / offline / about_to_gc. - A single workspace-wide active-tasks query backs all per-agent presence reads, eliminating N+1 across hover cards, list rows, and pickers. 30-second tick re-renders the hooks so the failed window expires even when no underlying data changes. - WS task lifecycle events (dispatch / completed / failed / cancelled) invalidate active-tasks via the prefix dispatcher. completed/failed were removed from specificEvents so they go through both the prefix invalidate and the existing chat ws.on() handlers. Reconnect refetch picks up active-tasks too. - Desktop bridges window.daemonAPI.onStatusChange directly into the runtimes cache via setQueryData, giving the local daemon sub-second feedback (vs. 75s server sweep). Bridge is wsId-bound so workspace switches automatically rebind the subscription; daemon_id matching covers the same-daemon-multiple-providers case. 24 derivation unit tests cover all branches plus null/empty/boundary inputs (FAILED_WINDOW_MS edges, null last_seen_at, missing completed_at). Full core suite: 112 tests passing. Typecheck green across all 8 workspace packages. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(agent-status): redesign agent runtime status as two orthogonal dimensions Splits the conflated 5-state agent presence into two independent axes: - AgentAvailability (3-state): online / unstable / offline — drives the dot indicator everywhere a dot appears. Pure runtime reachability; never sticky-red because of a past task outcome. - LastTaskState (5-state): running / completed / failed / cancelled / idle — surfaced as text + icon on focused surfaces (hover card, agent detail page, agents list, runtime detail). Never colours the dot. Major changes: * Domain layer: AgentPresence union → AgentAvailability + LastTaskState. derive-presence split into deriveAgentAvailability + deriveLastTaskState + deriveAgentPresenceDetail orchestrator. Tests reorganised into three groups (availability invariants, last-task invariants, composition). * Visual config: presenceConfig (5 entries) → availabilityConfig (3) + taskStateConfig (5). availabilityOrder + lastTaskOrder for filter chips. * Workspace-level presence prefetch: new useWorkspacePresencePrefetch hook + WorkspacePresencePrefetch mount component, wired into DashboardLayout (web) and WorkspaceRouteLayout (desktop). Hover cards render synchronously with no skeleton flash on first hover. * ActorAvatar hover: flipped default — disableHoverCard removed, enableHoverCard added (default false). Opt-in at ~14 decision-moment surfaces; pickers / decoration sub-chips stay plain. Status dot decoupled (showStatusDot prop) so picker rows can show presence without nesting popovers. * Hover cards: AgentProfileCard simplified — availability dot only, Detail link top-right (logs live on the detail page). New MemberProfileCard mirrors the structure: name + role + email + top-2 owned agents (sorted by 30d run count) with click-through to agent detail. * Agents list: split Status into two columns — availability (3-color dot + label) and Last run (task icon + label, optional running counts). Two independent filter chip groups (Status + Last run); combination acts as intersection ("online + failed" finds broken- but-alive agents). * Other UI surfaces (issue list/board/detail, comments, autopilots, projects, runtimes, mention autocomplete, subscribers picker) updated to the new dot semantics; status dot now strictly 3-color. Server changes accompany the client redesign — workspace-wide agent-task-snapshot endpoint, runtime usage queries, etc. — to feed the derive layer with the data it needs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(agent-detail): drop last-task chip from detail header + inspector The Recent work section on the agent detail page already shows the same data (with task titles, timestamps, error context) — surfacing "Completed" / "Failed" / etc. up in the header was redundant chrome. Detail surfaces now show only the 3-state availability dot. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(tables): handle narrow viewports across agents / skills / runtimes Three table layouts were squeezing content into adjacent cells at intermediate widths. Each fix is small and targeted: * runtime-list: the Runtime cell's base name had `shrink-0`, so it refused to truncate when its grid column was narrowed under width pressure — the name visually overflowed into the Health column ("ClaudeOnline" etc). Removed shrink-0, added truncate. The Health column was also a fixed 9.5rem reservation for the worst-case "Recently lost · 2m 14s ago" copy; switched to minmax(0,1fr) so it competes fairly with Runtime. * skills-page: had a single grid template with no responsive breakpoints — all 6 columns were rendered at any width and got visually jammed below md. Added a <md template that drops Source + Updated; the row markup hides those cells via `hidden md:block` / `md:contents`. * agent-list-item: the new Last run column was reserved at minmax(8rem, max-content); on narrow md viewports the 8rem floor pushed the row past available width. Changed to minmax(0,max-content) so the cell shrinks under pressure (its content already truncates). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(agent-card): hover-only Detail + add Runtime row + breathing room Three small polish tweaks to the agent hover card: - Detail link gets `mr-1` + fades in only on card hover (group-hover). It was visually flush against the popover edge and competing for attention; now it stays out of the way during a quick glance and surfaces only when the user is dwelling on the card. - Runtime row is back, in the meta block (cloud/local icon + runtime name). The earlier removal was over-aggressive — knowing where an agent runs is part of "who is this agent". The wifi badge stays dropped because the availability dot in the header already conveys reachability. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(runtime): wifi-style health icon (4-state) for runtime list + agent card Replaces the 6px coloured dot with a wifi-shape icon that carries both state (Wifi vs WifiOff) and severity (success/warning/muted/destructive). Mapping: - online → Wifi (success) - recently_lost → WifiHigh (warning) — transient hiccup, fewer bars - offline → WifiOff (muted) — long unreachable - about_to_gc → WifiOff (destructive) — sweeper coming soon Used in two places: - Runtime list: replaces HealthDot in the dedicated leading-icon column. Bumped the column from 0.5rem (dot-sized) to 0.875rem (icon-sized). - Agent profile card RuntimeRow: derives runtime health from runtime + clock (matching the 4-state semantics) and renders HealthIcon next to the runtime name. Cloud runtimes always read as online. The duplicate signal with the header availability dot is intentional — it confirms WHICH runtime is the one currently in the dot's state. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
c381d59c7a |
fix: preserve authored markdown links during linkify (#1761)
Co-authored-by: Eve <eve@multica.ai> |
||
|
|
d14265de2a |
fix(comments): preserve newlines from agent CLI writes (#1744)
* fix(comments): preserve newlines from agent CLI writes Agents (e.g. Codex) routinely emit `multica issue comment add --content "para1\n\npara2"` because Python/JSON-style string literals are their default. Bash does not expand `\n` inside double quotes, so the literal 4-char sequence flowed through the CLI into the database and rendered as text in the issue panel — comments came out as one wall of prose. Three coordinated fixes so the platform behavior no longer depends on whether a given model has strong bash-quoting intuition: - CLI: decode `\n / \r / \t / \\` in `--content` and `--description` for `issue create / update / comment add` (callers needing a literal backslash still have `--content-stdin`). - Agent prompt: rewrite the comment-add example in the injected runtime config to require `--content-stdin` + HEREDOC for any multi-line body, and call out the same rule for `--description`. The previous wording flagged stdin only for "backticks, quotes", which models read as irrelevant to plain paragraphs. - Renderer: add `remark-breaks` to the shared Markdown plugin chain so a bare `\n` becomes a visible line break instead of a CommonMark soft break — protects against models that emit single newlines for formatting. Tests: pin the new CLI helper, and pin the runtime-config guidance so the multi-line wording cannot decay back into a footnote. * fix(comments): address review feedback on newline-rendering PR - Cover the issue panel: ReadonlyContent (used by every comment card and the issue description) has its own react-markdown wiring; add remark-breaks there too so the renderer fix actually applies to the surface the bug was reported on, not just the chat panel. Pinned by ReadonlyContent line-break tests. - Make the prompt's `--description` guidance executable: add `--description-stdin` to `issue create` / `issue update`, refactor comment-add to share a single `resolveTextFlag` helper, and have the injected runtime config name the real flag instead of an imaginary "stdin / a tempfile" path. Pinned by the runtime-config guidance test. - Document the unescape contract on each affected flag's help text and pin the precise boundary in tests: `\n / \r / \t / \\` are decoded; `\d / \w / \s / \u / \0` and other unrecognised escapes pass through verbatim, so regex literals and Windows paths survive intact unless they embed a literal `\n` / `\r` / `\t`. Callers that need the literal sequence have `--content-stdin` / `--description-stdin` as the escape hatch. |
||
|
|
c3ae212b40 |
fix(markdown): treat CJK full-width punctuation as URL boundary (#1630)
linkify-it only recognizes ASCII characters as URL boundaries. In Chinese or Japanese text a URL followed by "。" (or any other full-width punctuation) was greedily swallowed into the URL along with everything up to the next whitespace, producing hrefs like `https://.../pull/1623。merge` that 404 when clicked. Truncate the detected URL at the first CJK full-width punctuation character and re-scan the tail, so adjacent URLs separated only by full-width punctuation are still each linked individually. The terminator character set mirrors the fix applied in mattermost/marked#22. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
fa7e4cbdca |
Feat/la te x (#1365)
* 排除提交文件 * feat(editor): 添加数学公式渲染支持 - 集成 KaTeX 库用于数学公式渲染 - 在编辑器样式中添加数学节点相关 CSS 样式 - 实现 BlockMathExtension 和 InlineMathExtension 两个数学公式扩展 - 为 Markdown 组件添加 remarkMath 和 rehypeKatex 插件支持 - 在 package.json 中添加 katex、remark-math、rehype-katex 依赖 - 更新 pnpm-lock.yaml 文件以包含新的依赖包 - 为只读内容组件添加数学公式渲染功能 - 创建 math.tsx 文件实现数学公式节点的完整功能 - 添加只读内容的数学公式渲染测试用例 |
||
|
|
53cb01cc91 |
refactor(editor): remove hardcoded CDN domain, unify file card rendering
- Add GET /api/config endpoint exposing cdn_domain from CLOUDFRONT_DOMAIN - Create packages/core/config/ zustand store, fetched at app startup - Extract file card preprocessing to packages/ui/markdown/file-cards.ts with isCdnUrl(url, cdnDomain) using exact hostname match - Add file card support to packages/ui/markdown/Markdown.tsx (was missing) - Remove hardcoded .copilothub.ai hostname check from file-card.tsx - Fix LocalStorage.CdnDomain() to return hostname not full URL - Always run preprocessFileCards regardless of cdnDomain availability (!file syntax works without CDN domain, only legacy matching needs it) - Use useConfigStore hook in common/markdown.tsx for reactive updates Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
a744cd4f45 |
feat(chat): redesign state, header, and unread tracking
State management - Pending task / live timeline are now Query-cache single source; Zustand mirror removed (fixes duplicate assistant render caused by the invalidate→refetch race window) - WS subscriptions moved from ChatWindow to global useRealtimeSync so pending state survives minimize and refresh - New GET /chat/sessions/:id/pending-task to recover live state on mount - Drafts persisted per-session (was per-workspace) Unread tracking - Migration 040: chat_session.unread_since (event-driven; old chats stay clean — no mass backfill) - POST /chat/sessions/:id/read clears unread; broadcasts chat:session_read so other devices sync - New GET /chat/pending-tasks aggregate for the FAB - ChatFab: brand-color impulse animation while running, brand-dot badge of unread session count - ChatWindow auto-marks read when user is viewing the session Header redesign - Two independent dropdowns: agent (avatar + name + My/Others grouping) at the input bottom-left; session (title + agent avatar) in the header - ⊕ new-chat button replaces the old + and history buttons - Session dropdown lists all sessions across agents with avatars - Empty state: 3 clickable starter prompts that send immediately - Mention link renderer falls through to default span on null — fixes @member/@agent/@all silently disappearing app-wide - User messages render through Markdown - Enter submits in chat input only (with IME guard + codeBlock skip); bubble menu hidden in chat Misc - Partial index on agent_task_queue for fast pending-task lookup - 2 new storage keys added to clearWorkspaceStorage - useMarkChatSessionRead has onError rollback - chat.* namespace logs across store, mutations, components, realtime Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
6c5879215d |
fix: sanitize markdown rendering in comments and shared renderers (#679)
* fix: sanitize markdown rendering in comments and shared renderers Add rehype-sanitize to both ReadonlyContent and Markdown components so that raw HTML parsed by rehype-raw is sanitized against a strict allowlist before reaching the DOM. On the backend, add a bluemonday sanitization pass when creating and updating comments to strip dangerous tags as defense-in-depth. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: add mention:// protocol to sanitize allowlist and validate file card URLs - Add mention:// to rehype-sanitize protocols.href in both ReadonlyContent and Markdown so @mention links survive sanitization - Validate data-href on file cards to only allow http(s) URLs, blocking javascript: and data: schemes in both frontend click handler and backend bluemonday policy - Narrow class attribute allowlist to specific elements (code, div, span, pre) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
ba32f3a187 |
chore: add shared ESLint config + enforce strict tsconfig across packages
- Add @multica/eslint-config package (base, react, next configs) - Replace `next lint` (removed in Next.js 16) with `eslint .` - Add lint scripts to all packages and desktop app - Add noUnusedLocals, noUnusedParameters, noImplicitReturns to base tsconfig - Fix all resulting TS/ESLint errors (unused imports, missing returns, stale eslint-disable comments from legacy eslint-config-next) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
35828492d5 |
feat(ui): extract packages/ui — shared atomic UI layer
- Move 55 shadcn components → packages/ui/components/ui/ - Move lib/utils.ts (cn function) → packages/ui/lib/ - Move 3 DOM hooks (auto-scroll, mobile, scroll-fade) → packages/ui/hooks/ - Extract CSS design tokens (@theme + :root + .dark) → packages/ui/styles/tokens.css - Refactor 3 common components to pure-props (actor-avatar, mention-hover-card, reaction-bar) - Move 6 markdown components with renderMention slot for IssueMentionCard decoupling - Create wrapper components in apps/web/ for data-aware ActorAvatar and Markdown - Update 116 import paths across apps/web/ - Add @source directives for Tailwind to scan packages Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |