237 Commits

Author SHA1 Message Date
Black
0427fd8cc7 fix(daemon): refresh workspace repos on checkout miss (#1085)
Co-authored-by: black-fe <black-fe@gate.me>
2026-04-15 19:10:54 +08:00
Jiayuan Zhang
d930bcaa18 feat(server): trigger agent when issue moves out of backlog (#1006)
* feat(server): trigger agent when issue moves out of backlog

When a member moves an agent-assigned issue from "backlog" to an active
status (e.g. "todo", "in_progress"), enqueue an agent task so the agent
starts working. This lets backlog act as a parking lot where issues can
be assigned to agents without immediately triggering execution.

Applies to both single and batch issue updates.

* fix(server): treat backlog as parking lot — no trigger on create/assign

Address review feedback: creating or assigning an agent to a backlog
issue no longer triggers immediate execution. Only moving out of backlog
to an active status triggers the agent, producing exactly one task.

- shouldEnqueueAgentTask now gates on backlog status
- backlog→active trigger uses isAgentAssigneeReady directly
- Added TestBacklogNoTriggerOnCreate test
- Updated TestBacklogToTodoTriggersAgent to assert exactly 1 task
  across the full create→move path (no manual cleanup)

* feat(ui): show toast hint when assigning agent to backlog issue

Users may not know that backlog issues won't trigger agent execution
until moved to an active status. Show an actionable toast with a
"Move to Todo" button when:

- Assigning an agent to a backlog issue in the detail page
- Creating a backlog issue with an agent assignee

* feat(ui): add "Don't show again" option to backlog agent toast

Users who understand the backlog parking lot behavior can dismiss the
hint permanently. Uses localStorage to persist the preference.

* feat(ui): replace backlog agent toast with AlertDialog

Use a modal dialog instead of a toast notification so users must
explicitly acknowledge the hint. The dialog offers three options:
- "Move to Todo" — changes status and triggers the agent
- "Keep in Backlog" — dismisses without action
- "Don't show again" — persists dismissal in localStorage

* fix(ui): improve backlog agent dialog

* fix(ui): close create dialog behind hint, use checkbox for don't-show-again

1. Create Issue dialog now closes when the backlog agent hint appears,
   so only the hint dialog is visible (not stacked behind).
2. "Don't show again" is now a checkbox instead of a separate button.
   When checked, clicking either "Keep in Backlog" or "Move to Todo"
   persists the preference.

* fix(ui): smooth backlog agent hint dialog

* fix(test): add useUpdateIssue mock to create-issue test

The test mock for @multica/core/issues/mutations was missing the
useUpdateIssue export that create-issue.tsx now imports, causing
CI failure.
2026-04-15 19:07:48 +08:00
Bohan Jiang
ce447c7f06 feat(agent): add custom CLI arguments support (#986)
* feat(agent): add custom CLI arguments support

Allow users to configure custom CLI arguments per agent that get
appended to the agent subprocess command at launch time. This enables
use cases like specifying different models (--model o3), max turns,
or other provider-specific flags without needing separate runtimes.

Changes:
- Add custom_args JSONB column to agent table (migration 041)
- Update API handler to accept/return custom_args in create/update
- Pass custom_args through claim endpoint to daemon
- Append custom_args to CLI commands for all agent backends
- Add ExecOptions.CustomArgs field in agent package
- Add Custom Args tab in agent detail UI
- Add --custom-args flag to CLI agent create/update commands

Closes MUL-802

* fix(agent): filter protocol-critical flags from custom_args

Add per-backend filtering of custom_args to prevent users from
accidentally overriding flags that the daemon hardcodes for its
communication protocol (e.g. --output-format, --input-format,
--permission-mode for Claude).

This follows the same pattern as custom_env's isBlockedEnvKey: we
only block the small, stable set of flags that would break the
daemon↔agent protocol — not every possible dangerous flag. Workspace
members are trusted for everything else.

Each backend defines its own blocked set:
- Claude: -p, --output-format, --input-format, --permission-mode
- Gemini: -p, --yolo, -o
- Codex: --listen
- OpenCode: --format
- OpenClaw: --local, --json, --session-id, --message
- Hermes: none (ACP is positional)

Includes unit tests for the filtering logic.

* fix(agent): address code review nits for custom_args

- Replace module-level `nextArgId` counter with `crypto.randomUUID()`
  in custom-args-tab.tsx to avoid SSR ID conflicts
- Add unit tests for custom args passthrough and blocked-arg filtering
  in both Claude and Gemini arg builders
2026-04-15 14:58:53 +08:00
Jiayuan Zhang
f94b0100cd refactor(autopilot): remove broken concurrency policies and fix multiple bugs (#1048)
Remove the concurrency_policy system (skip/queue/replace) — skip had an
orphan bug that permanently blocked triggers, queue didn't actually queue,
and replace didn't cancel running tasks. Every trigger now simply executes.

Bug fixes:
- Listener now handles in_review status (was silently ignored)
- Issue deletion fails linked autopilot runs before DELETE (prevents orphans)
- ComputeNextRun rejects invalid timezones instead of silent UTC fallback
- dispatchCreateIssue post-commit failures now properly fail the run

Reliability:
- Scheduler recovers lost triggers on startup (crash recovery)
- New index on autopilot_run(issue_id) for deletion lookups
- Migration 043 cleans up historical orphaned/skipped/pending runs
2026-04-15 13:48:21 +08:00
Bohan Jiang
762e64d469 fix(agent): restrict custom_env visibility to owner/admin (#1046)
* fix(agent): restrict custom_env visibility to agent owner and workspace admin

Agent environment variables (custom_env) were visible to all workspace
members, exposing sensitive tokens. Now only the agent owner and
workspace owner/admin can view them — regular members receive the field
omitted (null) from API responses, and the frontend hides the
Environment tab accordingly.

Closes #1018

* fix(agent): show masked env keys to non-authorized users instead of hiding tab

Instead of completely hiding the Environment tab for non-owner/non-admin
users, show the variable keys with masked values (****) in a read-only
view. This lets members see which variables are configured without
exposing the actual values.

- Backend: mask values with "****" instead of nullifying custom_env
- Added custom_env_redacted boolean to API response
- Frontend: EnvTab supports readOnly mode with lock icon and muted styling
2026-04-15 13:06:49 +08:00
Naiyuan Qing
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>
2026-04-15 10:43:36 +08:00
Jiayuan Zhang
d88fe2608e feat(autopilot): scheduled/triggered automations for AI agents (#1028)
* feat(autopilot): add scheduled/triggered automation for AI agents

Introduce the Autopilot feature — recurring automations that assign work
to AI agents on a schedule or manual trigger. Supports two execution
modes: create_issue (creates an issue for the agent to work on) and
run_only (directly enqueues an agent task without issue pollution).

Backend: migration (3 tables + 2 columns), sqlc queries, AutopilotService
with concurrency policies (skip/queue/replace), HTTP CRUD + trigger
endpoints, background cron scheduler (30s tick), event listeners for
issue→run and task→run status sync.

Frontend: types, API client methods, TanStack Query hooks with optimistic
mutations, realtime cache invalidation, list page with create dialog,
detail page with trigger management and run history, sidebar nav + routes
for both web and desktop apps.

* feat(autopilot): improve UX — trigger config, edit dialog, template gallery

- Replace raw cron input with friendly frequency tabs (Hourly/Daily/Weekdays/Weekly/Custom), time picker, and timezone dropdown defaulting to user's local timezone
- Fix Select components showing UUIDs instead of names (Base UI render function pattern)
- Add Edit button on detail page opening a unified edit dialog
- Remove project/concurrency/issue-title-template from create/edit (simplify for users)
- Add trigger configuration inline during autopilot creation
- Add template gallery on empty state (6 step-by-step workflow templates)
- Rename "Description" to "Prompt" throughout UI
- Inject autopilot run timestamp into issue description for agent date awareness
- Treat issue status "in_review" as run completion (fixes skip on next trigger)
- Make migration idempotent with IF NOT EXISTS clauses
2026-04-15 04:54:37 +08:00
Bohan Jiang
60c5848794 feat(invitation): dedicated /invite/{id} page for accepting invitations (#1023)
The email CTA now deep-links to /invite/{id} instead of the generic app
URL. If the user isn't logged in, they're redirected to login with a
?next= param that brings them back to the invite page.

Changes:
- Backend: GET /api/invitations/{id} endpoint (enriched with workspace/inviter names)
- Backend: Email template now links to /invite/{invitationId}
- Frontend: Shared InvitePage component (packages/views/invite/)
- Frontend: Web route at (auth)/invite/[id], Desktop route at invite/:id
- Frontend: /invite/ excluded from navigation history persistence
2026-04-15 00:37:53 +08:00
Bohan Jiang
1163f684fb feat(invitation): send email notification when inviting a user (#1021)
Uses the existing Resend email service to notify invitees.
Email includes inviter name, workspace name, and a link to the app.
Sent fire-and-forget in a goroutine to avoid blocking the API response.
2026-04-15 00:17:21 +08:00
Bohan Jiang
ff1d348274 feat(security): invitation acceptance flow for workspace members (#1019)
* feat(security): replace instant member-add with invitation acceptance flow

Users invited to a workspace must now explicitly accept the invitation
before becoming a member. This fixes the security vulnerability where
knowing someone's email was enough to auto-register their runtime to
your workspace.

Changes:
- Add workspace_invitation table with pending/accepted/declined/expired states
- Replace CreateMember with CreateInvitation (same endpoint, new behavior)
- Add accept/decline/revoke/list invitation API endpoints
- Add invitation WS events for real-time notification
- Frontend: invitation accept/decline UI in workspace switcher
- Frontend: pending invitations section in members settings tab

* fix(invitation): address PR review nits

- Fix invitation:revoked listener to send event to invitee user (was no-op)
- Remove duplicate queryClient2 in app-sidebar.tsx, reuse existing queryClient
- Add expires_at > now() filter to ListPendingInvitationsByWorkspace query
2026-04-15 00:01:18 +08:00
Bohan Jiang
b4b69f89f6 fix(server): allow members to create and manage their own skills (#1017)
Remove admin/owner-only restriction from skill creation and import routes.
Add canManageSkill helper that lets skill creators manage their own skills,
matching the existing canManageAgent pattern for agents.
2026-04-14 23:48:57 +08:00
devv-eve
40aa23a528 feat(desktop): daemon management panel with sidebar status bar (#952)
* feat(desktop): add daemon management panel with sidebar status bar

Integrate multica daemon lifecycle management into the desktop app so
users can start/stop/restart the daemon and view live logs without
leaving the UI. Session tokens are automatically synced to the CLI
config file, making daemon authentication transparent.

- daemon-manager.ts: Electron main process module for daemon lifecycle
  (health polling, start/stop via CLI, token sync, log tail)
- Preload bridge: new daemonAPI with IPC for all daemon operations
- Sidebar bottomSlot: persistent daemon status indicator in sidebar
  footer (desktop-only, injected via AppSidebar slot)
- Daemon panel Sheet: right-side drawer with status details, controls,
  and real-time log viewer with auto-scroll and level coloring
- Token sync: on login and app startup, JWT is written to
  ~/.multica/config.json so daemon can authenticate seamlessly

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(desktop): add P1+P2 daemon features — runtimes card, auto-start, settings

P1: Runtimes page Local Daemon card
- Add topSlot prop to shared RuntimesPage for platform injection
- DaemonRuntimeCard shows status, agents, uptime with Start/Stop/
  Restart/Logs buttons (desktop-only, injected via slot)

P2: Auto-start and auto-stop
- Daemon auto-starts on app launch when user is authenticated
  (controlled by autoStart preference, default: true)
- Daemon auto-stops on app quit (controlled by autoStop preference,
  default: false — daemon keeps running in background by default)
- Preferences persisted to ~/.multica/desktop_prefs.json

P2: Daemon settings tab
- New "Daemon" tab in Settings > My Account section (desktop-only)
- Toggle auto-start and auto-stop behavior
- CLI installation status check with link to install guide
- SettingsPage gains extraAccountTabs prop for platform injection

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(desktop): address PR review feedback on daemon management

Must-fix:
- before-quit handler now calls event.preventDefault(), awaits
  stopDaemon(), then re-calls app.quit() so the daemon actually
  stops before the app exits
- Add concurrency guard (operationInProgress lock) in daemon-manager
  to reject overlapping start/stop/restart IPC calls
- Extract shared types (DaemonState, DaemonStatus, DaemonPrefs),
  constants (STATE_COLORS, STATE_LABELS), and formatUptime to
  apps/desktop/src/shared/daemon-types.ts — all renderer components
  now import from this single source

Should-fix:
- Log viewer uses monotonic counter (LogEntry.id) instead of array
  index as React key, preventing full re-renders on overflow
- All start/stop/restart handlers now show toast.error() with the
  error message when the operation fails
- startLogTail retries up to 5 times with 2s delay when the log
  file doesn't exist yet (handles first-run case)

Minor:
- Cache findCliBinary() result after first successful lookup

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(logger): suppress ANSI color codes when stderr is not a TTY

Detect whether stderr is connected to a terminal and set tint's NoColor
option accordingly. Previously daemon.log files contained raw escape
sequences like \033[2m and \033[92m which made them unreadable in the
Desktop log viewer and any non-TTY sink (docker logs, systemd, etc).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(daemon): runtime watch/unwatch HTTP endpoints and denylist

Add GET/POST/DELETE /watch handlers on the daemon's health port so
clients (notably Desktop) can add or remove watched workspaces at
runtime without restarting the daemon or editing config.json. Each
handler updates in-memory state under d.mu and persists back to
~/.multica/profiles/<name>/config.json for survival across restarts.

- CLIConfig gains UnwatchedWorkspaces as an explicit opt-out denylist.
  syncWorkspacesFromAPI skips entries in the denylist so a manual
  unwatch isn't silently revived 30s later by the periodic sync.
- loadWatchedWorkspaces tolerates an empty config and returns nil
  instead of erroring out, because Desktop starts daemons with a
  fresh profile and relies on the sync loop / watch endpoint to
  populate the list.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(desktop): bundled CLI, per-backend profile, and watch UI

Make the Desktop app self-sufficient: it bundles its own multica
binary, manages its own daemon profile keyed by the backend URL, and
authenticates that daemon with a long-lived PAT it mints on first
login. The daemon panel gains a checkbox list of watched workspaces
and surfaces the active profile + server URL.

CLI bootstrap
- scripts/bundle-cli.mjs copies server/bin/multica into
  apps/desktop/resources/bin/ before electron-vite dev and
  electron-builder package. asarUnpack: resources/** already covers
  this path, so the binary ships with the .app in prod.
- main/cli-bootstrap.ts adds an ensureManagedCli() fallback that
  downloads the latest release from GitHub when no bundled binary
  exists (first launch on a machine without developer tooling).
- daemon-manager.resolveCliBinary prefers bundled > managed > download
  > PATH, so local iteration uses the freshly built binary.

Daemon profile
- resolveActiveProfile now derives a desktop-<host> profile name from
  the target API URL and creates its config.json on demand. Never
  reads or writes the user's hand-configured CLI profiles, avoiding
  the "Desktop polluted my default profile" class of bug.
- syncToken detects a JWT input and exchanges it for a PAT via
  POST /api/tokens; caches the resulting mul_* token in the profile
  config so subsequent launches skip the round-trip.
- startDaemon / stopDaemon / log tail all operate on the resolved
  profile; renderer sets the target URL via a new
  daemon:set-target-api-url IPC.

Workspace watching
- daemon-manager exposes daemon:list-watched / daemon:watch-workspace /
  daemon:unwatch-workspace IPCs backed by the daemon's new /watch
  endpoints.
- App.tsx reconciles the user's workspace list against the daemon's
  watched set whenever TanStack Query updates it — new workspaces are
  registered instantly instead of waiting for the daemon's 30s sync,
  and removed workspaces are unwatched.
- daemon-panel gains a "Watched Workspaces" section with per-workspace
  checkboxes that call watch/unwatch directly. Opt-outs persist in the
  profile's unwatched_workspaces denylist.

Lifecycle states + UI
- DaemonStatus gains `profile`, `serverUrl`, and an `installing_cli`
  state. Panel shows Profile / Server info rows and a "Setting up…"
  blurb during first-run CLI download; failure surfaces a Retry button.
- Status bar renders a spinner during installation and hides the Start
  button until setup finishes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(desktop): register /onboarding route

The create-workspace modal navigates to /onboarding on success, but
the Desktop router only had flat routes (issues, projects, runtimes,
etc.) — resulting in an "Unexpected Application Error! 404 Not Found"
page after creating a new workspace.

Mirror the web app's wiring: render OnboardingWizard with onComplete
pushing to /issues, via the shared navigation adapter.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(desktop): remove sidebar daemon status bar

Drop the bottom-left daemon indicator in favor of the DaemonRuntimeCard
at the top of the Runtimes page, which already shows the same info
plus full Start/Stop/Restart controls and the Logs entry point. A
single canonical place avoids fragmenting daemon status across the UI.

Also remove the now-unused `bottomSlot` prop from AppSidebar — Desktop
was the only consumer, Web never needed it, so keeping it would be
dead scaffolding.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(desktop): daemon panel layout and close button

- Logs section now fills the remaining vertical space down to the
  sheet bottom instead of being capped at h-64, which left a huge
  empty area below it. Top section (status, actions, watched list)
  keeps natural height as shrink-0; the watched list gets its own
  max-h-48 scroll so a long list can't push Logs off screen.
- Replace the Sheet's built-in close button with an explicit
  <button> wired directly to onOpenChange(false). The Base UI
  Dialog.Close wrapped in Button via the render prop wasn't firing
  on click in this panel; going straight through the controlled
  state guarantees it responds.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(desktop): make daemon panel clickable inside Electron drag region

The sheet opens at the top of the window, which visually overlaps the
TabBar's -webkit-app-region: drag zone. Even though the sheet portals
to document.body, Chromium computes drag regions over the final
composited pixels, so the sheet inherited "drag" and swallowed the
mouseup of every click (mousedown fired but click never resolved) —
including the X close button.

Mark the entire SheetContent popup with -webkit-app-region: no-drag
to subtract it from the drag region. This also fixes future buttons /
checkboxes inside the sheet that would have hit the same issue.

While here, move the close button into the SheetHeader as a flex
sibling of SheetTitle instead of an absolutely positioned overlay —
simpler layout and avoids any stacking-context weirdness.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(desktop): clickable daemon runtime card row

The whole Local Daemon row now opens the sheet panel — icon, title,
and status line are all part of one click target. This replaces the
standalone "Logs" button, which was redundant now that clicking
anywhere on the row does the same thing.

The right-side action cluster (Start / Stop / Restart) wraps its
onClick in stopPropagation so pressing those buttons doesn't bubble
up and open the panel.

Keyboard access: Enter / Space on the focused row opens the panel,
with a focus-visible background for feedback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(runtimes): mark Desktop-launched daemons as managed

When the Multica Desktop app spawns the CLI it ships with, the
resulting daemon shares its binary with the Electron bundle — Desktop
is responsible for updating that binary on every release. Letting the
daemon self-update would just get clobbered on the next Desktop launch
and could brick the embedded binary mid-update.

Propagate a "launched_by" signal end-to-end so the UI can hide the
CLI self-update affordance (and the daemon refuses updates as a second
line of defense):

- Desktop's startDaemon spawns execFile with env MULTICA_LAUNCHED_BY=desktop.
- daemon.Config gains LaunchedBy; cmd_daemon reads the env var on boot.
- registerRuntimesForWorkspace includes launched_by in the request body.
- Server DaemonRegister folds launched_by into runtime.metadata (JSONB
  — no migration needed).
- handleUpdate returns a "failed" status with an explanatory message
  when LaunchedBy == "desktop", so even a bypass API call can't trigger
  the self-update path.
- RuntimeDetail extracts metadata.launched_by and passes it to
  UpdateSection, which swaps the Latest / → available / Update button
  cluster for a muted "Managed by Desktop" label.

CLI-only users (brew install, direct tarball) keep the exact same
behavior — the env var is empty, the UI shows the update button,
the daemon still self-updates on request.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(desktop): harden daemon manager from PR review

- syncToken now takes userId and mints a fresh PAT on user switch,
  restarting a running daemon so it picks up the new credentials.
  A .desktop-user-id sidecar in each profile records the owner so a
  previous user's cached PAT can't be reused on the next login.
- App.tsx wires onLogout on CoreProvider to daemonAPI.clearToken()
  and daemonAPI.stop() so the cached PAT and live daemon don't
  outlive the session.
- startLogTail replaced with a cross-platform watchFile
  implementation (initial 32 KB window + poll for new bytes,
  handles truncation). spawn("tail") was broken on Windows.
- writeProfileConfig now serializes through a promise chain to
  prevent concurrent writes from corrupting config.json.
- startDaemon keeps the "starting" state until pollOnce confirms
  /health, avoiding a running → stopped flash when the Go daemon
  isn't yet listening after the supervisor returns.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(desktop): verify downloaded CLI against checksums.txt

Download goreleaser's checksums.txt alongside the release archive,
parse the sha256 lookup, stream the archive through createHash, and
refuse to install on mismatch or missing entry. Closes the supply-
chain gap where auto-install would execute an unverified binary on
first launch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(desktop): lint and style cleanups from PR review

- eslint.config.mjs: add scripts/**/*.{mjs,js} override with
  globals.node so bundle-cli.mjs lints clean (was erroring on
  undefined process/console).
- daemon-panel.tsx: log level classes now use semantic tokens
  (text-info, text-warning, text-destructive) instead of hardcoded
  Tailwind colors; escape the apostrophe in the retry copy.
- daemon-settings-tab.tsx: import DaemonPrefs from shared/daemon-
  types instead of redefining it.
- runtimes-page.tsx: fix indentation inside the new topSlot wrapper.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Devv <devv@Devvs-Mac-mini.local>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: yushen <ldnvnbl@gmail.com>
2026-04-14 19:12:39 +08:00
Naiyuan Qing
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>
2026-04-14 18:21:11 +08:00
Bohan Jiang
bf71802451 fix(server): trigger agent on reply in thread where agent already participated (#981)
When a member replies in a member-started thread without @mentioning the
assigned agent, the on_comment trigger was suppressed — even if the agent
had already replied in that thread. This meant the common flow of
"member posts → agent replies → member follows up" would not re-trigger
the agent on the follow-up.

Add HasAgentRepliedInThread SQL query and check it in isReplyToMemberThread
so that agent participation in a thread is treated as an ongoing conversation.
2026-04-14 18:00:29 +08:00
Bohan Jiang
0a998d1cef Merge pull request #846 from multica-ai/agent/j/feb218fd
feat(agent): support custom environment variables for router/proxy mode
2026-04-14 15:34:21 +08:00
Jiayuan Zhang
9dfe119f47 fix(daemon): use runtime's owner_id for agent migration on upgrade (#941)
* fix(daemon): prevent duplicate runtime registration on profile switch

The daemon_id included a profile name suffix (e.g. "hostname-staging"),
so switching profiles created a new daemon_id that bypassed the UPSERT
dedup constraint, leaving orphaned runtime records in the database.

Three changes:
- Remove profile suffix from daemon_id — use stable hostname only.
  The unique constraint (workspace_id, daemon_id, provider) already
  prevents collisions within the same workspace.
- Auto-migrate agents from old offline runtimes to the newly registered
  runtime during DaemonRegister (same workspace/provider/owner).
- Add TTL-based GC in the runtime sweeper to delete offline runtimes
  with no active agents after 7 days.

Closes MUL-695

* fix(daemon): address code review issues on PR #906

1. Move gcRuntimes() to the main sweep loop — previously it was inside
   sweepStaleRuntimes() after an early return, so it only ran when new
   runtimes were marked stale. Now it runs every sweep cycle independently.

2. Fix DeleteStaleOfflineRuntimes to exclude runtimes with ANY agent
   reference (not just active ones). The FK agent.runtime_id is ON DELETE
   RESTRICT, so archived agents also block deletion.

3. Scope MigrateAgentsToRuntime to the same machine by matching
   daemon_id LIKE '<current_daemon_id>-%'. This prevents cross-machine
   agent migration when the same user has multiple devices.

* fix(daemon): use runtime's owner_id for agent migration, not caller's

The migration was gated on ownerID.Valid which is only true for PAT/JWT
registrations. Daemon token registrations (the common case for background
daemon restarts) had ownerID as zero, skipping migration entirely.

Fix: use registered.OwnerID (preserved via COALESCE on upsert) instead
of the caller's ownerID. This ensures migration runs even when the daemon
re-registers via daemon token after an upgrade.
2026-04-14 13:42:27 +08:00
Bohan Jiang
5e74c411dc fix(server): cancel active tasks when issue status changes to cancelled (#940)
When a user cancels an issue, active agent tasks now get cancelled
automatically. Previously, task cancellation only triggered on assignee
changes — the cancelled status was incorrectly treated like any other
agent-managed status transition.

Closes #926
2026-04-14 12:53:45 +08:00
devv-eve
7c7d7feed3 fix(storage): scope S3 upload keys by workspace (#936)
* fix(storage): scope S3 upload keys by workspace

Upload keys now use `workspaces/{workspace_id}/{uuid}.{ext}` instead of
flat `{uuid}.{ext}`, isolating file storage per workspace. Files uploaded
without workspace context (e.g. avatars) keep the flat key structure.

Refs: MUL-577

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(storage): scope user uploads under users/{user_id}/ prefix

Non-workspace uploads (avatars, profile images) now use
`users/{user_id}/{uuid}.{ext}` instead of flat `{uuid}.{ext}`,
matching the workspace-scoped pattern from the previous commit.

Refs: MUL-577

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(storage): fix LocalStorage for nested key paths

- Add MkdirAll before WriteFile to create intermediate directories
  for workspace/user-scoped keys
- Fix KeyFromURL to preserve full path after /uploads/ prefix instead
  of stripping to just the filename
- Update tests to match new behavior

Refs: MUL-577

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(upload): validate ownership before writing to storage

Move Storage.Upload after issue_id/comment_id ownership validation
to prevent orphaned files in S3 when validation fails. Previously,
the file was uploaded first and validation happened after, leaving
files in workspace-scoped S3 prefixes even on rejected requests.

Refs: MUL-577

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(upload): restore workspace membership check before upload

The membership check was accidentally removed during the upload
reordering refactor. Without it, any authenticated user could upload
files to any workspace by setting the X-Workspace-ID header.

Also restores the comment explaining the 200-on-DB-error behavior.

Refs: MUL-577

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Devv <devv@Devvs-Mac-mini.local>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 21:01:50 -07:00
devv-eve
8c0708bb5d fix(server): validate workspace membership for subscriptions and uploads (#935)
* fix(server): validate workspace membership for subscription targets and file uploads

Closes MED-1 (cross-workspace subscription injection) and MED-2 (file upload
missing workspace member validation) from the security audit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test(server): add negative tests for cross-workspace subscription and upload

Address PR review feedback:
- Add tests verifying cross-workspace user_id is rejected with 403 on
  subscribe and unsubscribe
- Add test verifying upload with foreign workspace_id is rejected with 403
- Make isWorkspaceEntity explicitly enumerate "member"/"agent" and reject
  unknown user types

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Devv <devv@Devvs-Mac-mini.local>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 20:22:03 -07:00
Bohan Jiang
0d95a7c7ef fix(auth): increase email verification code resend cooldown to 60s (#912)
The 10-second cooldown was too short. Increase to 60 seconds in both
frontend countdown timer and backend rate limit.
2026-04-14 02:35:34 +08:00
Jiayuan Zhang
880c614039 Merge pull request #905 from multica-ai/agent/emacs/3a193323
fix(issue): inherit parent project for sub-issues
2026-04-14 02:06:26 +08:00
Jiayuan Zhang
ff5f6ac2ee fix(daemon): prevent duplicate runtime registration on profile switch (#906)
* fix(daemon): prevent duplicate runtime registration on profile switch

The daemon_id included a profile name suffix (e.g. "hostname-staging"),
so switching profiles created a new daemon_id that bypassed the UPSERT
dedup constraint, leaving orphaned runtime records in the database.

Three changes:
- Remove profile suffix from daemon_id — use stable hostname only.
  The unique constraint (workspace_id, daemon_id, provider) already
  prevents collisions within the same workspace.
- Auto-migrate agents from old offline runtimes to the newly registered
  runtime during DaemonRegister (same workspace/provider/owner).
- Add TTL-based GC in the runtime sweeper to delete offline runtimes
  with no active agents after 7 days.

Closes MUL-695

* fix(daemon): address code review issues on PR #906

1. Move gcRuntimes() to the main sweep loop — previously it was inside
   sweepStaleRuntimes() after an early return, so it only ran when new
   runtimes were marked stale. Now it runs every sweep cycle independently.

2. Fix DeleteStaleOfflineRuntimes to exclude runtimes with ANY agent
   reference (not just active ones). The FK agent.runtime_id is ON DELETE
   RESTRICT, so archived agents also block deletion.

3. Scope MigrateAgentsToRuntime to the same machine by matching
   daemon_id LIKE '<current_daemon_id>-%'. This prevents cross-machine
   agent migration when the same user has multiple devices.
2026-04-14 01:52:34 +08:00
Jiayuan Zhang
a29ecfe02a test(issue): cover explicit sub-issue project 2026-04-14 01:51:48 +08:00
Jiayuan Zhang
8d3cb21c03 fix(auth): detect cookie-based session during CLI setup flow (#904)
* fix(auth): detect cookie-based session during CLI setup flow

When users run `multica setup` after logging into multica.ai, the CLI
redirects to the login page which only checked localStorage for an
existing session. Since the web app stores auth tokens as HttpOnly
cookies (not localStorage), the session was never detected and users
had to log in again.

Now the login page also tries `api.getMe()` (which sends the HttpOnly
cookie automatically) when no localStorage token exists. A new
`POST /api/cli-token` endpoint lets cookie-authenticated sessions
obtain a bearer token to hand off to the CLI.

* fix(auth): prioritise cookie auth over localStorage in CLI setup flow

Address code review feedback: cookie-first detection avoids authorising
the CLI with a stale or mismatched localStorage token. The useEffect now
calls getMe() without a bearer token first (relying on the HttpOnly
cookie), and only falls back to localStorage if cookie auth fails.

handleCliAuthorize uses an authSourceRef to pick the matching token
source — issueCliToken for cookie sessions, localStorage for token
sessions — preventing the click handler from re-reading a potentially
stale localStorage entry.
2026-04-14 01:46:14 +08:00
Jiayuan Zhang
56c38dc521 fix(issue): inherit parent project for sub-issues 2026-04-14 01:30:40 +08:00
Jiayuan Zhang
5b4ee7c5e1 fix(workspace): surface slug conflicts (#895) 2026-04-14 00:09:12 +08:00
Jiayuan Zhang
cd92aad9e1 fix(workspace): auto-retry slug conflicts and show editable URL field (#892)
Workspace creation with duplicate slugs now auto-appends -2, -3, … on
the server side instead of returning 409. The onboarding wizard also
shows an editable Workspace URL field (multica.ai/<slug>) that
auto-generates from the name but can be manually customized.
2026-04-13 23:08:11 +08:00
Bohan Jiang
0b60f78e8a fix(comment): set trigger_comment_id to actual reply, not thread root (#871)
* fix(comment): set trigger_comment_id to actual reply, not thread root

When a user replies in a thread and @mentions an agent, the enqueued
task's trigger_comment_id was incorrectly set to the parent (thread
root) comment instead of the reply that contained the mention. This
caused the agent to read the wrong comment and miss the user's actual
instructions.

Always pass comment.ID to EnqueueTaskForMention so agents see the
comment that triggered them.

Fixes MUL-708

* fix(task): resolve thread root in createAgentComment for reply triggers

With trigger_comment_id now correctly pointing to the actual reply
(not the thread root), createAgentComment must resolve to the thread
root before posting. Otherwise error/system comments would have
parent_id pointing to a nested reply, making them invisible in the
frontend's flat thread grouping.

Part of MUL-708
2026-04-13 19:53:23 +08:00
Bohan Jiang
a94c6481dd fix: compute sub-issue progress from database instead of paginated client cache (#865)
The sub-issue progress indicator (e.g. "0/2") was undercounting because
it was computed from the client-side issue list, which only loads the
first 50 done issues. Sub-issues marked as done beyond that page were
excluded from both the total and done counts.

Added a dedicated backend endpoint (GET /api/issues/child-progress) that
aggregates child issue counts directly from the database, ensuring
accurate totals regardless of client-side pagination or filtering.

Fixes MUL-702
2026-04-13 19:10:28 +08:00
Bohan Jiang
04747b45a2 fix(auth): add user-facing task messages endpoint for cookie auth (#853)
The frontend's listTaskMessages() was calling /api/daemon/tasks/{id}/messages
which uses DaemonAuth middleware (requires Authorization header). After the
cookie auth migration (#819), cookie-mode sessions don't send an Authorization
header, causing 401 on this endpoint. The 401 then triggers handleUnauthorized()
which clears the workspace context, cascading into 400 errors on all subsequent
requests.

Fix: add GET /api/tasks/{taskId}/messages under regular user auth middleware,
and update the frontend to use it instead of the daemon endpoint.

Closes #833
2026-04-13 18:03:25 +08:00
Jiayuan Zhang
01232fc2f9 feat(onboarding): add full-screen onboarding wizard for new workspaces (#852)
* feat(onboarding): add full-screen onboarding wizard for new workspaces

Replace auto-provisioned workspace with an interactive 4-step onboarding
wizard: Create Workspace → Connect Runtime → Create Agent → Get Started.

- Remove server-side ensureUserWorkspace() so new users land in onboarding
- Add onboarding wizard in packages/views/onboarding/ (4 steps)
- Wire login/OAuth callbacks to redirect to /onboarding when no workspace
- Add DashboardGuard onboardingPath fallback for workspace-less users
- Sidebar "Create workspace" navigates to /onboarding instead of modal
- Remove CreateWorkspaceModal (replaced by wizard step 1)
- Auto-generate workspace slug from name (no user-facing URL field)
- Unified CLI install flow: install.sh + multica setup (auto-detects local)
- Create onboarding issues on completion with interactive "Say hello" task

* test(auth): update workspace tests to match onboarding flow

Login no longer auto-creates workspaces — new users start with zero
workspaces and create one through the onboarding wizard. Update both
integration and handler tests to assert 0 workspaces after verify-code.
2026-04-13 17:59:51 +08:00
Jiang Bohan
a73a9d4036 fix(agent): address PR review — env var blocklist, unmarshal logging, stable React keys
1. Security: add isBlockedEnvKey() blocklist that rejects MULTICA_*
   prefix and critical system vars (HOME, PATH, USER, SHELL, TERM,
   CODEX_HOME) from custom_env injection
2. Observability: log warnings when json.Unmarshal fails on custom_env
   (agentToResponse + claim endpoint)
3. UX: use stable auto-increment IDs for env entry React keys instead
   of array index to prevent input focus/state issues on add/remove
2026-04-13 17:39:02 +08:00
pradeep7127
297b436e65 fix(issue): default create status to todo instead of backlog (#746)
* fix(issue): default create status to todo instead of backlog

Issues created without an explicit status now default to `todo` so the
local daemon picks them up immediately. Previously they defaulted to
`backlog`, which daemons ignore, leaving new issues silently idle until
a user manually moved them.

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

* test(issue): verify create defaults to todo, explicit backlog still works

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

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 16:49:35 +08:00
Jiang Bohan
4165401d16 feat(agent): support custom environment variables for router/proxy mode
Add per-agent custom_env configuration that gets injected into the agent
subprocess at launch time. This enables users to configure custom API
endpoints (ANTHROPIC_BASE_URL), API keys (ANTHROPIC_API_KEY), and cloud
provider modes (CLAUDE_CODE_USE_BEDROCK, CLAUDE_CODE_USE_VERTEX) without
requiring code changes.

Changes:
- Migration 040: add custom_env JSONB column to agent table
- Backend: custom_env in agent CRUD API + claim endpoint
- Daemon: merge custom_env into subprocess environment variables
- Frontend: env var editor in agent settings (key-value pairs with
  visibility toggle for sensitive values)

Closes #816
Related: #807, #809
2026-04-13 16:47:56 +08:00
yushen
2f1000d815 merge: resolve conflict with main (runTask refactor + mergeUsage)
Main introduced executeAndDrain/mergeUsage refactor. Resolve by keeping
main's refactored structure and re-applying EnvRoot to the switch/case
in runTask. Rename newTestDaemon → newGCTestDaemon to avoid collision
with the helper added in daemon_test.go on main.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 15:51:02 +08:00
Jiayuan Zhang
a3eefcf2c4 Revert "feat: add online status indicator on agent & member avatars (#821)" (#837)
This reverts commit 1d64ea4ba6.
2026-04-13 15:03:31 +08:00
yushen
ff206baa6f feat(daemon): add periodic GC for workspace isolation directories
Isolation directories accumulate indefinitely because they're preserved
for session reuse but never cleaned up after the issue is closed.

This adds a background GC loop that periodically scans local workspace
directories and removes those whose issue is done/canceled and hasn't
been updated for 5 days (configurable via MULTICA_GC_TTL). Orphan
directories with no metadata are cleaned after 30 days.

Changes:
- Write .gc_meta.json (issue_id, workspace_id) at task completion
- Add GET /api/daemon/issues/{issueId}/gc-check endpoint for status queries
- Add gcLoop goroutine to daemon with configurable interval/TTL
- Prune stale git worktree references from bare repo caches each cycle
- New env vars: MULTICA_GC_ENABLED, MULTICA_GC_INTERVAL, MULTICA_GC_TTL,
  MULTICA_GC_ORPHAN_TTL

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 14:46:48 +08:00
Jiayuan Zhang
1d64ea4ba6 feat: add online status indicator on agent & member avatars (#821)
* feat: add online status indicator dot on agent & member avatars

Backend:
- Track member presence via WebSocket connections in the Hub
- Broadcast member:online/offline events when users connect/disconnect
- Add GET /api/workspaces/{id}/members/online endpoint
- Add member:online and member:offline event type constants

Frontend:
- Add isOnline prop to ActorAvatar with a status dot at top-right corner
- Green dot = online, gray dot = offline, no dot = status unknown
- Fetch online member list via new query, update optimistically on WS events
- Derive agent online status from existing agent.status field
- Wire online status through ActorAvatar views wrapper (enabled by default)

* fix: address code review — fix hub tests and avatar rounding

1. Hub tests: consume the member:online presence event from the first
   connection before asserting on broadcast messages.
2. ActorAvatar: use rounded-[inherit] on the inner wrapper so callers
   can override rounding (e.g. rounded-lg for agent list items).

* fix: consume member:online presence event in integration test

Same fix as the hub unit tests — read and discard the member:online
event before asserting on issue:created in TestWebSocketIntegration.
2026-04-13 14:46:34 +08:00
Jiang Bohan
93fffad82a fix(daemon): embed triggering comment content in agent prompt
When a task is triggered by a comment, the agent prompt now includes
the comment content directly. This prevents the agent from ignoring
the comment when stale output files exist in a reused workdir.

Closes #805
2026-04-13 14:20:26 +08:00
LinYushen
95bfd7dd96 feat(auth): migrate auth token to HttpOnly Cookie & WebSocket Origin whitelist (#819)
* feat(auth): migrate auth token to HttpOnly cookie & implement WebSocket Origin whitelist

Security improvements from the MUL-566 audit report:

1. Auth token is now set as an HttpOnly, SameSite=Lax cookie on login,
   preventing XSS-based token theft. Cookie-based auth includes CSRF
   protection via double-submit cookie pattern. The Authorization header
   path is preserved for Electron desktop app and CLI/PAT clients.

2. WebSocket upgrader now validates the Origin header against a
   configurable allowlist (ALLOWED_ORIGINS env var), rejecting
   connections from unauthorized origins.

Backend: new auth cookie helpers, middleware reads cookie as fallback,
WS handler accepts cookie auth, Origin whitelist, logout endpoint.
Frontend: CSRF token in API headers, cookie-aware auth store and WS
client, web app opts into cookieAuth mode while desktop keeps tokens.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(auth): address PR review — Strict cookies, HMAC-bound CSRF, origin sync

1. SameSite=Lax → SameSite=Strict per spec requirement
2. CSRF token now HMAC-signed with auth token (nonce.signature format),
   preventing subdomain cookie injection attacks
3. allowedWSOrigins uses atomic.Value to eliminate data race
4. Removed magic "cookie" sentinel string in WSProvider — pass null token
   and guard with boolean check instead
5. Removed dead delete uploadHeaders["Content-Type"] in API client

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 12:13:35 +08:00
Bohan Jiang
1ee4e0501a fix(handler): add .claude/skills/ candidate path for skills.sh import (#792)
Skills stored under .claude/skills/{name}/SKILL.md (the Claude Code
native discovery convention) were not found during skills.sh import,
causing a 502 error. Add this path to the candidate list.

Fixes #777
2026-04-12 23:39:46 +08:00
Bohan Jiang
d74d7f2b7b fix(handler): add cycle detection to BatchUpdateIssues parent_issue_id handling (#788)
BatchUpdateIssues was missing the ancestor-walk cycle detection that
single UpdateIssue has. This allowed creating circular parent
relationships (e.g. A→B→A) via the batch API. Added the same
depth-limited walk (up to 10 ancestors) to detect and skip issues
that would create cycles, consistent with UpdateIssue behavior.
2026-04-12 23:03:46 +08:00
Qiaochu Hu
0c2102b951 fix(handler): fix batch operations and error handling bugs (#779)
fix(handler): fix batch operations and error handling bugs
2026-04-12 23:00:40 +08:00
Jiang Bohan
7312b5650c fix(server): fix ListRuntimeUsage to filter by date range instead of row count (#765)
Replace LIMIT $2 with AND date >= $2 in ListRuntimeUsage query. When a
runtime uses multiple models each day has multiple rows, so a row LIMIT
silently returns fewer days than requested.

Also fixes displayName warnings in issue-detail test mocks and adds
missing setOpen to useCallback deps in search-command.

Co-authored-by: jayavibhavnk <jaya11vibhav@gmail.com>
Closes #731
2026-04-12 22:46:07 +08:00
Antar Das
d32c419b6d feat(storage): add local file storage fallback (#710)
* feat(storage): add local file storage fallback

- Add local storage implementation for file uploads
- Update .env.example with LOCAL_UPLOAD_DIR and LOCAL_UPLOAD_BASE_URL
- Integrate local storage into server router and handlers
- Add storage abstraction layer with util functions

* ♻️ refactor(storage): improve path handling and file serving

switch from path to filepath for better cross-platform support and replace manual file serving logic with http.ServeFile to enhance security against path traversal. update unit tests to use t.Setenv for cleaner environment variable management.
2026-04-12 14:04:22 +08:00
Jiayuan Zhang
f100b5b707 fix(auth): graceful email degradation for self-hosting (#742)
* fix(auth): log email send errors and gracefully degrade in non-production

In non-production environments (APP_ENV != "production"), if sending the
verification code email fails, log the error as a warning and still return
success. This lets self-hosting users log in with the master code (888888)
even when their Resend configuration is incomplete (e.g. unverified from-domain).

In production, the behavior is unchanged — email failures return 500.

Also adds guidance in .env.example about RESEND_FROM_EMAIL for self-hosters.

Closes #723

* fix(auth): remove APP_ENV degradation, keep error logging only

Remove the APP_ENV-based graceful degradation for email send failures
— it's risky if users forget to set APP_ENV=production. Instead, always
return 500 on email failure (safe for production) and rely on the error
log (slog.Error) with the actual Resend error for debugging.

Self-hosters who don't need real emails should leave RESEND_API_KEY empty
(codes print to stdout, master code 888888 works).
2026-04-12 02:30:01 +08:00
Zheng Li
a76194744a feat(cli): add --project filter to issue list (#691)
Co-authored-by: nocoo <nocoo@users.noreply.github.com>
2026-04-11 14:37:24 +08:00
Bohan Jiang
5956280d56 fix(server): don't inherit parent agent mentions when reply has its own mentions (#693)
When a reply explicitly @mentions anyone (agents or members), the user
is making a deliberate choice about who to involve. Previously, replying
with @AgentB under a comment mentioning @AgentA would trigger both agents.
Now parent mentions are only inherited when the reply has no mentions at all.
2026-04-11 14:29:01 +08:00
Jiayuan Zhang
82bbce98fd fix(security): add workspace ownership checks to daemon API routes (#684)
* fix(security): add workspace ownership checks to all daemon API routes

Switch daemon routes from middleware.Auth to middleware.DaemonAuth and
add per-handler workspace ownership verification. This prevents
cross-workspace access to runtimes, tasks, usage, and daemon lifecycle
endpoints (HIGH-1/2/3 + CHAIN-1/2/3).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(security): support mdt_ daemon tokens in DaemonRegister + add regression tests

DaemonRegister now handles both auth paths:
- mdt_ daemon tokens: verify workspace match, skip member check, zero OwnerID
  (SQL COALESCE preserves existing owner on upsert)
- PAT/JWT: existing member check + OwnerID from member

Also adds WithDaemonContext helper and regression tests covering:
- Successful register with daemon token
- Workspace mismatch rejection
- Cross-workspace heartbeat rejection
- Cross-workspace task status rejection

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 12:49:23 +08:00
Jiayuan Zhang
f4016fc721 fix(server): validate workspace ownership for attachment uploads and queries (#683)
Prevent cross-workspace attachment injection (CRIT-3) by verifying
issue_id/comment_id belong to the caller's workspace before creating
attachment records. Add workspace_id filter to ListAttachmentsByCommentIDs
query (MED-3) to prevent cross-workspace attachment data leakage.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 04:33:24 +08:00