Add multi-select with batch update/delete support in the list view.
Users can select issues via checkboxes (per-row or per-status-group)
and apply bulk status, priority, assignee changes or delete via a
floating toolbar.
* feat(server): distinguish agent vs human CLI actions via X-Agent-ID/X-Task-ID headers
Extract resolveActor helper in handler to centralize agent identity resolution
from X-Agent-ID header with X-Task-ID cross-validation. Fix DeleteComment,
DeleteIssue, and UpdateComment handlers that previously hardcoded "member" as
actor type. Forward MULTICA_TASK_ID as X-Task-ID header from CLI client.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(server): add debug logging and test coverage for resolveActor
Add slog.Debug on agent/task validation failures for easier debugging.
Add TestResolveActor with 5 cases covering member fallback, valid agent,
non-existent agent, valid task, and mismatched task.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move workspace membership and role validation from individual handlers
into dedicated Chi middleware. The new middleware resolves workspace ID
(from query param, X-Workspace-ID header, or URL param), validates
membership via DB, and injects the member into request context.
Handlers now read workspace ID and member from context instead of
calling requireWorkspaceMember/requireWorkspaceRole directly. This
eliminates ~17 duplicated permission checks across handlers and makes
it harder to accidentally omit access control on new routes.
When agents use the multica CLI during task execution, their comments,
issue updates, and issue creations were attributed to the daemon's user
(via JWT) instead of the agent. Pass MULTICA_AGENT_ID env var from the
daemon, send X-Agent-ID header from the CLI client, and use it in
handlers to set the correct author/actor identity.
Previously, `multica login` only saved server_url to config when it was
empty. A stale value (e.g. from a prior session pointing at a different
host) was never overwritten, causing subsequent API calls to hit the
wrong server.
Agents now decide which repo to use based on issue context and check out
repos on demand via `multica repo checkout <url>`. Workspace repos are
cached locally as bare clones for fast worktree creation.
Key changes:
- Add repocache package for bare clone management (clone, fetch, worktree)
- Add `multica repo checkout` CLI command that talks to local daemon
- Add POST /repo/checkout endpoint on daemon health server
- Pass workspace repos metadata through register + task claim responses
- Remove pre-created worktrees from execenv (workdir starts empty)
- Update CLAUDE.md template to instruct agents to use `multica repo checkout`
- Pass MULTICA_DAEMON_PORT, WORKSPACE_ID, AGENT_NAME, TASK_ID env vars to agent
- Coalescing queue: use HasPendingTaskForIssue (queued/dispatched only)
instead of HasActiveTaskForIssue so comments during a running task
enqueue exactly one follow-up task that picks up all new comments.
- Stale task cleanup: runtime sweeper now fails orphaned tasks when
their runtime goes offline (daemon crash/network partition).
- Cancel-aware daemon: handleTask checks task status after execution
and discards results if the task was cancelled mid-run (e.g. reassign).
- Terminal issue guard: ClaimTaskForRuntime auto-cancels pending tasks
for done/cancelled issues instead of executing them.
- Race condition safety net: unique partial index ensures at most one
pending task per issue at the DB level.
Inbox events (new, read, archived, batch) are now sent via SendToUser
instead of broadcasting to the entire workspace room. Adds a new
Hub.SendToUser method. Also guards task broadcasts against deleted
issues to prevent global event leaks.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Inbox items were previously queried only by recipient, which leaked data
across workspaces. All list/count/batch operations now filter by
workspace_id from the X-Workspace-ID header.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add comprehensive data visualization to the runtime detail page:
- Daily token usage stacked area chart and daily cost bar chart
- Model distribution donut chart with cost breakdown
- GitHub-style activity heatmap (13 weeks of daily token usage)
- Hourly task distribution bar chart with new backend endpoint
- Responsive 2-column grid layout for charts on wide screens
Backend: new GET /api/runtimes/{runtimeId}/activity endpoint
returning hourly task counts from agent_task_queue.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The only path to marking a runtime offline was the daemon's deregister
call on graceful shutdown. If the daemon crashed, was killed, or lost
network, the status stayed "online" forever. Add a background goroutine
that sweeps every 30s and marks runtimes offline after 45s without a
heartbeat (3 missed intervals).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Refactor real-time sync from per-event precise mutations to WS-as-invalidation-signal + debounced refetch.
Backend:
- Add SubscribeAll to Event Bus — auto-broadcasts ALL events, eliminates manual 25-item allEvents list
- Add skill event constants to protocol, fix skill handler string literals
- Add title_changed activity tracking
Frontend:
- WSClient: add onAny() method for wildcard event subscription
- useRealtimeSync: rewrite to refreshMap + prefix routing + 100ms debounce
- Precise handlers only for side effects: workspace:deleted, member:removed, member:added (self-check)
- Reconnect now refetches all stores (fixes missing members/skills/workspace refresh)
- Stale-while-revalidate: fetch() only shows loading spinner on initial load, not on refetch
- Remove redundant useWSEvent in agents/page.tsx and skills-page.tsx
- WSClient.disconnect() now clears all handler registrations
Inbox bugfixes:
- Unify sidebar badge count with page count via dedupedItems + unreadCount in store
- Sort by time DESC (removed severity-first ordering)
- Ellipsis on truncated detail labels
UI:
- Status/Priority pickers: replace RadioGroup with MenuItem for auto-close on selection
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add missing notifications for priority_changed and due_date_changed events
- Publish priority_changed and due_date_changed flags from UpdateIssue handler
- Add details JSONB column to inbox_item (migration 019) for structured change data
- Store from/to values in details for status, priority, assignee, and due_date changes
- Notification titles now use plain issue title; details carry structured context
- Add human-readable label maps (statusLabels, priorityLabels) in notification listeners
- Update inbox handler responses to include details field
- Frontend: InboxDetailLabel renders rich subtitles per notification type
- Status: "Set status to ● In Progress" with StatusIcon
- Priority: "Set priority to ◆ High" with PriorityIcon
- Assigned: "Assigned to Bob" with resolved actor name
- Due date: "Set due date to Apr 20"
- Comment: truncated comment body preview
- Frontend: HoverCard on inbox items shows issue title + description context
- Add due_date_changed to InboxItemType and typeLabels
- Add tests for priority_changed and due_date_changed notifications
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Extract shared timeAgo utility, remove duplicates from comment-card and issue-detail
- Remove unused replies prop from CommentCard
- Fix recursive delete to remove all descendant replies, not just direct children
- Improve formatActivity with human-readable status/priority labels and actor names
- Validate parent comment exists and belongs to same issue before creating reply
- Add priority_changed activity recording in activity listeners
- Fix activity SQL query to sort ASC (was DESC, then re-sorted in handler)
- Fix reply-input layout alignment and test submit button selector
- Minor: .gitignore additions, button dark mode aria-expanded fix
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix activity:created WS payload to match frontend expectations
(issue_id at top level, entry as TimelineEntry object)
- Promote child comments to top-level when parent is deleted
(both in handleDeleteComment and WS comment:deleted handler)
- Enforce one-level reply nesting: reject replies to replies with 400
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the comment-only list with a Linear-style unified timeline that
interleaves field changes and comments chronologically.
Backend:
- activity_listeners.go: records field changes (status, assignee, description,
task completed/failed) to activity_log table on domain events
- Timeline API: GET /api/issues/{id}/timeline merges activity_log + comments
sorted by created_at
- Comment reply: parent_id column + handler support for threading
Frontend:
- Unified timeline replaces comment list: activity entries as compact muted
lines, comments as Card components with reply threading
- Filter toggle (All / Comments / Activity)
- Reply UI: inline editor under comments with Cancel/Reply buttons
- Real-time sync for activity:created + comment events
- 10 new Go tests, all passing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add comment explaining subscriber→notification listener registration order in main.go
- Add issue_status field to notifySubscribers and notifyDirect (fixes missing StatusIcon in inbox)
- Backfill existing commenters as subscribers in migration 016
- Add TODO comment for @mention duplicate notification prevention (deferred until @mention feature is enabled)
- Add context.Background() usage note for future bus-level timeout improvements
- Add toast error feedback on subscribe/unsubscribe failure
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace inbox_listeners.go with a subscriber-driven notification system:
- Add issue_subscriber table with auto-subscribe on create/assign/comment
- New subscriber_listeners.go: maintains subscriber data on domain events
- New notification_listeners.go: notifySubscribers (fanout to all subscribers
minus actor) and notifyDirect (targeted, punches through unsubscribe)
- Subscriber API: list/subscribe/unsubscribe endpoints
- Frontend: subscribers section in issue detail sidebar with real-time sync
- Frontend: inbox notification grouping by (issue_id, type, actor_id)
- Remove createInboxForIssueCreator from task.go (unified through event bus)
- 21 new Go tests, all passing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: decouple task lifecycle from issue status, add daemon health server
- Remove automatic issue status changes from StartTask (in_progress),
CompleteTask (in_review), and FailTask (blocked) in task service.
Issue status is now fully managed by the agent via `multica issue status`.
- Update agent prompt and meta skill to instruct agents to manage issue
status themselves (in_progress → done/in_review/blocked).
- Add daemon health HTTP server on 127.0.0.1:19514 with /health endpoint
exposing pid, uptime, agents, and workspaces. Fail fast if port is taken
(another daemon already running).
- Update `multica status` to check both server and daemon health.
- Add Save button to repos section in workspace settings UI.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor(daemon): simplify prompt, fix runtime config path, improve task error logging
- Slim down BuildPrompt to a minimal hint; detailed workflow now lives in CLAUDE.md/AGENTS.md
- Write CLAUDE.md to workDir root instead of .claude/CLAUDE.md
- Fix git-exclude pattern (.claude → CLAUDE.md)
- Decouple task queue reconciliation from issue status changes (agents manage status via CLI)
- Add diagnostic logging when CompleteTask/FailTask fail due to unexpected task state
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(task): use task_completed/task_failed inbox notification types
FailTask was sending "agent_blocked" which conflates agent crash with
issue-level blocked status. Align notification types with the new
decoupled model: task_completed and task_failed. Update frontend types
and labels accordingly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tests for truncateID, formatAssignee, resolveAssignee (6 cases),
and validIssueStatuses.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Make truncateID use rune counting instead of byte length for unicode safety
- Refactor workspaceGet and workspaceMembers to use newAPIClient helper
for consistent server-URL validation
- Add --output flag to issueStatusCmd for JSON output support
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Expose workspace details (including context field) and member listing
via the CLI so agents can dynamically query workspace context instead
of relying on static snapshots.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- resolveAssignee now reports actual API errors instead of silently
falling through to "not found" when both member/agent fetches fail
- Comment content truncation uses rune count for correct CJK handling
- Remove unnecessary _ = aType discard
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add `multica issue` command group with subcommands for full issue
lifecycle management: list, get, create, update, assign, status,
and comment operations. Includes assignee resolution by name across
both workspace members and agents.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adapt runtime features (usage tracking, ping, heartbeat) to main's
multi-workspace architecture. Update frontend imports from @multica/types
to @/shared/types after the package consolidation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add a new "Runtimes" sidebar tab to manage local agent runtimes with three
main capabilities: runtime status overview, token usage tracking (reading
Claude Code and Codex CLI local JSONL logs via daemon), and an interactive
connection test that sends a ping through the daemon to verify end-to-end
agent CLI connectivity.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Support importing skills from external sources (clawhub.ai and skills.sh)
via a new POST /api/skills/import endpoint. The backend auto-detects the
source from the URL, fetches skill metadata and files, and creates the
skill in the workspace. The frontend CreateSkillDialog now has two tabs:
Create (manual) and Import (paste URL with source auto-detection badge).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove unused ListWorkspaces/Workspace from daemon client, add log when
default workspace is set implicitly, document token reload limitation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- `multica workspace list` — list all user workspaces from API (with
watching indicator)
- `multica watch <id>` — add workspace to daemon watch list (top-level)
- `multica unwatch <id>` — remove from watch list (top-level)
- `multica watches` — show current daemon watch list (top-level)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add `multica workspace watch/unwatch/list` CLI commands
- Daemon watches multiple workspaces from config's `watched_workspaces`
- Registers runtimes per workspace, polls all runtime IDs in round-robin
- Hot-reload: daemon detects config file changes every 5s and
adds/removes workspaces without restart
- Remove `--workspace-id` flag from daemon (workspace selection is now
purely config-driven via `multica workspace watch`)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The daemon now reads the auth token from ~/.multica/config.json (set by
`multica auth login`) instead of requiring a browser-based pairing flow.
If not authenticated, it logs a message and exits.
Workspace ID is auto-resolved from the user's workspaces when not
explicitly set via flag/env.
Removed: daemon.json, pairing session flow, --config-path flag,
PairingSession type, PersistedConfig, LoadWorkspaceIDFromDaemonConfig.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ReposRoot was a daemon-level config that locked all tasks to a single
git repo. Replace with RepoPath in TaskContext so the server can specify
the repo per task. When not provided, daemon falls back to directory mode.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace bare HTML with a styled card layout featuring dark/light mode
support, Multica brand icon, and auto-close behavior.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add state parameter to CLI browser login flow for CSRF protection — CLI
generates a random state, frontend passes it through, CLI verifies on
callback. Also restrict cli_callback to http: scheme only.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Prevent open redirect / JWT theft by only allowing localhost/127.0.0.1
as cli_callback hostname
- URL-encode the callback URL in the login query string
- Simplify resolveAppURL to use os.Getenv directly (no phantom flag)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
`multica auth login` now opens the browser for email verification,
receives the JWT via localhost callback, and exchanges it for a PAT.
The legacy PAT-paste flow is preserved via `--token` flag.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>