- 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>
* feat(auth): add email verification login flow with 401 auto-redirect
Replace the old OAuth-based login with email verification codes:
- Backend: send-code / verify-code endpoints, verification_codes table (migration 009), rate limiting, Resend email service
- Frontend: two-step login UI (email → 6-digit OTP), auth store with sendCode/verifyCode
- SDK: ApiClient gains onUnauthorized callback; 401 responses auto-clear token and redirect to /login
- Fix login button staying disabled due to global isLoading state
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(auth): add brute-force protection, redirect loop guard, and expired code cleanup
- VerifyCode: increment attempts on wrong code, reject after 5 failed tries (migration 010)
- onUnauthorized: skip redirect if already on /login to prevent infinite loops
- SendCode: best-effort cleanup of expired verification codes older than 1 hour
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(auth): add master verification code for non-production environments
Allow code "888888" to bypass email verification in non-production
environments to simplify development and testing workflows.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(auth): add personal access tokens for CLI and API authentication
Add full-stack PAT support: users create tokens in Settings, CLI authenticates
via `multica auth login`. Server stores SHA-256 hashes only. Auth middleware
extended to accept both JWTs and PATs (distinguished by `mul_` prefix).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add actor_type/actor_id to inbox items for proper attribution
- Extract issue detail into features/issues/components/issue-detail.tsx
- Inbox page and store updates for actor-based notifications
- Sidebar, layout, and actor-avatar refinements
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace raw fmt/log calls with structured slog logger (Go) and
console-based logger (TypeScript). Add request logging middleware.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
pgxpool.New is lazy and doesn't connect immediately. Add pool.Ping()
after creation so CI environments without PostgreSQL skip cleanly
instead of failing with os.Exit(1).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rename the binary and all references from multica-cli to multica for a
cleaner command-line experience.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
TestWebSocketIntegration was timing out because registerListeners()
was never called — events published via bus had no listeners, so
WS broadcasts never happened.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Hub processes client registration asynchronously via a channel.
Without a short delay, the issue creation can fire before the client
is added to the workspace room, so the broadcast has no recipients.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
HandleWebSocket now requires auth — update test to include
token and workspace_id query params in the WebSocket URL.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Merge origin/main which added the skills system (structured skills
with meta skill runtime injection). Resolve 4 conflicts:
- workspace/store.ts: keep both skills state + issue/inbox fetch
- types/index.ts: keep Skill types + our event exports
- handler/agent.go: merge visibility filtering + skills batch loading
- pnpm-lock.yaml: accept main's lockfile with skills deps
Also fix skill.go: migrate h.broadcast → h.publish (event bus)
to match our architecture where all WS events go through the bus.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Migration 008 drops agent.skills column, so test fixtures inserting
into the agent table must no longer reference it.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace agent.skills TEXT field with structured skill/skill_file/agent_skill
tables. Skills are workspace-level entities with supporting files, reusable
across agents via many-to-many bindings.
Backend: migration 008, sqlc queries, CRUD handler, agent-skill junction,
structured skill loading in task context snapshot.
Daemon: meta skill injection via runtime-native config (.claude/CLAUDE.md
for Claude, AGENTS.md for Codex) so agents discover .agent_context/ skills
through their native mechanism. Lean prompt without inlined skill content.
Frontend: Skills management page, agent Skills tab picker, SDK methods,
TypeScript types, workspace store integration.
Also removes auto-creation of init issues when creating agents.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Move all CreateInboxItem calls from handlers to centralized inbox_listeners.go
- Enrich issue:updated payload with change context (assignee_changed, status_changed, prev values)
- Enrich comment:created payload with issue context (assignee info)
- Bus listeners handle: issue assign, unassign, reassign, status change, comment notification
- ListAgents filters private agents: only visible to owner_id or workspace admin
- Zero CreateInboxItem calls remain in handler package
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add internal event bus (server/internal/events/) with synchronous
pub/sub and panic isolation per listener
- Upgrade WebSocket Hub to workspace-scoped rooms with JWT auth
and membership verification on connect
- Add 10 new WS event types (comment CRUD, inbox read/archive,
agent create/delete, workspace/member events)
- Refactor all handlers and TaskService to publish events via Bus
instead of direct Hub.Broadcast calls
- Add WS broadcast listener that routes events to correct workspace
- Frontend: WSClient sends token + workspace_id on connect with
auto-reconnect refetch
- Frontend: centralized useRealtimeSync hook dispatches all WS
events to global Zustand stores
- Migrate issues and inbox pages from local useState to global
useIssueStore/useInboxStore
- Make store addIssue/addItem idempotent to prevent duplicates
- Remove dead packages/hooks/src/use-realtime.ts
- Add feature tracking files for 4 planned features
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Resolve conflicts:
- CLAUDE.md: merge feature-based frontend docs from main with comprehensive
architecture docs from HEAD
- Makefile: merge .PHONY targets from both branches
- settings/page.tsx: keep Context field using main's Label component
- auth-context.test.tsx: accept main's deletion (moved to features/auth/)
- cmd/daemon/daemon.go: accept HEAD's deletion (moved to internal/daemon/)
- daemon/client.go: port requestError type and isWorkspaceNotFoundError from
main's old daemon into the new internal/daemon package
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>